最全面的 Deno 入门教程

做者:ROBIN WIERUCH

翻译:疯狂的技术宅javascript

原文:https://www.robinwieruch.de/D...前端

未经容许严禁转载java

Deno 是新的 JavaScript 和 TypeScript 运行时。Node.js 的发明者 Ryan Dahl 于 2020 年发布了 Deno,做为 Node.js 的改进。可是 Deno 不是 Node.js,而是全新的 JavaScript 运行时,同时也支持 TypeScript。与 Node.js 类似,Deno 可用于服务器端 JavaScript,但其目的是消除 Node.js 所犯的错误。它就像 Node.js 2.0 同样,只有时间才能告诉咱们是否会像 2009 年使用 Node.js 同样去使用它。node

为何会有 Deno

Node(2009)和 Deno(2020)的发明者 Ryan Dahl 发布了 Deno 做为 JavaScript 生态系统的补充。当 Ryan 在会议上第一次宣布 Deno 时,他谈到了 Node.js 中的错误。Node.js 已经成为 JavaScript 生态中不可或缺的工具,已被数百万人使用,可是 Ryan Dahl 对当时作出的决定感到不满。如今 Ryan Dahl 但愿经过 Deno 解决 Node 的设计缺陷。 Deno 是由 V8 JavaScript 引擎、Rust 和 TypeScript 实现的用于安全服务器端的 JavaScript 和 TypeScript 全新运行时。git

  • 语言:JavaScript 和 TypeScript 是 Deno 运行时的第一语言。不管你用哪一种编写 Deno 程序,仅须要一个文件扩展名便可。尽管 TypeScript 不断受到欢迎,但具备一流 TypeScript 支持的 Deno 多是这种趋势的合适答案。
  • 兼容性:Deno 尝试与 Web 兼容——这意味着 Deno 程序应该能够在 Deno 和浏览器中运行。毕竟它只是一个可执行的 JavaScript(或 TypeScript)文件,不须要过多关注其环境。在考虑全部这些兼容性的同时,Deno 但愿经过使用现代 JavaScript 和 TypeScript 功能来确保可以面向将来。
  • 安全性:默认状况下,Deno 是安全的。除非开发人员容许,不然不会进行文件、网络或环境的访问。这能够防止 Deno 脚本的恶意使用,这种恶意使用极有可能与 Node 脚本同样多。
  • 标准库:Deno 带有标准库,这意味着 Deno 中的应用程序比 Node 程序更自洽,由于 Deno 在 JavaScript 之上具备许多内部工具函数。此外 Deno 带有一些内置工具,可改善开发体验。

如下各节将详细介绍全部这些要点,同时从头开始逐步实现一个小的 Deno 程序。以后咱们将继续用 Deno 开发真实的 Web 应用。程序员

在 MacOS、Windows 和 Linux 上安装 Deno

有多种方法来设置 Deno 应用程序。对你而言,这取决于你的操做系统和在计算机上安装程序的工具链。例如我在 MacOS 上用 Homebrew 来管理计算机上的程序。对于你来讲,可能还有其余选择,因此你应该从 Deno 网站获取的这个方法列表中为你的计算机使用适当的命令。这些命令应在集成终端或命令行界面中执行:golang

# Shell (Mac, Linux):
curl -fsSL https://Deno.land/x/install/install.sh | sh
 
# PowerShell (Windows):
iwr https://Deno.land/x/install/install.ps1 -useb | iex
 
# Homebrew (Mac):
brew install Deno
 
# Chocolatey (Windows):
choco install Deno
 
# Scoop (Windows):
scoop install Deno
 
# 用 Cargo 从源码构建并安装
cargo install Deno

安装 Deno 后,能够在命令行上验证其安装。你的版本可能比个人版本新,由于就我而言,我安装了 Deno 的第一个发行版本 1.0.0。可是如下各节将假定你安装了最新的 Deno 版本:面试

Deno --version
-> Deno 1.0.0

若是要升级Deno的版本,可使用 Deno upgrade。另外还能够经过命令行执行下面的远程 Deno 程序,来验证 Deno 在你的计算机上是否可以正确运行:编程

Deno run https://Deno.land/std/examples/welcome.ts
-> Welcome to Deno

这个 Deno 程序只是在你的命令行上输出一段文本。可是它还向你展现了如何经过动态下载和编译 Deno 程序来从远程源执行该程序。若是你没法在计算机上设置 Deno,请按照 Deno 官方网站 上的安装说明进行操做。json

HELLO Deno

每次咱们学习新的编程语言知识时,都从 “Hello World” 示例开始。让咱们的第一个 Deno 应用程也从这里开始。在命令行中,为你的 Deno 项目建立一个文件夹,进入到该文件夹​​,并建立一个新文件。你本身决定如何命名文件夹和文件:

mkdir Deno-project
cd Deno-project
touch index.js

而后在你喜欢的编辑器或 IDE 中打开新建立的 index.js 文件。输入如下 JavaScript 代码:

console.log('Hello Deno');

而后在命令行经过如下命令启动 Deno 程序。

Deno run index.js
-> Hello Deno

你的第一个 Deno 程序输出 “Hello Deno”。你已经为 Deno 项目建立了一个文件夹,为实现细节建立了一个 JavaScript 文件,并在命令行上经过 Deno 运行了该文件。无需其余设置。

Deno 的权限

如下各节将经过逐步介绍 Deno 的每一个方面,来改进咱们的第一个 Deno 程序。本节将讨论 Deno 中的权限,由于 Deno 在默认状况下是安全的。在本示例中,咱们将了解这究竟意味着什么。

若是你想像我同样随时了解技术主题,你可能已经知道 Hacker News。你能够在这个网站上阅读有关技术的最新新闻。我喜欢在本身的教程中使用 Hacker News 的 API。为了学习有关 Deno 和权限中的数据获取的知识,咱们将用这个 API 来获取数据。若是浏览 Hacker News API,则可以找到如下 URL 来请求有关某个主题下的文章:

http://hn.algolia.com/api/v1/search?query=...

咱们将在 Deno 项目的 index.js 文件中使用此URL,来获取有关 JavaScript 的 Hacker News 文章:

const url = 'http://hn.algolia.com/api/v1/search?query=javascript';

接下来,用 Deno 内置的 fetch 函数处理 URL,该函数在 URL 上执行 HTTP GET 请求,并返回 JavaScript promise。你能够经过将其转换为 JSON 并用日志记录语句输出其结果来解决这个 promise:

const url = 'http://hn.algolia.com/api/v1/search?query=javascript';
 
fetch(url)
  .then((result) => result.json())
  .then((result) => console.log(result.hits));

若是你用 JavaScript 写过前端程序 ,则可能已经注意到,咱们所使用的浏览器 API 为客户端程序提供了相同的 fetch API(或至少使用相同实现细节的接口)。如前所述,Deno 尝试与 Web 兼容,而且任何 Deno 程序在执行其代码时都应该可以在浏览器中以相同的方式工做。所以 Deno 确保客户端 JavaScript 程序中可用的 API 也能够在服务器端 Deno 应用程序中使用。

如今,在命令行上再次启动 Deno:

Deno run index.js

你应该会看到 Deno 提示的错误:“Uncaught PermissionDenied: network access to "http://hn.algolia.com/api/v1/search?query=javascript", run again with the --allow-net flag”。出现这个错误的缘由是,在默认状况下 Deno 是安全的。若是咱们在 Deno 的域中操做,能够无需授予Deno任何许可而作不少事情而。可是若是咱们想超越 Deno 的职责范围,则须要明确容许它。在这种从远程 API 获取数据的状况下,须要容许网络请求:

Deno run --allow-net index.js

再次运行 Deno 程序后,你应该在命令行上看到一系列 Hacker News 文章。这个数组中的每一个项目都有许多信息,为了便于阅读,让咱们精简每一个项目(文章)的属性。以后输出会应更具可读性:

const url = 'http://hn.algolia.com/api/v1/search?query=javascript';
 
fetch(url)
  .then((result) => result.json())
  .then((result) => {
    const stories = result.hits.map((hit) => ({
      title: hit.title,
      url: hit.url,
      createdAt: hit.created_at_i,
    }));
 
    console.log(stories);
  });

在本节中,你了解了 Deno 在默认状况下是安全的。咱们必须容许本身可以访问 Deno 领域之外的全部内容,多是网络访问或文件访问,不然 Deno 将会拒绝工做。

Deno的兼容性

前面你已经看到了怎样在 Deno 中使用 fetch。咱们对浏览器中的 fetch API 是很熟悉的。因此在Deno 中能够用与浏览器端彻底相同的接口,而没必要为 Deno 使用新的 API。在使用 Deno 时咱们不须要从新考虑本身的方法。

Deno 尝试跟上现代 JavaScript 功能,不管是在客户端仍是在服务器上。以 async/await 为例,它仅在较新的 Node.js 版本中可用,默认状况下在 Deno 中是可用的。你不只可使用 async/await,并且还可使用 async 的 top level await(这在 Node.js 中已经存在很长时间了):

const url = 'http://hn.algolia.com/api/v1/search?query=javascript';
 
const result = await fetch(url).then((result) => result.json());
 
const stories = result.hits.map((hit) => ({
  title: hit.title,
  url: hit.url,
  createdAt: hit.created_at_i,
}));
 
console.log(stories);

做为常规 promise 的 then 和 catch 块的代替,能够用 await 同步运行代码。在 await 语句以后的全部代码仅在 promise 解决后执行。若是这种实现要在函数中运行,则必须把函数声明为异步。开箱即用的 Deno 中提供了 Async/await 和 top level await。

Deno 的标准库

Deno 带有一组实用函数,这些函数被称为 Deno 的标准库(简称:Deno std)。 Deno 并无从外部库中导入全部内容,而是尝试经过提供几种内部解决方案来使其可用。接下来咱们尝试用下面的标准库解决方案之一来设置 Web 服务器:

import { serve } from 'https://Deno.land/std/http/server.ts';
 
const server = serve({ port: 8000 });
 
for await (const req of server) {
  req.respond({ body: 'Hello Deno' });
}

首先咱们用绝对路径从标准库中进行命名导入。在 Deno 中,全部库导入(不管是从标准库仍是从第三方库)均使用指向专用文件的绝对路径来完成。你从这个 以服务器文件形式存在的 http 库 导出一个名为served的函数。

serve 函数为咱们建立了一个 Web 服务器,可经过已定义的端口对其进行访问。 JavaScript for await ... of 用于遍历每一个传入此服务器的请求。对于每一个请求,服务器在响应正文中返回相同的文本。

再次运行你的 Deno 程序,而后在浏览器中导航到 http://localhost:8000 。由于要再次使用网络,因此须要受权:

Deno run --allow-net index.js

http://localhost:8000 和带有结尾斜杠的 http://localhost:8000/ 这两个 URL 在浏览器中的工做方式相同。当在浏览器中打开其中一个 URL 时,都会向 Deno 程序发出 HTTP GET 请求,而且该请求返回带有 Hello Deno 正文的 HTTP 响应,而后该响应将显示在浏览器中。

接下来用前面的代码扩展该示例。咱们不会从服务器(Deno)上将硬编码文本发送回客户端(浏览器),而是从 Hacker News 获取最重要的 JavaScript 文章并将其发送给客户端:

import { serve } from 'https://Deno.land/std/http/server.ts';
 
const url = 'http://hn.algolia.com/api/v1/search?query=javascript';
 
const server = serve({ port: 8000 });
 
for await (const req of server) {
  const result = await fetch(url).then((result) => result.json());
 
  const stories = result.hits.map((hit) => ({
    title: hit.title,
    url: hit.url,
    createdAt: hit.created_at_i,
  }));
 
  req.respond({ body: JSON.stringify(stories) });
}

再次启动 Deno 程序后,应该可以看到从 fetch 请求中获得的结果以 JSON 的形式打印在浏览器中。之前咱们只在命令行上看到这个结果,可是如今有了 Web 服务器,能够将结果发送到客户端(浏览器)。

Deno 库

只依赖 Deno 的标准库还不足以建立 Deno 程序,这就须要第三方库(也称为外部库或库)发挥做用了。若是你再次从浏览器的最后一部分中检查结果,可能会注意到 createdAt 的格式对人类很不友好,咱们将用 date-fns 库来使其可读:

Deno 中的库经过绝对路径直接从 Web 导入。在浏览器中再次打开 URL,并阅读其中的源代码,并检查它是否真的导出了默认函数,即此处的 format 函数:

import { serve } from 'https://Deno.land/std/http/server.ts';
import format from 'https://Deno.land/x/date_fns/format/index.js';
 
const url = 'http://hn.algolia.com/api/v1/search?query=javascript';
 
const server = serve({ port: 8000 });
 
for await (const req of server) {
  const result = await fetch(url).then((result) => result.json());
 
  const stories = result.hits.map((hit) => ({
    title: hit.title,
    url: hit.url,
    createdAt: format(
      new Date(hit.created_at_i * 1000),
      'yyyy-MM-dd'
    ),
  }));
 
  req.respond({ body: JSON.stringify(stories) });
}

format 函数有两个强制性参数:日期和格式化日期的模式。咱们从 Hacker News API 收到的日期是一个 unix 时间戳 ,以秒为单位;因此要先把它转换为毫秒,而后再从中建立 JavaScript 日期。为函数第二个参数提供的模式使日期易于阅读。

再次启动 Deno 程序后,你会看到它从库中下载了 format 函数以及全部依赖项。因为使用了函数的直接 URL,因此只下载了库的这一部分。若是试着包含整个库路径,将会看到整个库将被下载, format 只是许多命名的导出(大括号)之一:

import { serve } from 'https://Deno.land/std/http/server.ts';
import { format } from 'https://Deno.land/x/date_fns/index.js';
 
const url = 'http://hn.algolia.com/api/v1/search?query=javascript';
 
const server = serve({ port: 8000 });
 
for await (const req of server) {
  ...
}

当再次启动 Deno 时,都会使用被缓存的库,因此无需再次下载。它都会检查全部的导入,将其下载并捆绑到一个可执行文件中。在 Deno 中导入库的方式受到 Go 语言 的启发。没必要在文件中保留依赖项列表(例如,Node.js 的package.json),也不须要使全部模块在项目中可见(例如,Node.js 的 node_modules)。

Deno 的导入

你已经了解到 Deno 的标准库或第三方库的导入是经过绝对路径执行的。这种方法的灵感来自 Go 语言,由于它不会产生太多混淆的空间。由于你的 Deno 程序有多个文件,所以能够用相对路径导入它们。

来看看它是怎样工做的:首先,在项目中建立一个名为 stories.js 的文件,该文件应该与 index.js 文件在同一路径下。在 stories.js 文件中,输入如下代码实现,这段代码本质上上是咱们以前在其余文件中所作的映射:

import { format } from 'https://Deno.land/x/date_fns/index.js';
 
export const mapStory = (story) => ({
  title: story.title,
  url: story.url,
  createdAt: format(
    new Date(story.created_at_i * 1000),
    'yyyy-MM-dd'
  ),
});

stories.js 文件中进行命名导出,并在 index.js 文件中进行命名导入,并在稍后的代码中使用该函数:

import { serve } from 'https://Deno.land/std/http/server.ts';
 
import { mapStory } from './stories.js';
 
const url = 'http://hn.algolia.com/api/v1/search?query=javascript';
 
const server = serve({ port: 8000 });
 
for await (const req of server) {
  const result = await fetch(url).then((result) => result.json());
 
  const stories = result.hits.map(mapStory);
 
  req.respond({ body: JSON.stringify(stories) });
}

这就是在 Deno 中导出和导入文件的过程。与以前所用的绝对路径不一样,咱们用相对路径来导入必要的内容。还要注意的是,不管绝对路径仍是相对路径,咱们都必须始终包含文件扩展名,由于不能留下任何产生歧义的余地。

在 Deno 中进行测试

在编程的过程当中,测试不该该过后再去考虑,在 Deno 中也同样,测试是必不可少的。接下来经过编写第一个单元测试来了解其工做原理。首先建立一个新的 stories.test.js 文件。而后编写如下代码:

import { mapStory } from './stories.js';
 
Deno.test('maps to a smaller story with formatted date', () => {
 
});

Deno 为咱们提供了一个 test 功能,用来定义名称、说明和实际的测函数。怎样在函数主体中实现测试取决于咱们本身。咱们已经导入了要测试的函数(即 mapStory),该函数实际上只接收一个文章列表数组,并返回具备较少属性和格式化日期的新文章数组。咱们须要作的就是定义一个用于 mapStory 的文章列表和一个其假定为该函数的输出的文章列表:

import { assertEquals } from 'https://Deno.land/std/testing/asserts.ts';
 
import { mapStory } from './stories.js';
 
Deno.test('maps to a smaller story with formatted date', () => {
  const stories = [
    {
      id: '1',
      title: 'title1',
      url: 'url1',
      created_at_i: 1476198038,
    },
  ];
 
  const expectedStories = [
    {
      title: 'title1',
      url: 'url1',
      createdAt: '2016-10-11',
    },
  ];
 
  assertEquals(stories.map(mapStory), expectedStories);
});

Deno 的标准库提供了 assertEquals 函数用来声明两个值。第一个值是要测试的函数的输出,第二个值是预期的输出。若是二者都匹配,则测试应变为绿色。若是它们不匹配,则测试应失败并变为红色。在命令行上运行全部测试:

Deno test
 
-> running 1 tests
-> test maps to a smaller story with formatted date ... ok (9ms)
 
-> test result: ok. (10ms)
-> 1 passed;
-> 0 failed;
-> 0 ignored;
-> 0 measured;
-> 0 filtered out

测试变成绿色。用 Deno test 命令将拾取全部具备命名模式 test.{js,ts,jsx,tsx} 的文件。你也能够用 Deno test <file_name> 仅测试特定文件,在本例中为 Deno test stories.test.js

在 Deno 中使用 TypeScript

Deno 支持把 JavaScript 和 TypeScript 同时做为第一语言。这就是为何进行文件导入时要始终包含文件扩展名的缘由——不管这些文件是从 Deno 项目的相对路径导入仍是从 Deno 标准库或第三方库绝对路径导入。

因为 Deno 把 TypeScript 做为一等公民提供支持,因此能够把 stories.js 文件重命名为 stories.ts。你会注意到须要调整全部导入——在 index.jsstories.test.js 中指向该文件,由于文件扩展名从 .js 被改成了 .ts

带有全部实现细节的 stories.ts 文件如今须要类型。咱们将经过 StoryFormattedStory 提供函数输入和输出的接口:

import { format } from 'https://Deno.land/x/date_fns/index.js';
 
interface Story {
  title: string;
  url: string;
  created_at_i: number;
}
 
interface FormattedStory {
  title: string;
  url: string;
  createdAt: string;
}
 
export const mapStory = (story: Story): FormattedStory => ({
  title: story.title,
  url: story.url,
  createdAt: format(
    new Date(story.created_at_i * 1000),
    'yyyy-MM-dd'
  ),
});

如今这个 function 已彻底类型化了。经过将 stories.test.js 文件重命名为 stories.test.ts,并将 index.js 文件重命名为 index.ts,你能够本身继续把 JavaScript 转换为 TypeScript。这些新的 TypeScript文件并非都须要添加类型或接口,由于大多数类型是自动推导的。

若是要再次启动 Deno 应用程序,这时必须调整 Deno 脚本的文件扩展名:

Deno run --allow-net index.ts

Deno 带有默认的 TypeScript 配置。若是要自定义它,能够添加自定义 tsconfig.json 文件。毕竟,因为 TypeScript 和 JavaScript 同样,都是一等的公民,因此由你本身决定为未来的 Deno 项目选择哪一种文件扩展名。

Deno中的环境变量

环境变量很是适合隐藏有关 Deno 程序的敏感信息。这能够是 API 密钥、密码或他人不该该看到的数据。这就是咱们要经过建立 .env 文件来隐藏敏感信息的缘由。接下来咱们将建立这个文件,并把如下信息传给服务器程序的端口:

PORT=8000

index.ts 文件中,咱们能够把这个环境变量与第三方库在一块儿配合使用:

import { serve } from 'https://Deno.land/std/http/server.ts';
import { config } from 'https://Deno.land/x/dotenv/mod.ts';
 
import { mapStory } from './stories.ts';
 
const url = 'http://hn.algolia.com/api/v1/search?query=javascript';
 
const server = serve({
  port: parseInt(config()['PORT']),
});
 
for await (const req of server) {
  const result = await fetch(url).then((result) => result.json());
 
  const stories = result.hits.map(mapStory);
 
  req.respond({ body: JSON.stringify(stories) });
}

config 函数从 .env 文件返回带有有全部键值对的对象。咱们必须将 'PORT' 键的值解析为数字,由于它能够在对象中做为字符串使用。如今该信息不会存在于源代码中,而仅在环境变量文件中可用。

再次启动 Deno 程序后,你应该在命令行上看到另外一个权限错误:"Uncaught PermissionDenied: read access to "/Users/mydspr/Developer/Repos/Deno-example", run again with the --allow-read flag"。能够用另外一个权限标志来容许访问环境变量:

Deno run --allow-net --allow-read index.ts

重要提示:.env 文件不该在每一个人均可以看到的公共存储库中共享。若是你将源代码公开(例如在GitHub上),请考虑将 .env 文件添加到 .gitignore 文件中。

毕竟服务器程序的端口不是敏感数据的最好例子。咱们使用端口是为了了解环境变量。可是一旦你处理了Deno 程序的更多功能,最终可能会获得源代码所中使用的信息,这些信息对于其余人不可见。


总结

本文向你介绍了 Deno 全部的基础知识。从小型脚本到功能完善的服务器应用,Deno 将在与 Node.js 相同的领域中使用,但其默认设置会大大改善。在默认状况下,它是权限是安全的,并与许多客户端的 API 兼容,有着诸如 top level await 等现代功能,并支持 JavaScript 和 TypeScript 。

173382ede7319973.gif


本文首发微信公众号:前端先锋

欢迎扫描二维码关注公众号,天天都给你推送新鲜的前端技术文章

欢迎扫描二维码关注公众号,天天都给你推送新鲜的前端技术文章

欢迎继续阅读本专栏其它高赞文章:


相关文章
相关标签/搜索