大家好,我是 @洛竹 本文首发于 洛竹的官方网站 本文同步于公众号『洛竹早茶馆』,转载请联系作者。 创作不易,养成习惯,素质三连!
2018 年时,一篇 如何看待 ry 的项目 deno 的 issue 被中文刷屏的事件? 的文章成功引起了我对 Deno 的注意,cnode 有一篇文章严厉斥责这是中国开发者的耻辱,对此我不敢完全认同,毕竟 996 的大环境下,是很难孵化出国外这种创新精神的。但我并不否认学不学的动,全看个人。本人近期最敬佩的 蜗牛老湿_大圣坚持每日 5 点多起床,做到如此勤奋与持久,何愁学不动?
1、热度,虽说关于学不动的问题造成了负面影响,但是 deno 成功地因此赚足了噱头和流量。
2、趋势,下图中我们可以清楚地看到,Deno 从 2018 年创建至今已斩获近 70K 的 star,尤其是 2020-05-13 发布 1.0 之后,更是迎来一波高峰。
3、掘金征文活动,上次沸点活动有幸获赠豪华升降桌,但是我最想要的其实是掘金周边大礼包。
从 Deno 的名字就可以看出和 Node 的关系:De(Destroy)no(Node),销毁 Node, ry 在演讲中曾列举了 Node 存在的一些问题:
基于以上问题,ry 决定利用 JavaScript 和浏览器最新特性开发一款现代的 JavaScript 运行时。更多问题,大家请观看参考资料的演讲。
Deno 刚发布的时候,社区除了各种"学不动"的声音之外,还有就是 Deno 是否会取代 Node.js。我认为活在当下(NodeJs),未雨绸缪(Deno)是最佳态度。
Deno 没有外部依赖,以单一可以执行文件发布。你可以 使用下面的安装程序安装 Deno,或者先从 版本发布页面下载已发布的二进制可执行文件。
「使用 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
「使用 Homebrew (Mac):」
$ brew install deno
deno -V
,如果它打印出 Deno 版本,说明安装成功。deno help
以查看帮助文档。deno help <subcommand>
以查看子命令的选项。要升级已安装的版本,运行:
deno upgrade
这会从 github.com/denoland/deno/releases 获取最新的发布版本,然后解压并替换现有的版本。
您也可以用此来安装一个特定的版本:
deno upgrade --version 1.0.1
推荐使用 VSCode 及 VSCode Deno 进行开发,VSCode Deno 是 justjavac 大佬开发的。
vscode 执行 Deno: Initialize Workspace Configuration
命令初始化项目,洛竹建议如下配置:
{
"deno.enable": true,
"deno.lint": true,
"deno.unstable": true,
"editor.formatOnSave": true,
"[typescript]": {
"editor.defaultFormatter": "denoland.vscode-deno"
}
}
试着运行如下的简单程序:
$ deno run https://deno.land/std/examples/welcome.ts
$ deno run --allow-read --allow-net https://deno.land/std@0.62.0/http/file_server.ts
创建一个名为 index.ts
的文件
import { serve } from 'https://deno.land/std@0.62.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' });
}
执行 deno run xxx
命令执行文件:
$ deno run --allow-net --reload index.ts // --reload 是第一次执行时缓存模块用的
通过 HTTP 请求从服务器获取数据是一件很常见的事。让我们编写一个简单的程序来获取文件并打印到终端。
就像浏览器一样,您可以使用 web 标准的 fetch
API 来发出请求。
// 我们取得了第一个命令行参数,存储到变量 url。
const url = Deno.args[0];
// 我们向指定的地址发出请求,等待响应,然后存储到变量 res。
const res = await fetch(url);
// 我们把响应体解析为一个 ArrayBuffer,等待接收完毕,将其转换为 Uint8Array,最后存储到变量 body。
const body = new Uint8Array(await res.arrayBuffer());
// 我们把 body 的内容写入标准输出流 stdout。
await Deno.stdout.write(body);
$ deno run --allow-net sendHttp.ts http://example.com
// 或
$ deno run --allow-net=example.com https://deno.land/std/examples/curl.ts https://example.com
const encoder = new TextEncoder();
const greetText = encoder.encode('Hello World\nMy name is youngjuning!');
await Deno.writeFile('greet.txt', greetText);
Deno 也提供内置的 API,它们都位于全局变量 Deno
中。您可以在此找到相关文档:doc.deno.land。
文件系统 API 没有 web 标准形式,所以 Deno 提供了内置的 API。
在这个程序中,每个命令行参数都是一个文件名,参数对应的文件将被依次打开,打印到标准输出流。
const filenames = Deno.args;
for (const filename of filenames) {
const file = await Deno.open(filename);
await Deno.copy(file, Deno.stdout);
file.close();
}
除了内核到用户空间再到内核的必要拷贝,这里的 copy()
函数不会产生额外的昂贵操作,从文件中读到的数据会原样写入标准输出流。这反映了 Deno I/O 流的通用设计目标。
尝试一下:
$ deno run cat.ts /etc/passwd
新建 cat.ts
,这个示例是一个 TCP echo 服务,接收 8080 端口的连接,把接收到的任何数据返回给客户端。
const hostname = '127.0.0.1';
const port = 8080;
const listener = Deno.listen({ hostname, port });
console.log(`Listening on ${hostname}:${port}`);
for await (const conn of listener) {
Deno.copy(conn, conn);
}
尝试用 netcat 向它发送数据。
像示例 cat.ts
一样,copy()
函数不会产生不必要的内存拷贝。它从内核接收数据包,然后发送回去,就这么简单。
在任何地方导入 URL 似乎都不方便。如果其中一个 URL ,链接到了一个稍微不同的库版本呢?在大型项目中,维护 URL 是否容易出错?解决方案是在中心 deps.ts 文件,导入和重新导出外部库(与 Node 的 package.json 文件目的相同)。例如,假设您在一个大型项目中,使用了上述测试库。要做的,不是在任何地方导入"https://deno.land/std/testing/mod.ts",而是可以创建一个 deps.ts
,用来导出第三方代码:
export * from "https://deno.land/std/http/server.ts"; // 推荐
export * as Server from "https://deno.land/std/http/server.ts";
export { default as Server } from "https://deno.land/std/http/server.ts";
在整个项目中,都可以从 deps.ts 导入,这样就可以避免对同一个 URL 进行多次引用:
import { test, assertEquals } from './deps.ts';
这种设计避免了,由于包管理软件、集中的代码库和多余的文件格式,而产生的过多复杂性。
deno bundle
自带打包和 tree shaking 功能,可以将我们的代码打包成单文件
#!/bin/sh
deno bundle ./src/index.ts ./dist/index.js
deno install
可以将我们的代码生成可执行文件进行直接使用
#!/bin/sh
deno install --allow-read --allow-net --allow-write -n youngjuning ./src/index.ts
我们也可以直接安装远程的库:
deno install --allow-read --allow-net https://deno.land/std/http/file_server.ts
deno 的可执行文件默认都放在 /Users/yangjunning/.deno/bin/
目录下,我们需要将它注册到环境变量中:
$ export PATH="/Users/yangjunning/.deno/bin:$PATH"
我们已经知道了默认情况下,Deno 是安全的。因此 Deno 模块没有文件、网络或环境的访问权限,除非您为它授权。在命令行参数中为 deno 进程授权后才能访问安全敏感的功能。
以下权限是可用的:
-A
, --allow-all
:允许所有权限,这将禁用所有安全限制。--allow-env
:允许环境访问,例如读取和设置环境变量。--allow-hrtime
: 允许高精度时间测量,高精度时间能够在计时攻击和特征识别中使用。--allow-net=<allow-net>
: 允许网络访问。您可以指定一系列用逗号分隔的域名,来提供域名白名单。--allow-plugin
: 允许加载插件。请注意:这是一个不稳定功能。--allow-read=<allow-read>
允许读取文件系统。您可以指定一系列用逗号分隔的目录或文件,来提供文件系统白名单。--allow-run
允许运行子进程。请注意,子进程不在沙箱中运行,因此没有与 deno 进程相同的安全限制,请谨慎使用。--allow-write=<allow-write>
允许写入文件系统。您可以指定一系列用逗号分隔的目录或文件,来提供文件系统白名单。Deno 还允许您使用白名单控制权限的粒度。
这是一个用白名单限制文件系统访问权限的示例,仅允许访问 /usr
目录,但它会在尝试访问 /etc
目录时失败。
--allow-write
也一样,代表写入权限。
fetch.ts
:
const result = await fetch('https://deno.land/');
这是一个设置 host 或 url 白名单的示例:
$ deno run --allow-net=github.com,deno.land fetch.ts
如果 fetch.ts
尝试与其他域名建立网络连接,那么这个进程将会失败。
允许访问任意地址:
$ deno run --allow-net fetch.ts
一个适用于 Deno 的类似于 dotenv的插件
「使用」
你可以直接导入它,然后就可以使用和它同级目录的.env
文件:
import { load } from 'https://deno.land/x/denv/mod.ts';
await load();
console.log(Deno.env.get('HOME')); // e.g. outputs "/home/alice"
console.log(Deno.env.get('MADE_UP_VAR')); // outputs "Undefined"
「Env File 规则」
除了 double quoted values expand new lines
没有实现,其他的规则和 dotenv 一样。
deno bundle
自带打包和 tree shaking 功能,可以将我们的代码打包成单文件
$ deno bundle ./src/index.ts ./dist/index.js
deno install
可以将我们的代码生成可执行文件进行直接使用
$ deno install --allow-read --allow-net --allow-write -n youngjuning ./src/index.ts
deno 的可执行文件默认都放在 /Users/yangjunning/.deno/bin/
目录下,我们需要将它注册到环境变量中:
$ export PATH="/Users/yangjunning/.deno/bin:$PATH"
我们都知道, deno 默认是安全的,就是导致了默认情况下是不允许访问网络、读写文件等。比如有个名为 index.ts 的文件内容如下:
import { serve } from 'https://deno.land/std@0.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' });
}
如果直接执行 deno run index.ts
, 会报错:
error: Uncaught PermissionDenied: network access to "0.0.0.0:8000", run again with the --allow-net flag
所以我们很自然的就会在启动命令的最后加上 --allow-net
,如下:
$ deno run index.ts --allow-net
但是,这样仍然会报错。查了资料才知道 ,--allow-net
、--allow-read
之类的标志是不可以放到文件名后面的,必须紧跟在 deno run
后面,比如,如下才是正确的:
$ deno run --alow-net index.ts
--reload
刷新缓存。(所以它在飞机上也能工作)。