二、工具链

既然我们已经熟悉了事件语言、Node 的历史以及导致 Deno 出现的原因,那么我们就可以开始编写一些代码了。

在本章中,我们要做的第一件事是设置环境和代码编辑器。 我们将继续编写我们的第一个 Deno 程序,并使用 REPL 试验运行时 api。 然后,我们将探索模块系统以及 Deno 缓存和模块解析如何与实际示例一起工作。 我们将了解版本控制,还将学习如何处理第三方依赖关系。 然后,我们将使用 CLI 来研究包及其文档,以及如何安装和重用 Deno 脚本。

在运行和安装几个脚本之后,我们将通过学习权限系统如何工作以及如何保护我们运行的代码来深入了解权限。

在我们学习工具链的过程中,我们不能忽略代码格式和检测,所以我们也将在本章中探索这些主题。 我们将通过编写和运行一些简单的测试来探索 Deno 的测试套件,最后我们将展示 Deno 如何将代码捆绑到一个自我可持续的二进制或单个 JavaScript 文件中。

在本章中,我们将涵盖以下主题:

  • 设置环境
  • 安装 VS 代码
  • 你好世界
  • 模块系统和第三方依赖关系
  • 运行和安装脚本
  • 使用 test 命令
  • 使用权限
  • 格式化和检测代码
  • 捆绑的代码
  • 编译成二进制文件
  • 使用 upgrade 命令

让我们开始吧!

技术要求

本章所有代码可在https://github.com/PacktPublishing/Deno-Web-Development/tree/master/Chapter02中找到。

设置环境

Deno 的原则之一是尽可能保持其单一可执行文件的完整性。 这个决定,特别是,极大地简化了安装步骤。 在本节中,我们将安装 VS Code 和推荐的插件,并学习如何在不同的系统上安装 Deno。

安装 Deno

在接下来的几页中,我们将学习如何安装 Deno。 为了确保本书中所写的一切都能顺利运行,我们将使用 1.7.5 版本。

根据您的操作系统,这是本书中很少出现的情况不同的部分之一。 安装完成后,如何安装 Deno 并没有什么区别。

让我们实践一下,在我们的机器上安装 Deno。 下面的要点告诉你如何在不同的操作系统上安装运行时:

  • Shell (Mac, Linux)
  • PowerShell (Windows)

然后,为了确保一切工作正常,让我们运行以下命令获取当前的 Deno 版本:

$ deno --version

我们应该得到以下输出:

$ deno --version 
deno 1.7.5 (release, x86_64-apple-darwin) 
v8 9.0.123 
typescript 4.1.4

现在我们已经安装了正确版本的 Deno,我们可以开始编写和执行程序了。 然而,为了让我们的体验更流畅,我们将安装并配置所选的编辑器。

安装 VS Code

VS Code 是我们将在本书中使用的编辑器。 这主要是因为它有一个官方的 Deno 插件。 还有其他编辑器提供了令人愉快的 JavaScript 和 TypeScript 体验,所以请随意使用它们。

这一系列的步骤并不是完美地跟随本书的其余部分所必需的,所以你可以随意跳过它们。 要安装它,请遵循以下步骤:

  1. 进入https://code.visualstudio.com/,点击下载按钮。
  2. 下载完成后,将其安装到您的系统上。
  3. 安装 VS Code 后,最后一步是安装 Deno 的 VS Code 插件。
  4. Plugins部分(VS Code 左侧第 5 个图标),搜索Deno,安装由 Denoland 编写的 Deno 插件,这是官方版本:

Figure 2.1 – Plugin icon on VS Code's left bar

图 2.1 - VS Code 左边栏的插件图标

这就是 Deno 的 VS Code 插件的样子:

Figure 2.2 – Deno extension on the VS Code Marketplace

图 2.2 - VS Code 市场上的 Deno 扩展

要在你的项目中启用 Deno 插件,你必须创建一个本地 VS Code 文件夹,工作区配置文件将放在其中。 为了做到这一点,我们将创建一个名为.vscode的文件夹,其中有一个名为settings.json的文件,并在该文件中写入以下内容:

{
 "deno.enable": true
}

这将使 VS Code 激活我们当前所在文件夹中的扩展。 当使用不稳定特性时,我们也可以启用deno.unstable设置,这在插件的文档中也提到过。

外壳完井

Deno 还为我们提供了一种方法来生成 shell 完成。 这样,当我们在终端上编写 Deno 命令时,就会得到自动完成建议。 我们可以通过运行以下命令来实现:

$ deno completions <shell>

shell 的取值为:zshbashfishpowershellelvish。 确保你选择了你正在使用的。 这个命令将把完成输出到 stdout。 然后您可以将其粘贴到您的 shell 配置文件(https://deno.land/manual@v1.7.5/getting_started/setup_your_environment#shell-autocomplete)。

有了这些,我们已经完成了如何安装它 Deno。 我们还安装并配置了运行时和编辑器。 现在,让我们用 Deno!

Hello World

所有的东西都在处,让我们写第一个程序!

首先,我们需要创建一个名为my-first-deno-program.js的文件,并写入一些我们熟悉的内容。 我们将使用consoleAPI 向控制台写入消息:

console.log('Hello from deno');

要执行此操作,让我们使用上一节中安装的 CLI。 我们执行程序必须使用的命令叫做run:

$ deno run my-first-deno-program.js
Hello from deno

提示

所有 Deno CLI 命令都可以使用--help标志执行,该标志将详细说明所有命令可能的行为。

在这一点上,我们还没有真正做什么,我们已经不知道要做什么了。 我们刚刚用我们熟悉的语言 JavaScript 编写了一个console.log文件。

有趣的是,我们学习了如何使用run命令执行程序。 我们将在本章后面更详细地探讨这一点。

REPL

Read Eval Print Loop,也称为REPL,是一种在解释性语言中常用的工具。 它允许用户运行代码行并立即获得输出。 Node.js、Ruby 和 Python 是大量使用它的一些语言的例子。 Deno 也不例外。

要打开它,你只需要运行以下命令:

$ deno

您现在可以花一些时间来研究这种语言(提示:有制表符完成功能)。 如果您对哪些 api 是可用的感到好奇,那么您可以在合适的地方试用它们。 稍后我们将深入探究这些,只是给你一些建议,你可以看一下Deno名称空间,网络 API-compatible 等功能fetch,或对象Mathwindow等,所有这些都在 Deno 上市的文档(https://doc.deno.land/builtin/stable)。

试一试!

Eval

另一种执行不存在于文件中的代码的方法是使用eval命令:

$ deno eval "console.log('Hello from eval')"
Hello from eval

eval命令对于运行简单的内联脚本非常有用。

到目前为止,我们编写的程序都相当简单。 我们只是通过几种不同的方式将值记录到控制台。 然而,当我们开始接近真实世界时,我们知道我们将编写更复杂的逻辑。 逻辑越复杂,错误就越多,因此需要调试代码。 这就是我们接下来要学习的内容。

Deno 调试代码

即使我们遵循最佳实践并尽最大努力编写简单、干净的代码,任何相关程序也很可能需要偶尔进行调试。

掌握快速运行和调试代码的能力是提高任何技术学习曲线的最佳方法之一。 这种技能可以通过反复试验和快速实验来轻松测试和理解事物的工作原理。

让我们学习如何调试代码。

第一步是创建第二个程序。 让我们添加几个变量,以便稍后检查。 这个程序的主要目标是返回当前时间。 我们将使用已知的Date对象来做这件事。 让我们把这个文件命名为get-current-time.js,像这样:

const now = new Date();
console.log(`${now.getHours()}:${now.getMinutes()}:  ${now.getSeconds()}`);

如果我们想在now变量输出到控制台之前调试它的值,该怎么办? 这就是调试发挥作用的地方。 让我们运行相同的程序,但带有--inspect-brk标志:

$ deno run --inspect-brk get-current-time.js
Debugger listening on ws://127.0.0.1:9229/ws/32e48d8a-5c9c-4300-8e09-ee700ab79648

我们现在可以打开谷歌 Chrome 在chrome://inspect/。 将列出在本地主机上运行的名为deno的远程目标。 点击inspect,Chrome DevTools 检查器窗口将打开,其执行将在第一行停止:

Figure 2.3 – Chrome stopping on the first line to be debugged

图 2.3 - Chrome 停止在第一行要调试

此时,我们可以添加断点、记录特定值、检查变量等等。 它启用了与在 Node 或浏览器中调试时相同的功能。

--inspect旗也可以用于此。 然而,为了方便起见,我们在这里使用了--inspect-brk。 两者都有类似的行为,但是inspect需要在代码中提供调试器。 当代码执行并解释调试器关键字时,它尝试连接到一个已经在运行的检查器实例。

既然我们已经了解了如何运行和调试代码,我们就可以开始编写自己的程序了。 还有很多东西需要学习,但我们已经适应了最低限度。

当我们开始编写我们的程序和我们的代码库增长时,很可能我们会开始将逻辑提取到不同的模块中。 当这些模块变得可重用时,我们可以将它们提取到包中,以便在项目之间共享。 这就是为什么我们需要理解 Deno 如何处理模块解析的原因,我们将在下一节中进行说明。

模块和第三方依赖

Deno 使用完全兼容浏览器的 ECMAScript 模块和导入。 模块的路径是绝对的,因此它包含了文件扩展名,这也是浏览器世界的标准

Deno 非常重视成为脚本的浏览器。 它与 web 浏览器的一个共同点是,它深刻地利用了 url。 它们是分享资源和在网络上工作的最灵活的方式之一。 为什么不在模块解析中使用它们呢? 这就是浏览器的作用。

模块的路径是绝对的,这使得我们可以不依赖第三方实体,如 npm,或复杂的模块解析策略。 使用绝对导入,我们可以直接从 GitHub 导入代码,从专有服务器,甚至从 gist 导入代码。 唯一的要求是它有一个 URL。

这个决定可以使用一个完全分散的模块分布,并使 Deno 内部的模块解析变得简单,并且与浏览器兼容。 这在 Node 上是不会发生的。

Deno 甚至利用 url 进行版本控制。 例如,要从标准库导入 0.83.0 版本的 HTTP 服务器,我们将使用以下代码:

import { serve } from 
'https://deno.land/std@0.83.0/http/server.ts'

这就是导入模块的简单方法。 这里,代码是从https://deno.land/加载的,但是模块可以从其他任何地方加载。 唯一的要求是有链接到它。

例如,如果您有自己的服务器,其中的文件可以通过 URL 获得,那么您可以在 Deno 中直接使用它们。 之前,我们了解到 Deno 会自动安装并缓存依赖项,所以让我们进一步了解一下它是如何工作的。

本地缓存依赖

我们已经知道,Deno 没有像node_modules这样的惯例。 对于来自 Node 的人来说,这可能听起来很奇怪。 这是否意味着你的代码总是从互联网上获取模块? 不。 你还能离线工作吗? 是的。

让我们实践一下。

创建一个名为hello-http-server.js的文件,并在其中添加以下代码:

import { serve } from
"https://deno.land/std@0.84.0/http/server.ts";
for await (const req of serve(":8080")) {
  req.respond({ body: "Hello deno" });
}

您可能已经猜到了,这个程序在端口8080上启动一个 HTTP 服务器,然后用Hello deno响应每个请求。

如果您仍然觉得奇怪,请不要担心——我们将在下一章更深入地了解标准库。

让我们运行这个程序,并在执行代码之前注意 Deno 做了什么:

$ deno run hello-http-server.js
Download https://deno.land/std@0.83.0/http/server.ts
Download https://deno.land/std@0.83.0/encoding/utf8.ts
Download https://deno.land/std@0.83.0/io/bufio.ts
Download https://deno.land/std@0.83.0/_util/assert.ts
Download https://deno.land/std@0.83.0/async/mod.ts
Download https://deno.land/std@0.83.0/http/_io.ts
Download https://deno.land/std@0.83.0/textproto/mod.ts
Download https://deno.land/std@0.83.0/http/http_status.ts
Download https://deno.land/std@0.83.0/async/deferred.ts
Download https://deno.land/std@0.83.0/async/delay.ts
Download https://deno.land/std@0.83.0/async/mux_async_iterator.ts
Download https://deno.land/std@0.83.0/async/pool.ts
Download https://deno.land/std@0.83.0/bytes/mod.ts
error: Uncaught PermissionDenied: network access to "0.0.0.0:8080", run again with the --allow-net flag

发生了什么事? 在它运行代码之前,Deno 查看代码的导入,下载任何依赖项,编译它们,并将它们存储在本地缓存中。 最后仍然有一个错误,但我们稍后会讲到。

为了理解 Deno 对下载的文件做了什么,我们将使用另一个命令info:

$ deno info
DENO_DIR location: "/Users/alexandre/Library/Caches/deno"
Remote modules cache: "/Users/alexandre/Library/Caches/deno/deps"
TypeScript compiler cache: "/Users/alexandre/Library/Caches/deno/gen"

这打印出了德诺的安装信息。 注意DENO_DIR,这是 Deno 存储本地缓存的路径。 如果我们在那里导航,我们可以访问.js文件和各自的源地图。

在第一次下载和缓存模块之后,Deno 将不会重新下载它们,并将继续使用本地缓存,直到明确要求它不要这样做。

不运行代码进行缓存

为了确保你有代码依赖项的本地副本,而不必运行它,你可以使用以下命令:

$ deno cache hello-http-server.js

这将做完全相同的事情,Deno 做之前运行您的代码; 唯一的区别是它不能运行。 因此,我们可以在deno cache命令和npm install命令之间建立一些并行性。

重新加载缓存

cacherun命令可以使用--reload标志强制下载依赖项。 以逗号分隔的必须重新加载的模块列表可以作为参数发送到--reload标志:

$ deno cache hello-http-server.js --reload=https://deno.land/std@0.83.0/http/server.ts
Download https://deno.land/std@0.83.0/http/server.ts

在上面的例子中,只有来自https://deno.land/std@0.83.0/http/server.ts的模块会被重新下载,我们可以通过查看命令的输出来确认。

最后运行服务器

现在已经下载了依赖项,我们仍然有一些东西阻止我们运行服务器,一个PermissionDenied错误:

error: Uncaught PermissionDenied: network access to "0.0.0.0:8080", run again with the --allow-net flag

现在,让我们按照建议添加--allow-net标志,这将授予我们的程序完全的网络访问权限。 我们将在本章的后面讨论权限:

$ deno run --allow-net hello-http-server.js

提示(Windows)

请记住,如果您使用的是 Windows,您可能会得到 Windows 的本机网络授权弹出,通知您一个程序(Deno)正在试图访问网络。 如果你想让这个 web 服务器能够运行,你应该点击允许访问

现在,我们的服务器应该正在运行。 如果我们用curl连接8080端口,它会显示Hello``Deno:

$ curl localhost:8080
Hello deno

这结束了我们的最简单的 web 服务器; 我们将用几页的时间来讨论这个问题。

管理依赖

如果您使用过其他工具,或者甚至 Node.js 本身,您可能会觉得在代码周围使用 url 不是很直观。 我们还可以认为,在代码中直接编写 URL 可能会导致问题,比如同一个依赖项有两个不同版本,或者 URL 中有拼写错误。

Deno 解决了这个问题,它摒弃了复杂的模块解析策略,转而使用普通的 JavaScript 和绝对导入。

跟踪依赖关系的建议是使用一个文件,导出所有需要的依赖关系,并将它们放在一个包含 url 的文件中。 让我们看看它是怎么运作的。

创建一个名为deps.js的文件,并在那里添加我们的依赖项,导出我们需要的:

export { serve } from 
"https://deno.land/std@0.83.0/http/server.ts";

通过使用前面的语法,我们从标准库的 HTTP 服务器导入serve方法并导出它。

回到我们的hello-http-server.js文件,我们现在可以改变导入,这样我们就可以从deps.js文件中使用导出函数:

import { serve } from "./deps.js";
for await (const req of serve(":8080")) {
  req.respond({ body: "Hello deno" });
}

现在,每次我们添加一个依赖项时,我们都可以运行deno cache deps.js来保证我们有一个模块的本地副本。

这是 Deno 的管理依赖关系的方法。 它就是这么简单——没有魔法,没有复杂的标准,只是一个导入和导出符号的文件。

完整性检查

现在您已经知道如何导入和管理第三方依赖项,您可能会觉得仍然缺少一些东西。

什么能保证下一次我们,同事,甚至 CI 尝试安装项目时,我们的依赖关系没有改变?

这是一个合理的问题,因为它是一个 URL,所以可能会发生这种情况。

我们可以使用完整性检查来解决这个问题。

生成锁文件

Deno 有一个特性,可以通过使用 JSON 文件存储和检查子资源的完整性,类似于其他技术使用的锁文件方法。

要创建我们的第一个锁文件,让我们运行以下命令:

$ deno cache --lock=lock.json --lock-write deps.js 

使用--lock标志,我们选择文件的名称,通过使用--lock-write,我们给 Deno 权限来创建或更新相同的文件。

查看生成的lock.json文件,这是我们会发现的:

{
    "https://deno.land/std@0.83.0/_util/assert.ts":    "e1f76e77c5ccb5a8e0dbbbe6cce3a56d2556c8cb5a9a8802fc9565 af72462149",
    "https://deno.land/std@0.83.0/async/deferred.ts":    "ac95025f46580cf5197928ba90995d87f26e202c19ad961bc4e317 7310894cdc",
    "https://deno.land/std@0.83.0/async/delay.ts":    "35957d585a6e3dd87706858fb1d6b551cb278271b03f52c5a2cb70 e65e00c26a",

它生成一个 JSON 对象,其中键是依赖关系的路径,值是 Deno 用来保证资源完整性的散列。

然后应该将该文件签入到版本控制系统中。

在下一节中,我们将学习如何安装依赖项,并确保每个人都在运行相同版本的代码。

使用锁文件安装依赖项

一旦创建了锁文件,任何想下载代码的人都可以运行带有--lock标志的缓存命令。 这可以在下载依赖项时进行完整性检查:

$ deno cache --reload --lock=lock.json deps.js

也可以在run命令中使用--lock标志来启用运行时验证:

$ deno run --lock=lock.json --allow-net hello-http-server.js

重要提示

当用run命令使用锁标志时,包含还没有被缓存的依赖项的代码将不会被锁文件检查。

为了确保新的依赖项在运行时被检查,我们可以使用--cached-only标志。

这样,如果我们的代码使用了不在lock.json文件中的任何依赖项,Deno 将抛出一个错误。

这就是我们需要做的,以确保我们运行的是我们想要的依赖项的确切版本,消除版本更改可能带来的任何问题。

导入地图

Deno 支持导入地图(https://github.com/WICG/import-maps)。

如果你不熟悉它们是什么,我将简要地为你解释:它们用于控制 JavaScript 导入。 如果你以前在 webpack 之类的代码包中使用过 JavaScript,那么这就是一个类似于你所知道的“别名”的特性。

重要提示

这个特性目前还不稳定,所以必须使用--unstable标志启用它。

让我们创建一个 JSON 文件。 名称在这里并不重要,但为了简单起见,我们将其命名为import-maps.json

在这个文件中,我们将用imports键创建一个 JavaScript 对象。 在这个对象中,任何键都是模块名,任何值都是真正的导入路径。 我们的第一个导入映射将把http单词映射到标准库 HTTP 模块的根:

{
  "imports": {
    "http/": "https://deno.land/std@0.83.0/http/"
  }
}

通过这样做,我们现在可以将标准库的 HTTP 模块导入到我们的deps.js文件中,如下所示:

export { serve } from "http/server.ts"; 

为了运行它,我们将使用--import-map标志。 通过这样做,我们可以选择导入映射所在的文件。 然后,因为这个特性仍然不稳定,我们必须使用--unstable标志:

$ deno run --allow-net --import-map=import-maps.json --unstable hello-http-server.js

正如我们所看到的,我们的代码运行得很完美。

这是一种不依赖于任何外部工具的定制模块解析的简单方法。 它也被建议作为一些东西,被添加到浏览器。 希望在不久的将来,这将被接受。

巡检模块

我们只是使用标准库的 HTTP 模块来创建服务器。 如果您还不太熟悉标准库,请不要担心; 我们将在下一章详细解释。 目前,我们所需要知道的是,我们可以在其网站(https://deno.land/std)上探索其模块。

让我们看看在前面的脚本中使用的模块,即 HTTP 模块,并使用 Deno 来查找关于它的更多信息。

我们可以使用info命令来做到这一点:

$ deno info https://deno.land/std@0.83.0/http/server.ts
local:/Users/alexandre/Library/Caches/deno/deps/https/deno.land/2d926cfeece184c4e5686c4a94b44c9d9a3ee01c98bdb4b5e546dea4 e0b25e49
type: TypeScript
compiled: /Users/alexandre/Library/Caches/deno/gen/https/deno.land/2d926cfeece184c4e5686c4a94b44c9d9a3ee01c98bdb4b5e546dea4 e0b25e49.js
deps: 12 unique (total 63.31KB)
https://deno.land/std@0.83.0/http/server.ts (10.23KB)
├── https://deno.land/std@0.83.0/_util/assert.ts *
├─┬ https://deno.land/std@0.83.0/async/mod.ts (202B)
 ├── https://deno.land/std@0.83.0/async/deferred.ts *
 ├── https://deno.land/std@0.83.0/async/delay.ts (279B)
 ├─┬ 

    └── https://deno.land/std@0.83.0/encoding/utf8.ts *
└─┬ https://deno.land/std@0.83.0/io/bufio.ts (21.15KB)
    https://deno.land/std@0.83.0/_util/assert.ts (405B)
    https://deno.land/std@0.83.0/bytes/mod.ts (4.34KB)

这个命令列出了大量关于 HTTP 模块的信息。 让我们分解一下。

在第一行,我们获得了脚本缓存版本的路径。 在那之后的一行中,我们看到了文件的类型。 我们已经知道标准库是用 TypeScript 编写的,所以这一点也不奇怪。 下一行也是一个路径,这次是模块的编译版本,因为 TypeScript 模块是在下载步骤中编译成 JavaScript 的。

该命令输出的最后一部分是依赖项树。 通过查看它,我们可以迅速识别出它只是链接到标准库中的其他模块。

提示

我们可以使用--unstable--json标志以及deno``info来获得可编程访问的 JSON 输出。

在使用第三方模块时,我们不仅需要知道它们依赖于什么,还需要知道模块提供了哪些函数和对象。 我们将在下一节中学习这一点。

探索文档

文档是任何软件项目的关键方面。 Deno 在保持所有 api 文档化方面做得很好,而 TypeScript 在这方面帮助很大。 由于标准库和运行时函数都是用 TypeScript 编写的,大部分文档都是自动生成的。

该文档可在https://doc.deno.land/上获取。

如果您不能上网,并且想要访问本地安装的模块的文档,那么 Deno 可以为您提供帮助。

许多编辑器,即 VS Code,允许你这样做,以著名的Cmd/Ctrl+ click 为例。 但是,Deno 并不依赖于编辑器特性,因为doc命令提供了您需要的所有基本特性。

让我们看看标准库的 HTTP 模块的文档:

$ deno doc https://deno.land/std@0.83.0/http/server.ts
function _parseAddrFromStr(addr: string): HTTPOptions
    Parse addr from string
async function listenAndServe(addr: string | HTTPOptions, handler: (req: ServerRequest) => void): Promise<void>
    Start an HTTP server with given options and request handler
async function listenAndServeTLS(options: HTTPSOptions, handler: (req: ServerRequest) => void): Promise<void>
    Start an HTTPS server with given options and request 
      handler
function serve(addr: string | HTTPOptions): Server
    Create a HTTP server
...

我们现在可以看到公开的方法和类型。

在我们以前的一个程序中,我们使用了serve方法。 要了解关于这个特定方法的更多信息,我们可以将方法(或任何其他符号)的名称作为第二个参数发送:

$ deno doc https://deno.land/std@0.83.0/http/server.ts serve
Defined in https://deno.land/std@0.83.0/http/server.ts:282:0
function serve(addr: string | HTTPOptions): Server
    Create a HTTP server
        import { serve } from         "https://deno.land/std/http/server.ts";
        const body = "Hello World\n";
        const server = serve({ port: 8000 });
        for await (const req of server) {
          req.respond({ body }); add 
        }

这是一个非常有用的特性,它使开发人员无需依赖编辑器就可以浏览本地安装的模块的文档。

正如我们将在下一章学到的,并且您可能通过使用 REPL 注意到的,Deno 有一个内置的 API。 要查看它的文档,我们可以运行以下命令:

$ deno doc --builtin

输出将是大量的,因为它列出了所有的公共方法和类型。

在*nix 系统中,这可以很容易地管道到应用,如less:

$ deno doc --builtin | less

与远程模块类似,也可以通过方法名进行过滤。 以 Deno 命名空间中的writeFile函数为例:

$ deno doc --builtin Deno.writeFile
Defined in lib.deno.d.ts:1558:2
function writeFile(path: string | URL, data: Uint8Array, options?: WriteFileOptions): Promise<void>
  Write `data` to the given `path`, by default creating a new file if needed,
  else overwriting.
  ```ts
  const encoder = new TextEncoder();
  const data = encoder.encode("Hello world\n");
  await Deno.writeFile("hello1.txt", data);  // overwrite "hello1.txt" or create it
  await Deno.writeFile("hello2.txt", data, {create: false});  // only works if "hello2.txt" exists
  await Deno.writeFile("hello3.txt", data, {mode: 0o777});  // set permissions on new file
  await Deno.writeFile("hello4.txt", data, {append: true});  // add data to the end of the file
  ```
 Requires `allow-write` permission, and `allow-read` if `options.create` is `false`.

doc命令是开发工作流中很有用的一部分。 然而,如果你有互联网接入,并想以一种更容易理解、更直观的方式访问它,https://doc.deno.land/是你应该去的地方。

您可以使用文档网站来了解更多关于内置 api 或标准库模块的信息。 此外,它还允许显示任何可用模块的文档。 为此,我们只需要用一个反斜杠/替换模块 URL 的://部分,并在 URL 前面加上https://doc.deno.land/

例如,要访问 HTTP 模块的文档,URL 为https://doc.deno.land/https/deno.land/std@0.83.0/ HTTP /server.ts

如果导航到该 URL,将显示一个干净的界面,其中包含模块的文档。

我们现在知道如何使用和探索第三方模块。 然而,当我们开始编写应用时,可能会有一些我们希望在项目之间共享的实用程序。 我们可能还希望在系统中的任何地方都可以使用特定的包。 下一节将帮助我们做到这一点。

运行和安装脚本

在他的第一次演讲中,和在 Deno 的第一版发布笔记中(https://deno.land/posts/v1#a-web-browser-for-command-line-scripts),达尔用了一个我很喜欢的句子:

“Deno 就像一个命令行脚本的网络浏览器。”

每次我用 Deno,这句话对我来说越来越有意义。 我相信随着这本书的进行,你也会开始明白这一点的。 让我们进一步探讨一下。

在浏览器中,当您访问 URL 时,它会运行那里的代码。 它解释 HTML 和 CSS,然后执行一些 JavaScript。

Deno 遵循其作为脚本浏览器的前提,只需要一个 URL 来运行代码。 让我们看看它是怎么运作的。

老实说,这和我们已经做过的几次没有太大不同。 回顾一下,上次我们执行简单的 web 服务器时,我们做了以下操作:

$ deno run --allow-net --import-map=import-maps.json --unstable hello-http-server.js

在这里,hello-http-server.js只是当前文件夹中的一个文件。

让我们尝试做同样的事情,但使用远程文件——通过 HTTP 提供的文件。

我们将从 Deno 标准库的一组示例中执行一个“echo 服务器”。 你可以在这里查看代码(https://deno.land/std@0.83.0/examples/echo_server.ts)。 它是一个服务器,对发送到它的任何东西进行回传:

$ deno run --allow-net https://deno.land/std@0.83.0/examples/ echo_server.ts
Download https://deno.land/std@0.83.0/examples/echo_server.ts
Check https://deno.land/std@0.83.0/examples/echo_server.ts
Listening on 0.0.0.0:8080

重要提示

如果您使用的是 Windows 机器,可能无法访问0.0.0.0:8080; 你应该访问localhost:8080。 它们都指向本地机器中的同一事物。 然而,当0.0.0.0出现在书的其余部分时,如果你运行的是 Windows,你应该尝试访问localhost

每次时间文件没有被缓存时,Deno 都会下载并执行它们。

它和一个浏览器有那么大的区别吗? 我可不这么认为。 我们给它一个 URL,它就会运行代码。

为了确保它能正常工作,我们可以建立一个 Telnet(https://en.wikipedia.org/wiki/Telnet)连接,并发送一个消息,让服务器回显:

$ telnet 0.0.0.0 8080
Trying 0.0.0.0...
Connected to 0.0.0.0.
Escape character is '^]'.
hello buddy
hello buddy

您可以使用任何可用的 Telnet 客户端进行此操作; 在这里,我们使用的是一个 macOS 客户端,我们通过 Homebrew 安装(https://brew.sh/)。 第一个“你好,伙计”是我们发送的消息,而后者是被回传的消息。 这样,我们就可以验证 echo 服务器是否正常工作。

重要提示

如果您正在使用任何其他 telnet 客户端,请确保启用了“本地行编辑”设置。 有些客户端在默认情况下没有启用此功能,并在您键入字符时发送字符,导致消息带有重复字符。 下图向您展示了如何在 PuTTY for Windows 中配置该设置。

Figure 2.4 – PuTTY local line editing setting

图 2.4 - PuTTY 本地行编辑设置

这证实了我们之前所说的,因为 Deno 使用与解析模块相同的方法来运行代码:它以类似的方式处理本地和远程代码。

安装实用工具 ts

有些实用程序我们只写一次,有些程序我们使用多次。 有时,为了便于重用它们,我们只是将这些脚本从一个项目复制到另一个项目。 对于其他用户,我们将它们保存在 GitHub 存储库中,并不断去那里获取它们。 我们使用最多的可能需要包装在一个 shell 脚本中,添加它/usr/local/bin(在*nix 系统上),并使其在我们的系统中可用。

为此,Deno 提供了install命令。

这个命令将一个程序包装到一个瘦 shell 脚本中,并将其放入安装 bin 目录中。 该脚本的权限是在安装时设置的,不会再次要求:

$ deno install --allow-net --allow-read https://deno.land/std@0.83.0/http/file_server.ts

这里,我们使用了标准库中的另一个模块file_server。 它创建一个服务于当前目录的 HTTP 服务器。 您可以通过访问导入 URL(https://deno.land/std@0.83.0/http/file_server.ts)来查看其代码。

安装命令将使file_server脚本在系统上可用。

要给它起一个除了file_server以外的名字,我们可以使用-n标志,像这样:

$ deno install --allow-net --allow-read -n serve https://deno.land/std@0.83.0/http/file_server.ts 

现在,让我们提供当前目录:

$ serve
HTTP server listening on http://0.0.0.0:4507/

如果我们访问http://localhost:4507,我们将得到:

Figure 2.5 – Deno file server web page

图 2.5 - Deno 文件服务器网页

这适用于远程 url,但也适用于本地 url。 如果您有一个用 Deno 编写的程序,想要将其转换为可执行文件,您也可以使用install命令。

我们可以通过简单的 web 服务器来实现,例如:

$ deno install --allow-net --unstable hello-http-server.js

通过运行上述代码,将创建一个名为hello-http-server的脚本,并在我们的系统中使用。

这就是执行本地和远程脚本所需要的全部内容。 Deno 让这变得非常简单,因为它以非常直接的方式处理导入和模块,非常像浏览器。

以前,我们使用权限来允许脚本访问网络或文件系统等资源。 在本节中,我们使用了install命令的权限,但之前我们也使用了run命令。

到目前为止,您可能已经了解它们是如何工作的,但是我们将在下一节中更详细地研究它们。

权限

在几页前,当我们编写第一个 HTTP 服务器时,我们第一次遇到了 Deno 的权限。 当时,我们必须给脚本访问网络的权限。 从那时起,我们在不知道它们如何工作的情况下使用了几次。

在本节中,我们将探讨权限是如何工作的。 我们将了解存在哪些权限以及如何配置它们。

如果我们运行deno run --help,我们会得到run命令的帮助输出,其中列出了某些权限。 为了让您更轻松地完成此任务,我们将列出所有现有权限,并对每个权限进行简要说明。

————“

这将禁用所有权限检查。 运行带有该标志的代码意味着它可以访问用户拥有的所有内容,这与默认情况下 Node.js 的情况非常相似。

使用此方法运行代码时要小心,当代码不是您的时尤其要小心。

——allow-env

这给环境带来了好处。 它被用来使程序可以访问环境变量。

——allow-hrtime

这样就可以获得高分辨率的时间管理。 它可以用于精确的基准测试。 将此权限授予错误的脚本可能会导致指纹识别和定时攻击。

——allow-net=

这授予对网络的访问权。 当不带参数使用时,it 允许所有网络访问。 当与参数一起使用时,它允许我们传递一个逗号分隔的域列表,其中允许网络通信。

——允许-插件

这允许加载插件。 注意这仍然是一个不稳定的特性。

——allow-read=

授予对文件系统的读访问权。 当不带参数使用时,它授予用户可以访问的所有内容。 使用参数,这只允许访问由逗号分隔的列表提供的文件夹。

——allow-run

这授予了对运行子进程的访问权(例如,使用Deno.run)。 请记住,子流程不是沙箱化的,应该谨慎使用。

——allow-write=

这授予文件系统写访问权。 当使用不带参数的时,它授予用户可以访问的所有内容的访问权。 使用参数时,它只允许访问由逗号分隔的列表提供的文件夹。

每当一个程序运行时,它没有正确的权限,一个PermissionError将被抛出。

权限用于runinstall命令。 它们之间的唯一区别是您赋予权限的那一刻。 对于run,您必须在运行时提供它们,而对于install,您在安装脚本时提供它们。

还有一种方法可以让 Deno 程序拥有权限。 它不需要事先给予许可,而是在需要时提出请求。 我们将在下一章中探索这个特性,在那里我们将学习 Deno 的名称空间。

这是它! 在权限方面真的没有什么可添加的,除了它们是 Deno 的一个非常重要的特性,因为它默认地将我们的代码沙箱化,并让我们决定我们的代码应该访问什么。 在本书中,我们将继续对将要编写的应用使用权限。

到目前为止,我们已经学习了如何运行,安装和缓存模块,以及如何使用权限。 当我们编写和运行更复杂的程序时,开始需要对它们进行测试。 我们可以使用test命令来实现这一点,我们将在下一节中学习。

使用 test 命令

作为主二进制文件的一部分,Deno 还提供了一个测试运行器。 毫不奇怪,它的命令叫做test。 在本节中,我们将研究它并运行几个测试。

在本节中,我们将主要研究命令本身,而不是测试语法。 我们将在本书后面的专门章节中更深入地研究它的语法和最佳实践。

test命令基于{*_,*.,}test.{js,mjs,ts,jsx,tsx}glob 表达式查找要运行的文件。

由于 glob 表达式可能不太容易理解,我们将简要地解释它们。

它匹配任何文件js,mjs,ts,jsx,tsx``test的扩展和他们的名字之前下划线(_)或一个点(.)

下面是一些与表达式匹配并被视为测试的文件示例:

  • example.test.ts
  • example_test.js
  • example.test.jsx
  • example_test.mjs

Deno 测试也在沙箱环境中运行,因此它们需要权限。 看一看前面的部分,了解更多关于如何做到这一点的信息。

当运行测试时,也可以使用我们在本章前面学到的调试命令。

过滤测试

当您有一个完整的测试套件时,一个常见的需求是只运行其中的一个特定部分。 为此,test命令提供了--filter标志。

假设我们有以下文件,其中定义了两个测试:

Deno.test("first test", () => {});
Deno.test("second test", () => {});

如果我们只想运行其中一个,我们可以使用--filter标志,并传递一个匹配测试名称的字符串或模式:

$ deno test --filter second
running 1 tests
test second test ... ok (3ms)
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out (3ms)

前面的代码只是运行了与过滤器匹配的测试。 当我们为代码库的一小部分开发测试时,这个特性变得非常有用,我们想要得到关于过程的快速反馈。

快速失败

在持续集成服务器这样的环境中,如果有多少测试失败并不重要,我们可能希望快速失败,如果有任何测试失败,我们只希望测试阶段结束。

为此,我们可以使用--fail-fast标志。

这就是我们现在需要知道的关于检测的一切。 正如我们之前提到的,我们将在第 8 章中回到测试,测试-单元和集成。 我们只是想熟悉这里的 CLI 命令。

我们认为测试是保证代码正常工作的工具,也是记录代码行为的一种方法。 测试是任何正在运行和演进的代码库的基础,Deno 通过在二进制代码中包含一个测试运行器,使成为一流的公民。 然而,测试只是一个更大的工具集的一部分——它也涵盖了开发人员的需求,比如检测和格式化。

在下一节中,我们将学习 Deno 如何解决这些问题。

格式和检测

检查和格式化是两个能力,被认为是维护一致性的关键,是在任何代码库中实施良好实践的关键。 考虑到这一点,Deno 在其 CLI 中嵌入了支持这两种功能的工具。 我们将在这一部分了解它们。

格式

为了格式化 Deno 的代码,CLI 提供了fmt命令。 这是一个固执己见的格式化器,旨在解决关于代码格式化的任何问题。 其主要目标是让开发人员不必关心代码的格式——无论是在编写代码时,还是在审查拉请求时。

运行以下不带参数的命令将格式化当前目录下的所有文件:

$ deno fmt
/Users/alexandre/Deno-Web-Development/Chapter02/my-first-deno-program.js
/Users/alexandre/Deno-Web-Development/Chapter02/bundle.js

如果我们想格式化单个文件,可以将其作为参数发送。

为了检查文件的格式错误,我们可以将它与--check标志一起使用,它将把在文件中发现的错误输出到标准输出。

忽略行和文件

要使格式化程序忽略一行或一个完整的文件,可以使用ignore注释:

// deno-fmt-ignore
const book = 'Deno 1.x – Web Development'; 

使用deno-fmt-ignore会忽略注释后面的行:

// deno-fmt-ignore-file
const book = 'Deno 1.x – Web Development';
const editor = 'PacktPub'

使用deno-fmt-ignore-file忽略整个文件。

线头

仍然在不稳定标志下,lint命令将代码中发现的警告和错误打印到标准输出。

让我们通过对一个名为to-lint.js的脚本运行 linter 来看看它的实际作用。 你可以用它来对抗任何你想要的东西。 这里,我们只是一个文件,它将抛出一个错误,因为它包含一个debugger:

$ deno lint --unstable to-lint.js
(no-debugger) `debugger` statement is not allowed
  debugger;
    ~~~~~~~~~
    at /Users/alexandre/dev/personal/Deno-Web-Development/Chapter02/to-lint.js:4:2
Found 1 problems

在这一节中,我们学习了如何使用fmtlint命令在代码库中加强代码一致性和最佳实践。

这是 Deno CLI 提供的两个命令,将在我们编写 Deno 程序时在日常生活中使用。 他们都非常固执己见,所以没有空间去支持不同的标准。 这一点也不奇怪,因为 Deno 深受golang的启发,而且这种方法与gofmt等工具所能实现的功能是一致的。

通过这些,我们知道了如何格式化和检查代码的最佳实践。 将这一点添加到我们在前几节中学到的内容中,没有什么能阻止我们在生产环境中运行代码。

当我们投入生产时,我们显然希望我们的服务器尽可能快。 在前一章中,我们了解到 Deno 最慢的部分之一是 TypeScript 解析。 当我们编写 TypeScript 代码时,我们不想在每次服务器的新实例启动时都花费解析它的时间。 同时,当我们编写干净的、独立的模块时,我们不想将它们单独地发布到生产环境中。

这就是为什么 Deno 提供了一个特性,允许我们将代码捆绑到单个文件中。 我们将在下一节中学习这一点。

捆绑代码

在上一章中,当我们介绍 Deno 时,我们选择了捆绑代码作为一个令人兴奋的特性,原因有很多。 这一特性具有巨大的潜力,我们将在第 7 章、HTTPS、提取配置和浏览器中的 Deno 中详细探讨这一特性。 但是由于我们在这里研究的是 CLI,所以我们将了解适当的命令。

它被称为bundle,它将代码打包成一个单独的、自包含的 ES 模块。

不依赖于 Deno 命名空间的绑定代码也可以在浏览器中使用<script type="module">和 Node.js 运行。

让我们使用它来构建我们的get-current-time.js脚本:

$ deno bundle get-current-time.js bundle.js
Bundle file:///Users/alexandre/dev/deno-web-development/Chapter02/2-hello-world/get-current-time.js
Emit "bundle.js" (2.33 KB)

现在,我们可以运行生成的bundle.js:

$ deno run bundle.js
0:11:4

这将打印当前时间。

我们也可以用 Node.js 来执行它,因为它是兼容 ES6 的 JavaScript(你需要安装 Node.js 才能运行以下命令):

$ node bundle.js
0:11:4

要在浏览器中使用相同的代码,我们可以创建一个名为index-bundle.html的文件,并导入生成的 bundle:

<!DOCTYPE html>
<html>
  <head>
    <title>Deno bundle</title>
  </head>
  <body>
    <script type="module" src="bundle.js"></script>
  </body>
</html>

有了上节学到的知识,我们可以在当前文件夹中运行标准库的文件服务器:

$ deno run --allow-net --allow-read https://deno.land/std@0.83.0/http/file_server.ts
HTTP server listening on http://0.0.0.0:4507/ 

现在,如果您导航到http://localhost:4507/index-bundle.html,并打开浏览器控制台,您将看到当前时间已经打印出来。

捆绑是一个非常有前途的特性,我们将在后面的第 7 章HTTPS、提取配置和浏览器中的 Deno 中探索。 它允许我们在应用中创建一个 JavaScript 文件。

我们会回到这一点,并在这本书的后面向你展示它能实现什么。 正如我们在本章中所看到的,捆绑是发布 Deno 应用的一种很好的方式。 但是,如果您希望分发应用,以便它可以在计算机以外的地方运行,该怎么办呢? bundle命令为我们做这些吗?

好吧,其实不是。 如果执行代码的地方安装了 Node、Deno 或浏览器,它就会这样做。

但如果不是呢? 这就是我们接下来要学习的内容。

编译成二进制

当 Deno 最初启动时,达尔声明,它的目标之一是能够将 Deno 代码作为单一的二进制,类似于 golang 所做的,从第一天起。 这与 nexe(https://github.com/nexe/nexe)或 pkg(https://github.com/vercel/pkg)对 Node 所做的工作非常相似。

这与生成 JavaScript 文件的 bundle 特性不同。 当您将 Deno 代码编译为二进制文件时,所有的运行时和代码都包含在该二进制文件中,使其能够自我维持。 一旦你编译了它,你就可以把这个二进制文件发送到任何地方,你就可以执行它了。

重要提示

在撰写本文时,正如在 https://deno.land/posts/v1.7#improvements-to-codedeno-compilecode 的发布说明中提到的,这仍然是一个不稳定的特性,有许多限制。

这个过程非常简单。 我们来看看怎么做。

我们只需要使用compile命令。 对于本例,我们将使用上一节中使用的脚本; 即,get-current-time.js:

$ deno compile --unstable get-current-time.js
Bundle file:///Users/alexandre/dev/Deno-Web-Development/Chapter02/get-current-time.js
Compile file:///Users/alexandre/dev/Deno-Web-Development/Chapter02/get-current-time.js
Emit get-current-time

这将发出一个名为get-current-time的二进制文件,我们现在可以执行它:

$ ./get-current-time
16:10:8

这是工作! 这个特性允许我们轻松地分发应用。 这是可能的,因为它包含代码及其所有依赖项,包括 Deno 运行时,使其具有自我可持续性。

随着 Deno 的不断发展,新的特性、bug 修复和改进将会被添加进来。 目前 Deno 每个季度只发布几个版本,所以你可能会想要升级我们的 Deno 版本。 CLI 也为此提供了一个命令。 我们将在下一节中学习这一点。

使用 upgrade 命令

我们从学习如何安装 Deno 开始这一章,并安装了运行时的单个版本。 但 Deno 一直在修复和改进 bug——在这些早期版本中更是如此。

当有新的更新时,我们可以使用安装 Deno 时使用的包管理器来升级它。 但是,Deno CLI 提供了一个可以用来升级自己的命令。 该命令称为upgrade,可以与--version标志一起使用,以选择我们要升级到的版本:

$ deno upgrade --version=1.7.4

如果没有提供版本,则默认为最新版本。 要在另一个位置安装新版本,而不是替换当前安装,你可以使用--output标志,如下所示:

$ deno upgrade --output $HOME/my_deno

upgrade是另一个实用程序,它遵循了 Deno 的哲学,提供了我们编写和维护应用所需的一切,而这个周期的一部分肯定是更新我们的运行时。

小结

在本章中,我们主要关注的是了解 Deno 提供的工具,包括它的主二进制文件中的工具。 这些工具将在我们的日常生活中大量使用,并贯穿本书的其余部分。

我们首先准备好环境和编辑器,然后深入到工具链中。

然后,我们编写并执行了一个Hello World应用。 使用REPLeval命令可以在没有文件的情况下进行试验和运行代码。 在那之后,我们看看模块系统。 我们不仅导入和使用了模块,还深入了解了 Deno 如何在本地下载和缓存依赖项。

在熟悉了模块系统之后,我们学习了如何管理外部依赖关系,即锁文件和完整性检查。 在这一节中,我们不能不谈谈一个仍然不稳定但很有前途的特性:导入地图。

之后,我们在info命令的帮助下,探索了一些第三方模块及其代码和依赖关系。 文档并不是由 Deno 留下的,我们也学习了如何使用documentation命令和各自的网站来查看第三方的代码文档。

由于脚本是 Deno 的头等大事,我们研究了一些命令,这些命令使我们能够通过直接从 URL 运行代码并全局安装实用程序脚本来重用代码。

在本书中,我们已经提到权限是 Deno 的亮点之一。 在本章中,我们学习了如何在运行代码时使用权限来调整其权限。

接下来,我们学习了测试运行器,以及如何运行和筛选测试。 我们了解到的另一个特性是如何根据 Deno 的标准格式化和检查代码。 我们了解了fmtlint命令,这两个自以为是的工具确保开发人员不必担心格式化和检查,因为它们是自动处理的。

最后,我们介绍了bundlecompile命令。 我们学习了如何将我们的代码捆绑到一个 JavaScript 文件中,以及如何生成一个包含我们的代码和 Deno 运行时的二进制文件,使其具有可持续性。

这一章涉及了许多有趣的内容。 我向你保证,接下来的事情会更令人兴奋。 在下一章中,我们将了解标准库,并在学习 Deno api 的同时用它编写简单的应用。

兴奋? 我们走吧!