一、Deno 简介
Deno 是一个 JavaScript/TypeScript 的运行时,默认使用安全环境执行代码,有着卓越的开发体验。Deno 含有以下功能亮点:
-
默认安全。外部代码没有文件系统、网络、环境的访问权限,除非显式开启。
-
支持开箱即用的 TypeScript 的环境。
-
只分发一个独立的可执行文件(deno)。
-
有着内建的工具箱,比如一个依赖信息查看器(deno info)和一个代码格式化工具(deno fmt)。
-
有一组经过审计的 标准模块,保证能在 Deno 上工作。
-
脚本代码能被打包为一个单独的 JavaScript 文件。
Deno 是一个跨平台的运行时,即基于 Google V8 引擎的运行时环境,该运行时环境是使用 Rust 语言开发的,并使用 Tokio 库来构建事件循环系统。Deno 建立在 V8、Rust 和 Tokio 的基础上,它的架构如下:
(图片来源:https://deno.land/manual/contributing/architecture)
1.1 Rust
Rust 是由 Mozilla 主导开发的通用、编译型编程语言。设计准则为 “安全、并发、实用”,支持函数式、并发式、过程式以及面向对象的编程风格。Deno 使用 Rust 语言来封装 V8 引擎,通过 libdeno
绑定,我们就可以在 JavaScript 中调用隔离的功能。
1.2 Tokio
Tokio 是 Rust 编程语言的异步运行时,提供异步事件驱动平台,构建快速,可靠和轻量级网络应用。利用 Rust 的所有权和并发模型确保线程安全。Tokio 构建于 Rust 之上,提供极快的性能,使其成为高性能服务器应用程序的理想选择。在 Deno 中 Tokio 用于并行执行所有的异步 IO 任务。
1.3 V8
V8 是一个由 Google 开发的开源 JavaScript 引擎,用于 Google Chrome 及 Chromium 中。V8 在运行之前将JavaScript 编译成了机器代码,而非字节码或是解释执行它,以此提升性能。更进一步,使用了如内联缓存(inline caching)等方法来提高性能。有了这些功能,JavaScript 程序与 V8 引擎的速度媲美二进制编译。在 Deno 中,V8 引擎用于执行 JavaScript 代码。
二、安装 Deno
Deno 能够在 macOS、Linux 和 Windows 上运行。Deno 是一个单独的可执行文件,它没有额外的依赖。你可以通过以下方式来安装它:
- 使用 Shell (macOS 和 Linux):
curl -fsSL https://deno.land/x/install/install.sh | sh
- 使用 PowerShell (Windows):
iwr https://deno.land/x/install/install.ps1 -useb | iex
- 使用 Scoop (Windows):
scoop install deno
- 使用 Chocolatey (Windows):
choco install deno
- 使用 Homebrew (macOS):
brew install deno
- 使用 Cargo (Windows,macOS,Linux):
cargo install deno
Deno 也可以手动安装,只需从 github.com/denoland/deno/releases 下载一个 zip 文件。它仅包含一个单独的可执行文件。在 macOS 和 Linux 上,你需要为它设置执行权限。当你成功安装之后,可以通过执行 deno --version
命令来查看已安装的 Deno 版本:
$ deno --version
deno 1.0.0
v8 8.4.300
typescript 3.9.2
2.1 deno_install
在安装过程中,如果遇到问题的话,大家可以试试 **justjavac(迷渡)**大神提供的安装脚本 —— deno_install。该脚本通过单行命令将 Deno 安装到系统中(国内加速)。
2.1.1 安装最新版
使用 Shell:
curl -fsSL https://x.deno.js.cn/install.sh | sh
使用 PowerShell:
iwr https://x.deno.js.cn/install.ps1 -useb -outf install.ps1; .\install.ps1
# iwr https://x.deno.js.cn/install.ps1 -useb | iex
2.1.2 安装某个特定版本
使用 Shell:
curl -fsSL https://x.deno.js.cn/install.sh | sh -s v0.41.0
使用 PowerShell:
iwr https://x.deno.js.cn/install.ps1 -useb -outf install.ps1; .\install.ps1 v0.41.0
更多详细的信息可以浏览 x.deno.js.cn 站点。
2.2 deno-cli
deno-cli 命令行界面提供了一组集成功能,让你可以沉浸在 Deno 的专有开发环境中。以下是 Deno 1.0.0 版本支持的所有子命令:
SUBCOMMANDS:
bundle Bundle module and dependencies into single file
cache Cache the dependencies
completions Generate shell completions
doc Show documentation for a module
eval Eval script
fmt Format source files
help Prints this message or the help of the given subcommand(s)
info Show info about cache or info related to source file
install Install script as an executable
repl Read Eval Print Loop
run Run a program given a filename or url to the module
test Run tests
types Print runtime TypeScript declarations
upgrade Upgrade deno executable to given version
2.3 REPL
在命令中输入 deno 命令,你就会启动一个 REPL(Read-Execute-Print-Loop):
$ deno
Deno 1.0.0
exit using ctrl+d or close()
> 1 + 2
3
> const name = "semlinker";
undefined
> console.log(name);
semlinker
undefined
2.4 VSCode Deno extension
相信很多小伙伴都在使用 VSCode IDE 进行前端开发,对于学习和开发 Deno 的开发者来说,一定不能错过 justjavac(迷渡) 大神开发的 Visual Studio Code Deno extension。
2.4.1 未安装 Deno extension
如果我们写 from "./hello.ts"
这样的语句,在 VSCode 中将会出现波浪号的错误信息。因为默认情况下,TypeScript 项目不需要添加 .ts
扩展名。
ts(2691): An import path cannot end with a ‘.ts’ extension. Consider importing ‘./hello’ instead.
Deno 允许从 URL 中导入模块,但是 TypeScript 并不支持从 URL 中导入模块。
ts(2307): Cannot find module ‘https://deno.land/x/std/log/mod’.
2.4.2 已安装 Deno extension
Deno 将远程导入(imports)缓存在 $DENO_DIR
环境变量指定的特殊目录中。如果未指定 $DENO_DIR
,则默认为系统的缓存目录。
该插件可以将远程导入(remote imports)解析为本地路径。
(本章节图片来源:https://marketplace.visualstudio.com/items?itemName=justjavac.vscode-deno)
了解 VSCode Deno extension 更多的详细信息,可以阅读 justjavac(迷渡) 大佬 我为 VS Code 开发了一个 Deno 插件 这篇文章。
三、Deno 初体验
3.1 welcome demo
相信一些读者安装完 Deno 已经迫不及待了,现在我们立马来体验一下 Deno 应用程序。首先打开你熟悉的命令行,然后在命令行输入以下命令:
$ deno run https://deno.land/std/examples/welcome.ts
Download https://deno.land/std/examples/welcome.ts
Warning Implicitly using master branch https://deno.land/std/examples/welcome.ts
Compile https://deno.land/std/examples/welcome.ts
Welcome to Deno 🦕
通过观察以上输出,我们可以知道当运行 deno run https://deno.land/std/examples/welcome.ts
命令之后,Deno 会先从 https://deno.land/std/examples/welcome.ts
URL 地址下载 welcome.ts
文件,该文件的内容是:
console.log("Welcome to Deno 🦕");
当文件下载成功后,Deno 会对 welcome.ts
文件进行编译,即编译成 welcome.ts.js
文件,然后再通过 V8 引擎来执行编译生成的 JavaScript 文件。需要注意的是,如果你在命令行重新运行上述命令,则会执行缓存中已生成的文件,并不会再次从网上下载 welcome.ts
文件。
$ deno run https://deno.land/std/examples/welcome.ts
Welcome to Deno 🦕
那如何证明再次执行上述命令时, Deno 会优先执行缓存中编译生成的 JavaScript 文件呢?这里我们要先介绍一下 deno info
命令,该命令用于显示有关缓存或源文件相关的信息:
$ deno info
DENO_DIR location: "/Users/fer/Library/Caches/deno"
Remote modules cache: "/Users/fer/Library/Caches/deno/deps"
TypeScript compiler cache: "/Users/fer/Library/Caches/deno/gen"
在上述的输出信息中,我们看到了 TypeScript compiler cache 这行记录,很明显这是 TypeScript 编译器缓存的目录,进入该目录后,通过一层层的查找,我们最终在 examples
目录下找到了 welcome.ts.js
文件:
➜ examples ls
welcome.ts.js welcome.ts.js.map welcome.ts.meta
打开目录中 welcome.ts.js
文件,我们可以看到以下内容:
"use strict";
console.log("Welcome to Deno 🦕");
//# sourceMappingURL=file:///Users/fer/Library/Caches/deno/gen/https/deno.land/std/examples/welcome.ts.js.map
下面我们来修改该文件,在文件中添加一行输出信息 console.log("Hello Semlinker, from Cache");
,具体如下:
"use strict";
console.log("Hello Semlinker, from Cache");
console.log("Welcome to Deno 🦕");
//# sourceMappingURL=file:///Users/fer/Library/Caches/deno/gen/https/deno.land/std/examples/welcome.ts.js.map
接着我们在命令行中重新执行以下命令:
$ deno run https://deno.land/std/examples/welcome.ts
Hello Semlinker, from Cache
Welcome to Deno 🦕
那么现在问题又来了,如何强制刷新缓存,即重新编译 TypeScript 代码呢?针对这个问题,在运行 deno run
命令时,我们需要添加 --reload
标志,来告诉 Deno 需要重新刷新指定文件:
$ deno run --reload https://deno.land/std/examples/welcome.ts
Download https://deno.land/std/examples/welcome.ts
Warning Implicitly using master branch https://deno.land/std/examples/welcome.ts
Compile https://deno.land/std/examples/welcome.ts
Welcome to Deno 🦕
除了 --reload
标志之外,Deno run 命令还支持很多其他的标志,感兴趣的读者可以运行 deno run --help
命令来查看更多的信息。
3.2 TCP echo server
前面我们已经介绍了如何运行官方的 welcome 示例,下面我们来介绍如何使用 Deno 创建一个简单的 TCP echo 服务器。首先我们创建一个 learn-deno 项目,然后在该项目下新建一个 quickstart 目录,接着新建一个 echo_server.ts
文件并输入以下代码:
const listener = Deno.listen({ port: 8080 });
console.log("listening on 0.0.0.0:8080");
for await (const conn of listener) {
Deno.copy(conn, conn);
}
for await…of 语句会在异步或者同步可迭代对象上创建一个迭代循环,包括 String,Array,Array-like 对象(比如 arguments 或者 NodeList),TypedArray,Map, Set 和自定义的异步或者同步可迭代对象。
for await…of 的语法如下:
for await (variable of iterable) { statement }
输入完以上代码之后,相信很多读者会跟我一样,直接在命令行运行以下命令:
➜ quickstart deno run ./echo_server.ts
Compile file:///Users/fer/LearnProjects/learn-deno/quickstart/echo_server.ts
error: Uncaught PermissionDenied: network access to "0.0.0.0:8080", run again with the --allow-net flag
at unwrapResponse ($deno$/ops/dispatch_json.ts:43:11)
at Object.sendSync ($deno$/ops/dispatch_json.ts:72:10)
at Object.listen ($deno$/ops/net.ts:51:10)
at Object.listen ($deno$/net.ts:152:22)
at file:///Users/fer/LearnProjects/learn-deno/quickstart/echo_server.ts:1:23
很明显是权限错误,从错误信息中,Deno 告诉我们需要设置 --allow-net
标志,以允许网络访问。为什么会这样呢?这是因为 Deno 是一个 JavaScript/TypeScript 的运行时,默认使用安全环境执行代码。下面我们添加 --allow-net
标志,然后再次运行 echo_server.ts
文件:
➜ quickstart deno run --allow-net ./echo_server.ts
listening on 0.0.0.0:8080
当服务器成功运行之后,我们使用 nc
命令来测试一下服务器的功能:
➜ ~ nc localhost 8080
hell semlinker
hell semlinker
介绍完如何使用 Deno 创建一个简单的 TCP echo 服务器,我们再来介绍一下如何使用 Deno 创建一个简单的 HTTP 服务器。
3.3 HTTP Server
与 TCP Server 一样,在 quickstart 目录下,我们新建一个 http_server.ts
文件并输入以下内容:
import { serve } from "https://deno.land/std@v0.50.0/http/server.ts";
const PORT = 8080;
const s = serve({ port: PORT });
console.log(` Listening on <http://localhost>:${PORT}/`);
for await (const req of s) {
req.respond({ body: "Hello Semlinker\\n" });
}
友情提示:在实际开发过程中,你可以从 https://deno.land/std 地址获取所需的标准库版本。示例中我们显式指定了版本,当然你也可以不指定版本,比如这样:https://deno.land/std/http/server.ts 。
在上述代码中,我们导入了 Deno 标准库 http 模块中 serve 函数,然后使用该函数快速创建 HTTP 服务器,该函数的定义如下:
// std/http/server.ts
export function serve(addr: string | HTTPOptions): Server {
if (typeof addr === "string") {
const [hostname, port] = addr.split(":");
addr = { hostname, port: Number(port) };
}
const listener = listen(addr);
return new Server(listener);
}
serve 函数接收一个参数,其类型是 string | HTTPOptions
,其中 HTTPOptions 接口的定义如下:
/** Options for creating an HTTP server. */
export type HTTPOptions = Omit<Deno.ListenOptions, "transport">;
export interface ListenOptions {
/** The port to listen on. */
port: number;
/** A literal IP address or host name that can be resolved to an IP address.
* If not specified, defaults to `0.0.0.0`. */
hostname?: string;
}
当输入的参数类型是字符串时,serve 函数会使用 :
冒号对字符串进行切割,获取 hostname 和 port,然后包装成对象赋值给 addr 参数,接着使用 addr 参数继续调用 listen
函数进一步创建 listener
对象,最终调用 new Server(listener)
创建 HTTP 服务器。
创建完 HTTP 服务器,我们来启动该服务器,打开命令行输入以下命令:
➜ quickstart deno run --allow-net ./http_server.ts
Compile file:///Users/fer/LearnProjects/learn-deno/quickstart/http_server.ts
Listening on <http://localhost>:8080/
接着打开浏览器,在地址栏上输入 http://localhost:8080/ 地址,之后在当前页面中会看到以下内容:
Hello World\n
四、调试 Deno
Deno 支持 V8 Inspector Protocol。使用 Chrome Devtools 或其他支持该协议的客户端(比如 VSCode)能够调试 Deno 程序。要启用调试功能,用 --inspect
或 --inspect-brk
选项运行 Deno,对应的选项描述如下:
--inspect=<HOST:PORT>
activate inspector on host:port (default: 127.0.0.1:9229)
--inspect-brk=<HOST:PORT>
activate inspector on host:port and break at start of user script
--inspect
选项允许在任何时间点连接调试器,而 --inspect-brk
选项会等待调试器连接,在第一行代码处暂停执行。
4.1 Chrome Devtools
让我们用 Chrome 开发者工具来调试一个简单的程序,我们将使用来自 std
的 file_server.ts,这是一个简单的静态文件服务。
使用 --inspect-brk
选项,在第一行代码处暂停执行。
$ deno run --inspect-brk --allow-read --allow-net https://deno.land/std@v0.50.0/http/file_server.ts
Debugger listening on ws://127.0.0.1:9229/ws/1e82c406-85a9-44ab-86b6-7341583480b1
Download https://deno.land/std@v0.50.0/http/file_server.ts
Compile https://deno.land/std@v0.50.0/http/file_server.ts
...
打开 chrome://inspect
,点击 Target 旁边的 Inspect
。
进一步了解更详细的调试说明,可访问 https://deno.land/manual/tools/debugger URL 地址。
4.2 VSCode
Deno 可以在 VSCode 中调试。插件的官方支持正在开发中 https://github.com/denoland/vscode_deno/issues/12,当然我们也可以通过手动提供 launch.json
配置,来连接调试器:
{
"version": "0.2.0",
"configurations": [
{
"name": "Deno",
"type": "node",
"request": "launch",
"cwd": "${workspaceFolder}",
"runtimeExecutable": "deno",
"runtimeArgs": ["run", "--inspect-brk", "-A", "<entry_point>"],
"port": 9229
}
]
}
注意:将 <entry_point>
替换为实际的脚本名称。
下面让我们来尝试一下调试本地源文件,创建 server.ts
:
import { serve } from "https://deno.land/std@v0.50.0/http/server.ts";
const s = serve({ port: 8000 });
console.log("http://localhost:8000/");
for await (const req of s) {
req.respond({ body: "Hello World\n" });
}
将 <entry_point>
改为 server.ts
,然后运行。
(图片来源:https://deno.land/manual/tools/debugger)
(图片来源:https://deno.land/manual/tools/debugger)
若想进一步了解 Deno 与 Node.js 的 区别,可以阅读 超杰_ Deno 正式发布,彻底弄明白和 node 的区别 这篇文章。
在 Deno 1.0.0 发布之后,本人才开始踏上学习 Deno 的征程,有写得不好的地方,欢迎小伙伴们指出,对 Deno 感兴趣的小伙伴可以加我个人微信号 semlinker 一起学习与交流哈。