Deno 1.0即将发布,你需要知道的都在这里了

将近两年过去了,漫长的等待已接近尾声。Deno 1.0 的 API 已被冻结,离 5 月 13 日的正式发布只剩几十个小时了。

Deno 肯定会因为自己著名的创建者和富有前瞻性的理念,成为 JavaScript 领域近期最令人兴奋和最具争议性的软件产品。

Deno 是一款通用的 JavaScript/TypeScript 编程环境。它汇集了许多最出色的开源技术,并使用一个很小的可执行文件提供了全面的解决方案。

Deno 由 Ryan Dahl 创建,他最出名的头衔是 Node.js 的幕后策划者。Deno 充分利用了自 2009 年 Node.js 发布以来不断加强的 JavaScript 特性。它还解决了 Ryan 在他的“Node.js 令我感到遗憾的 10 件事”中谈到的设计缺陷。有些人称其为 Node.js 的续作,尽管作者本人并未提出这种主张。

https://www.youtube.com/watch?v=M3BM9TB-8yA

与用 C++ 编写的 Node.js 不同,Deno 是用 Rust 编写的。它建立在 Tokio 平台之上,并且像 Node.js 一样使用 V8 引擎执行 JavaScript 代码。它的一项出众特性是内置了 TypeScript。尽管它仍需要编译为 JavaScript 才能运行,但这是在内部完成的,因此对用户来说 TypeScript 的行为就好像它是原生支持的一样。

入门

要下载 Deno,请按照首页上的说明操作。要更新到将来的版本,请使用 deno upgrade 命令。

要获得关于 Deno 子命令的帮助,请使用以下任一命令。

  • 获取摘要:deno [subcommand] -h
  • 获取详细信息:deno [subcommand] --help

在这篇指南中,我们将介绍 Deno 1.0 所提供的所有杀手级功能,并提供如何以最新语法使用它们的示例。我会尽可能使用 TypeScript,等效的 JavaScript 方法大家应该都能看出来。

我相信,看完这篇文章后你会爱上 Deno 的。这份指南应该能为你提供入门 Deno 所需的一切内容。

安全性

默认情况下,Deno 是安全的。相比之下,Node.js 拥有对文件系统和网络的完全访问权限。

要在没有权限的情况下运行程序,请使用:

deno run file-needing-to-run-a-subprocess.ts

如果代码需要权限设置,则会提醒你。

error: Uncaught PermissionDenied: access to run a subprocess, run again with the --allow-run flag

Deno 使用命令行选项来显式许可访问系统的各个部分。最常用的包括:

  • 环境访问
  • 网络访问
  • 文件系统读 / 写访问
  • 运行一个子进程

要查看权限示例的完整列表,请输入 deno run -h。

这里的最佳实践是在 read、write 和 net 上使用权限白名单。这样你就可以更清楚地了解 Deno 被允许访问哪些内容。例如,要允许 Deno 在 /etc 目录中只读文件,请使用:

deno --allow-read=/etc

使用权限的快捷方式

你可能很快就会厌倦每次运行应用程序时都要显式启用权限的操作。要解决这个问题,你可以采用以下任一种方法。

1. 允许所有权限

你可以使用 --allow-all 或其快捷方式 -A 启用所有权限。我不建议这样做,因为它抹除了权限控制所带来的安全性优势。

2. 制作一个批处理脚本

为运行程序所需的最小权限创建一个 bash 脚本。

#!/Bin/Bash

// Allow running subprocesses and file system write access
deno run --allow-run --allow-write mod.ts

这样做的缺点是你可能需要好几个脚本,分别用于运行、测试和打包场景。

3. 使用任务运行器

你可以使用 GNU 工具 make 创建一个带有一组 Deno 命令的文件来处理权限。你还可以使用针对 Deno 的版本 Drake

4. 安装一个可执行的 Deno 程序

使用 deno install 命令安装一个 Deno 程序,该程序具有执行所需的所有权限。安装完成后,你可以从 $PATH 中的任何位置访问它:

https://deno.land/std/manual.md#installing-executable-scripts

标准库

Deno 标准库是由 Deno 项目维护,并保证可用于 Deno 的常用模块集合。它涵盖了用户最常用的常见任务代码,并且是基于 Go 编程语言提供的标准库。

JavaScript 一直以来困扰用户的一个问题就是缺乏标准库。用户被迫一次又一次地重新发明轮子,开发人员不得不经常在 npm 上搜索第三方模块,以解决本应由平台制造商解决的常见问题。

React 之类的库可以解决很多复杂问题,这些第三方包很好用。但是对于 UUID 生成等简单的事情来说,我们最好使用官方的标准库。这些小型库可作为大型库的构建块,从而加快了开发速度并减少了开发人员的负担。曾一度流行的库被遗弃,留给用户自己维护或者需要用户找一个替代品的事情时有发生。实际上,常用的 OSS 包中有 10%到 20%并没有得到积极维护:

https://blog.tidelift.com/up-to-20-percent-of-your-application-dependencies-may-be-unmaintained

可用模块及其 npm 等效

Deno 内置了 Typescript

TypeScript 是添加了显式类型的 JavaScript。任何有效的 JavaScript 代码也是有效的 TypeScript 代码,因这个将你的代码转换为 TypeScript 是非常容易的。只需将扩展名更改为.ts 并开始添加类型即可。

要在 Deno 中使用 TypeScript 无需执行任何操作。没有 Deno 时,必须将 TypeScript 编译为 JavaScript 才能运行。Deno 会在内部为你完成这个步骤,让 TypeScript 更容易上手。

使用自己的 tsconfig.json

对于熟悉 TypeScript 的人来说,你会习惯使用 tsconfig.json 文件来提供编译器选项。使用 Deno 时这是可选的,因为它已经有了自己的默认配置。如果你使用自己的 tsconfig.json 且与 Deno 冲突,则会收到警报。

这个功能需要 -c 选项和 tsconfig.json。

deno run -c tsconfig.json [file-to-run.ts]

有关默认 tsconfig.json 设置的完整细节,请参见 Deno 手册

大多数开发人员很高兴能看到 Deno 默认使用 strict 模式。除非有不怀好意之人改写它,否则 Deno 将针对用户那些草率的编码实践尽量提出合理的警告。

Deno 尽可能使用 Web 标准

创建一个 Web 标准需要很长时间,但一旦标准被确定下来,我们就不应该忽略它。虽然框架来来去去,但 Web 标准是会长期存在的。花费在学习标准化 API 上的时间永远不会白费,因为没有人敢于破坏 Web;它可能已经使用了数十年,甚至可能在你剩下的职业生涯中一直发光发热。

fetch 这个 Web API 提供了用于提取资源的接口。浏览器中有一个 JavaScript fetch() 方法。如果你想在 Node.js 中使用这个标准,则需要使用第三方库 Node Fetch。在 Deno 中它是内置的,并且就像浏览器版本一样开箱即用。

Deno 1.0 提供以下与 Web 兼容的 API。

  • addEventListener
  • atob
  • btoa
  • clearInterval
  • clearTimeout
  • dispatchEvent
  • fetch
  • queueMicrotask
  • removeEventListener
  • setInterval
  • setTimeout
  • AbortSignal
  • Blob
  • File
  • FormData
  • Headers
  • ReadableStream
  • Request
  • Response
  • URL
  • URLSearchParams
  • console
  • isConsoleInstance
  • location
  • onload
  • onunload
  • self
  • window
  • AbortController
  • CustomEvent
  • DOMException
  • ErrorEvent
  • Event
  • EventTarget
  • MessageEvent
  • TextDecoder
  • TextEncoder
  • Worker
  • ImportMeta
  • Location

这些都可以在程序的顶级范围内使用。这意味着如果你不去用 Deno() 命名空间上的任何方法,那么你的代码应该能同时与 Deno 和浏览器兼容。尽管这些 Deno API 并不是都 100%符合其等效的 Web 规范,但这对前端开发人员而言仍然是一个很大的好处。

ECMAScript 模块

相比 Node.js,Deno 的一项主要进步是它使用了官方的 ECMAScript 模块标准,而不是老式的 CommonJS。Node.js 直到 2019 年底才在 13.2.0 版本中启用 ECMAScript 模块,但支持还是不够成熟,并且仍然包含有争议的.mjs 文件扩展名。

Deno 在模块系统中使用了现代 Web 标准,从而避免了旧时代的影响。模块使用 URL 或文件路径引用,并包含必需的文件扩展名。例如:

import * as log from "https://deno.land/std/log/mod.ts";
import { outputToConsole } from "./view.ts";

使用文件扩展名的问题

Deno 期望模块具有文件扩展名,但 TypeScript 没有。

在任何地方都使用文件扩展名都是合乎逻辑的,并且似乎是显而易见的方法。不幸的是,实践中事情要复杂得多。现在,你可以使用 Visual Studio Code Extension 来为只用 Deno 的项目解决这个问题:

https://marketplace.visualstudio.com/items?itemName=axetroy.vscode-deno

对于 TypeScript 的创建者来说,这个问题似乎引起了争议。在我们最终放弃 CommonJS 之前,应该是不存在一种快速简便的解决方案的。

让我们花点时间向明智而古老的编程之神祈祷吧。让他们废除这些传统格式,并惩罚那些损害我们所有人利益的守旧者。

包管理

Deno 中包管理的工作机制是经过彻底的重新设计的。它是去中心化的,不依赖什么中央存储库。任何人都可以托管一个包,就像任何人都可以在 Web 上托管任何类型的文件一样。

使用像 npm 这样的中心化存储库有优点也有缺点,而 Deno 在这一方面肯定是最能引发争议的。

Deno 的全新包管理机制

这种机制如此简单,可能会让你非常惊讶。

import { assertEquals } from "https://deno.land/std/testing/asserts.ts";

具体分析一下。

  • 不再有中心化的包管理器了。你可以直接从 Web 导入 ECMAScript 模块。
  • 不再有“神奇的”Node.js 模块解析了。现在语法是明确的,这让各种事情更容易推理。
  • 不再有 node_modules 目录。现在依赖项会下载并隐藏在你的硬盘中。如果要刷新缓存并再次下载,只需在命令中添加 --reload。

如果你要与项目代码一起下载依赖项而不是使用全局缓存,请使用 $DENO_DIR env 变量。

寻找兼容的第三方库

有一个用户区是为与 Deno 兼容的第三方模块准备的,但是在本文撰写时它还相当原始。例如,用户没法按受欢迎程度或下载数量搜索模块。我预计这个用户区将会扩大,或者会出现一些替代的模块站点。

尽管 Deno 没有官方支持对 Node.js 的向后兼容性,但也有许多库和应用程序可以正常用于 Deno。有些是开箱即用的,而另一些则需要一些准备工作。

安装第三方模块

Deno 刚诞生不久,其生态系统仍在初步发展阶段。在撰写本文时,我建议将 Pika 作为在标准库和用户库之后开始寻找兼容模块的第一站。

Pika 的开发人员已与 Deno 合作,通过称为 X-TypeScript-Types的 ECMAScript 模块提供了 TypeScript 类型。要使用 Pika 查找包:

Package found! However, no web-optimized "module" entry point was found in its package.json manifest.

但 preact 是兼容的。点击它,然后点击导入。你应该能看到以下内容:

将 import 语句复制到你的代码中。

import * as pkg from "https://cdn.pika.dev/preact@^10.3.0";

超越 Package.Json

大多数 JavaScript 生态系统还是围绕 package.json 建立的。它已经膨胀得很大了,其中包含许多职责,诸如:

  • 保留项目的元数据
  • 列出项目依赖项和版本控制
  • 将依赖项分类为 dpendencies 或 devDependencies
  • 定义程序的入口点
  • 存储与项目相关的 Shell 脚本
  • 定义一个类型类别,最近被引入以改进 ECMAScript 模块支持:
{
  "name": "Project Name", // metadata
  "version": "1.0.0", // metadata
  "description": "My application", // metadata
  "type": "module", // module functionality
  "main": "src/mod.ts", // module functionality
  "scripts": {
    "build": "npm run _copy-build-files && rollup -c",
    "build-watch": "npm run _copy-build-files && rollup -cw"
  }, // scripting functionality
  "license": "gpl-3.0", // metadata
  "devDependencies": {
    "@rollup/plugin-typescript": "^3.1.1",
    "rollup": "^1.32.1",
    "typescript": "^3.8.3"
  }, // versioning and categorizing functionality
  "dependencies": {
    "tplant": "^2.3.3"
  } // versioning and categorizing functionality
}

所有这些实践随着时间的流逝而融合在一起,现在成为了 JavaScript 生态系统的标准运作方式。但我们很容易忘记这并不是官方标准的事实;只有当这些功能成为必需品时才会想到它。现在 JavaScript 已经迎头赶上,是时候重新思考了。Deno 仍无法取代 package.json 的所有功能,但目前有一些解决方案。

使用 deps.ts 和 URL 进行版本控制

针对包版本控制有一个 Deno 约定,那就是使用名为 deps.ts 的一种特殊文件。在内部,依赖项被重新导出。这就能让应用程序中的不同模块都引用相同的源。

不是告诉 npm 要下载哪个模块版本,而是在 deps.ts 中的 URL 中引用。

export { assert } from "https://deno.land/std@v0.39.0/testing/asserts.ts";
export { green, bold } from "https://deno.land/std@v0.39.0/fmt/colors.ts";

如果要更新任何模块,你可以在 deps.ts 中更改 URL。例如,将 @v0.39.0 替换为 @v0.41.0,之后新版本将在所有位置上启用。如果你改为直接将 https://deno.land/std@v0.39.0/fmt/colors.ts 导入每个模块,你就得费劲地遍历整个应用程序并更改每个引用。"你之前下载的模块以后不会被篡改"这种假设可能会带来安全风险。所以我们还可以选择创建锁定文件。这将确保新下载的模块与你最初下载的模块是一样的。

Deno doc使用JSDoc处理元数据

JSDoc 于 1999 年发布,到现在已经过去了 21 年。现在,它是为 JavaScript 和 TypeScript 编写文档的最常用和受支持最广泛的方法。虽然它不是正式的 Web 标准,但它是 package.json 中所有元数据的一个完美替代品。

/**
 * @file Manages the configuration settings for the widget
 * @author Lucio Fulci
 * @copyright 2020 Intervision
 * @license gpl-3.0
 * @version 1.0
 *

Deno 开箱即用地支持 JSDoc,并将其用于内置的文档系统。虽然 deno doc 命令现在不使用上面的元数据,但它会读取函数的描述及其参数的描述。

/**
 * Returns a value of (true?) if the rule is to be included
 *
 * @param key Current key name of rule being checked
 * @param val Current value of rule being checked
 **/

你可以使用 deno doc <文件名>命令来查看程序的文档。

deno doc mod.ts

function rulesToRemove(key: string, val: any[]): boolean
  Returns a value of if the rule is to be included

在线托管程序时,请使用在线文档查看器来查阅细节。

Deno 的内置工具

这是对前端开发人员影响最大的领域。JavaScript 工具链现在的状况非常混乱。当你加入 TypeScript 工具链时,复杂度甚至会进一步增加。

JavaScript 的最大优势之一是它不需要编译,所以可以在浏览器中直接运行。这样你就可以立刻获得编码的反馈。入门门槛很低;你只需一个文本编辑器和一个浏览器就能编写软件了。

不幸的是,这种简单性和可访问性已被称为过度工具链的风气破坏了。这种风气已经将 JavaScript 的开发工作变成了一场噩梦。我甚至看过一整套关于配置 Webpack 的课程。这种乱象需要有个尽头——生命苦短啊。

工具链已经混乱到了这样的程度:许多开发人员迫切希望回到实际编写代码的流程,而不是浪费时间鼓捣那些配置文件,并为应该选择哪种标准而苦恼不已。针对这一问题的一个新兴项目是 Facebook 的 Rome。在撰写本文时,它还处于起步阶段。尽管它可能被证明是有价值的,但 Deno 有潜力成为更实质性的解决方案。

Deno 本身就是一个完整的生态系统,具有运行时和自己的模块 / 包管理系统。这样就有了更大的空间来内置所有工具。下面我们研究一下 1.0 版本中可用的工具,以及如何使用它来减少对第三方库的依赖并简化开发流程。

目前尚无法在 Deno 中替换整个前端构建管道,但这一天应该不久就会到来了。

测试

测试运行器(test runner)是使用 Deno.test() 函数内置到 Deno 核心中的。断言库是在标准库中提供的。它包括了你常用的所有断言,例如 assertEquals() 和 assertStrictEq(),以及一些不太常见的断言,如 assertThrowsAsync()。

在撰写本文时,Deno 尚无测试覆盖功能,并且需要使用第三方工具(如 Denon)来设置监视(watch)模式。

要查看所有的测试运行器选项,请使用 deno test --help 命令。尽管选项数量不多,但很多功能你可能之前在 Mocha 之类的程序中就很熟悉了。例如,–failfast 将在遇到第一个错误时停止,而 --filter 可用来过滤要运行的测试。

使用测试运行器

最基本的语法是 deno test。它将在工作目录中运行以 _test 或.test 结尾的所有文件,扩展名为.js、.ts、.jsx 或.tsx(例如 example_test.ts)

import { assertEquals } from "https://deno.land/std/testing/asserts.ts";

Deno.test({
  name: "testing example",
  fn(): void {
    assertEquals("world", "world");
    assertEquals({ hello: "world" }, { hello: "world" });
  },
});

如果你的代码使用 DOM,则需要使用 lib: [“dom”, “esnext”] 提供自己的 tsconfig.json。我们将在下面详细介绍。

格式化

dprint 提供格式化功能,dprint 是 Prettier 的高性能替代品,它复制了所有已有的 Prettier 2.0 规则。

要格式化一个或多个文件,请使用 deno fmt或 Visual Studio Code 扩展(稍后将详细介绍)。

编译和打包

Deno 可以使用 deno bundle 从命令行创建一个简单的打包,但是它也公开了一个内部编译器 API:

https://deno.land/std/manual.md#compiler-api

因此用户可以创建自己自定义的输出。这个 API 现在被标记为不稳定状态,因而你需要使用 --unstable 标志。

尽管 Deno 有一些与 Web 兼容的 API,但它们并不完整。如果要编译任何引用 DOM 的前端 TypeScript,则需要在编译或打包时告知 Deno 这些类型。你可以使用编译器 API 选项 lib。

index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <h1 id="greeter">Replace me</h1>
  </body>
</html>
test-dom.ts
let greeter: HTMLElement | null = document.getElementById("greeter")!; // Please forgive the Non-Null Assertion Operator

greeter.innerText = "Hello world!";
compile.ts
const [errors, emitted] = await Deno.compile("test-dom.ts", undefined, {
  lib: ["dom", "esnext"], // include "deno.ns" for deno namespace
  outDir: "dist",
});

if (errors) {
  console.log("There was an error:");
  console.error(errors);
} else {
  console.log(emitted); // normally we would write the file
}

下面是终端中生成的发射图输出。

{
 dist/test-dom.js.map: "{\"version\":3,\"file\":\"test-dom.js\",\"sourceRoot\":\"\",\"sources\":[\"file:///home/david/Downloads/deno-arti...",
 dist/test-dom.js: "\"use strict\";\nlet greeter = document.getElementById(\"greeter\");\ngreeter.innerText = \"Hello world!\";\n..."
}

在上面的示例中,我们编译了引用 DOM 的 test-dom.ts 文件。在 Deno.compile() 中使用 lib 选项会覆盖 Deno 使用的所有 lib 默认选项,因而你需要重新添加 esnext 和可选的 deno.ns,才能使用 Deno 命名空间。这一切仍然是试验性的,但是我希望 bundle 命令能够发展下去,以处理摇树之类的事情,并能更像 Rollup.js。

调试

Deno 具有内置的调试功能,但是在撰写本文时 Visual Studio Code 扩展还不支持它。要开始调试,请按以下步骤手动操作。

  • deno run -A --inspect- brk fileToDebug.ts(注意:对模块使用最低权限)
  • 在 Chrome 或 Chromium 中打开 chrome://inspect。你会看到下面这样的页面:
  • 单击“inspect”以连接并开始调试你的代码

文件监视

Deno 使用 Rust notify 库,通过 Deno.watchFs() API 内置了文件监视功能。Deno 喜欢通过其 API 在后台处理繁重的工作,并让用户以自己喜欢的方式实现他们的代码。这里没有 --watch 标志,而是需要创建自己的实现或使用第三方模块。

制作自己的文件监视器时唯一需要注意的是防抖(debouncing)。这个 API 可以连续快速触发多个事件,并且你可能不想多次执行操作。用户 Caesar2011 使用 Date.now(),仅用了 23 行 TypeScript 代码就解决了这个问题:

https://github.com/Caesar2011/rhinoder/blob/master/mod.ts

还有一个更高级的 Deno 文件监视解决方案,称为 Denon。它相当于 nodemon。如果你想监视工作区中的更改并重新运行测试,只需输入:

denon test

Visual Studio Code 插件

到目前为止,最好的扩展是可以从 Visual Studio MarketPlace 获取的 axetroy 插件。安装完成后,在你的项目文件夹中创建文件.vscode/settings.json,并在每个项目上都启用扩展。

// .vscode/settings.json
{
  "deno.enable": true,
}

现在,你就获得了全面的 IntelliSense 支持以及开始编程所需的一切。

总结

事实证明,JavaScript 生态系统的快速变化是有利有弊的。从积极的一面来看,今天我们有如此之多的高质量工具可用。消极的一面是,不断涌现的新框架和库让人产生了厌倦的感觉。

Deno 成功地克服了 JavaScript 开发工作中的许多缺陷。以下是其中一些改进。

  • 通过使用 Web 标准,Deno 的 API 未来可期。这给了开发人员信心,让他们知道自己不会浪费时间去学习很快就会过时的东西;
  • 在 JavaScript 上加入 TypeScript 消除了编译开销,并允许更紧密的集成;
  • 内置工具意味着用户无需浪费时间寻找第三方支持;
  • 去中心化的包管理机制将用户从 npm 中解放出来,同时相比过时的 CommonJS,ECMAScript 模块令人心旷神怡。

尽管 Deno 可能还无法完全替代 Node.js,但它已经成为了可以日常使用的绝佳编程环境。

英文原文

Deno 1.0 what you need to know

  • 发表于:
  • 本文为 InfoQ 中文站特供稿件
  • 首发地址https://www.infoq.cn/article/pGiJ18JyDysdop17fzWO
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券