高能警告Node&js杀手来了,了不得的 Deno 入门教程!

建立了一个 “重学TypeScript” 的微信群,想加群的小伙伴,加我微信 "semlinker",备注重学TS。node

1、Deno 简介

Deno 是一个 JavaScript/TypeScript 的运行时,默认使用安全环境执行代码,有着卓越的开发体验。Deno 含有如下功能亮点:git

  • 默认安全。外部代码没有文件系统、网络、环境的访问权限,除非显式开启。github

  • 支持开箱即用的 TypeScript 的环境。chrome

  • 只分发一个独立的可执行文件(deno)。typescript

  • 有着内建的工具箱,好比一个依赖信息查看器(deno info)和一个代码格式化工具(deno fmt)。shell

  • 有一组通过审计的 标准模块,保证能在 Deno 上工做。编程

  • 脚本代码能被打包为一个单独的 JavaScript 文件。json

Deno 是一个跨平台的运行时,即基于 Google V8 引擎的运行时环境,该运行时环境是使用 Rust 语言开发的,并使用  Tokio 库来构建事件循环系统。Deno 创建在 V八、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 代码。

2、安装 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-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.2 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

3、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

4、调试 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 有没有产生兴趣呢?若是有的话,欢迎小伙伴给我留言,后续我再来一篇使用 Deno 开发 Web API 的文章哈。