这是一篇关于deno初体验文章

Deno – 小而美的脚本运行时

a simple, modern and secure runtime for JavaScript and TypeScript that uses V8 and is built in Rust。

Dahl(Node.js 的创始人之一)在2017年创建,Rust 语言开发。 deno 音译帝诺,恐龙(dinosaur)的简称,deno 这个名字就是来自 Node 的字母重新组合(Node = no + de),表示”拆除 Node.js”(de = destroy, no = Node.js)。

创建原由

  • Node.js Callback hell。
  • 原生支持CommonJs,对ES模块不兼容。
  • npm(你怕吗)模块管理工具,npm_modules 极其庞杂,还有Dependency Hell,版本重复问题等问题,极难管理。详情可查看node_modules 困境
    img
  • 功能不完整,要学习很多外部工具,诸如webpack,babel,typescript、eslint、prettier……

“由于上面这些原因,Ryan Dahl 决定放弃 Node.js,从头写一个替代品,彻底解决这些问题。deno 这个名字就是来自 Node 的字母重新组合(Node = no + de),表示”拆除 Node.js”(de = destroy, no = Node.js)。
跟 Node.js 一样,Deno 也是一个服务器运行时,但是支持多种语言,可以直接运行 JavaScript、TypeScript 和 WebAssembly 程序。
它内置了 V8 引擎,用来解释 JavaScript。同时,也内置了 tsc 引擎,解释 TypeScript。它使用 Rust 语言开发,由于 Rust 原生支持 WebAssembly,所以它也能直接运行 WebAssembly。它的异步操作不使用 libuv 这个库,而是使用 Rust 语言的 Tokio 库,来实现事件循环(event loop)。”
— 出自阮一峰大神《Deno 运行时入门教程:Node.js 的替代品

闲言少叙,书归正文,让我们一起来看看Deno的特性吧。

Feature

  • 服务器运行时,支持多种语言,可直接运行 JavaScriptTypeScriptWebAssembly 程序。
  • 具有安全控制,默认情况下脚本不具有读写权限。如果脚本未授权,就读写文件系统或网络,会报错。
  • 只有一个可执行文件,所有操作都通过这个文件完成。它支持跨平台(Mac、Linux、Windows)。
  • 异步操作 一律返回 Promise。
  • Deno 支持 Web API,尽量跟浏览器保持一致。windowfetchwebCryptoworker, 也支持 onloadonunloadaddEventListener 等事件操作函数。
  • 所有模块通过 URL 加载,比如import { bar } from "https://foo.com/bar.ts"(绝对 URL)或import { bar } from './foo/bar.ts'(相对 URL)。本地缓存,有个Cache总目录
  • 原生支持TypeScript, 内置TypeScript编译器swc的Rust库组合实现。
  • 打包、格式清理、测试、安装、文档生成、linting、脚本编译成可执行文件等一条龙服务。

优势

不足

  • 缺少集大成框架,满足不了企业级应用开发。
  • 社区还不完善,第三方库不够丰富。
  • 缺乏包管理工具,管理起来混乱。
  • 网络上一些怀疑的声音

CLI

permission

WebAssembly

const wasmCode = new Uint8Array([
  0, 97, 115, 109, 1, 0, 0, 0, 1, 133, 128, 128, 128, 0, 1, 96, 0, 1, 127,
  3, 130, 128, 128, 128, 0, 1, 0, 4, 132, 128, 128, 128, 0, 1, 112, 0, 0,
  5, 131, 128, 128, 128, 0, 1, 0, 1, 6, 129, 128, 128, 128, 0, 0, 7, 145,
  128, 128, 128, 0, 2, 6, 109, 101, 109, 111, 114, 121, 2, 0, 4, 109, 97,
  105, 110, 0, 0, 10, 138, 128, 128, 128, 0, 1, 132, 128, 128, 128, 0, 0,
  65, 42, 11
]);
const wasmModule = new WebAssembly.Module(wasmCode);
const wasmInstance = new WebAssembly.Instance(wasmModule);
const main = wasmInstance.exports.main as CallableFunction
console.log(main().toString());

第三方库

Installation

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

Using Homebrew (macOS):

brew install deno

Getting Started

Try running a simple program:

deno run https://deno.land/std/examples/welcome.ts

Or a more complex one:

import {
  serve
} from "https://deno.land/std@0.83.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"
  });
}

例子

缓存目录

以Mac为例,举个🌰

 $HOME/Library/Caches/Deno
#  远程库下载地址
deno run https://deno.land/std@0.83.0/examples/welcome.ts
# DIRECTORIES
gen/: 缓存编译为JavaScript的文件
deps/: 缓存导入的远程url的文件
  |__ http/: http方式导入的文件
  |__ https/: https方式导入的文件

# FILES
deno_history.txt: Deno REPL历史记录缓存

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

gen/

$DENO_DIR/gen/ 被用来存放 JavaScript 文件,这些文件是从 TypeScript 源码编译来的。这样的编译是必要的,因为 V8 不识别 JS 子集之外的 TypeScript 语法。

gen/目录下的每一个 JS 文件的文件名是他的 TypeScript 源码的 hash 值。同时 JS 文件也对应一个 .map 为后缀的 source map 文件。

缓存存在的原因是为了避免在用户没有修改代码的情况下,每次运行时不断的重新编译文件。比如我们有一个 hello-world.ts 文件,他只是包含了代码 console.log(“Hello world”)。在第一次运行时,我们会看到编译信息:

$ deno hello-world.ts
Compiling /Users/kevinqian/my-folder/hello-world.ts
Hello world

但是在没有修改文件内容的情况下,当你重新运行代码:

$ deno hello-world.ts
Hello world

不会再有编译信息的提示。这是因为在这一次运行中,Deno 直接使用了 gen/ 中缓存的版本,而不用再次编译。

缓存加载和保存的代码,可以从文件 src/deno_dir.rs 中的 DenoDir::load_cache 和 DenoDir::code_cache 中找到。

如果想要强制 Deno 重新编译你的代码而不是使用缓存的版本,你需要使用 --recompile 标志。

deps/

$DENO_DIR/deps 被用来保存远端 url import 获得的文件。根据 url 的模式,他包含了子目录(现在只有http和https),并且保存文件的位置由 URL path 决定。比如,对于下面的的 import(请注意,Deno 要求用户显式地指定扩展名)。

import {
  serve
} from "https://deno.land/x/std/net/http.ts";

下载的http.ts文件将会被存储在:

$DENO_DIR/deps/https/deno.land/x/std/net/http.ts

需要注意,除非用户用 --reload 标志运行代码,否则我们的http.ts文件在接下来的运行中不会被重新下载。

当前(警告:将来可能改变),Deno 会关注从远端下载的文件的内容的 MIME 类型。在文件缺少扩展名或扩展名与内容类型不匹配的情况下,Deno 将创建一个以 .mime 结尾的额外文件,来存储 HTTP 响应头提供的 mime 类型。如果我们下载的文件名是 a.ts,然而响应头里面是 Content-Type: text/javascript,一个包含text/javascript内容的a.ts.mime文件将会在他旁边被创建。由于.mime文件的存在,a.ts 后面将会被当做一个 JavaScript 文件被 import。

演示

cd /Users/lulu/my-projects/deno-demo && code .

总结

Deno的目标不是替代Node,而是Node的一个补充完善方案。
目前Deno还处于初步阶段,标准库,兼容性还有待改进,适合脚本,小型应用开发。想要完成一个成熟的大型项目,还需要时间的沉淀。让我们好好期待下,未来的Deno会如何发展?