初探Deno.js

2023-04-13 16:18:22 浏览数 (2)

Node.js 的作者 Ryan Dahl 在 2018 年 JSConf EU 上发布了一篇演讲:10 Things I Regret About Node.js,讲述了他认为自己在设计 Node.js 时的十个(实际上演讲中只提到七个)失误,包括:没有坚持使用 Promise,使用了 GYP 构建系统,package.json 和 node_modules 的设计失误等。

在演讲中 Ryan Dahl 宣布了一个新项目:Deno,一个新的服务端 Javascript 运行时。经过两年多的发展,Deno 已经发布了 1.8 版本,也有了一个活跃的开发者社区。我们今天就来简单的了解一下 Deno。

Deno 简介

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

Deno 使用 V8 引擎,由 Rust 构建,是一个简单、现代、安全的 Javascript 和 Typescript 运行时。

Deno 的主要特性有:

  • 默认安全。外部代码没有文件系统、网络、环境的访问权限,除非显式开启。
  • 支持开箱即用的 TypeScript 的环境。
  • 只分发一个独立的可执行文件 (deno)。
  • 有着内建的工具箱,比如一个依赖信息查看器 (deno info) 和一个代码格式化工具 (deno fmt)。
  • 有一组经过审计的标准模块,保证能在 Deno 上工作。
  • 脚本代码能被打包为一个单独的 JavaScript 文件。

安装 Deno

可以执行快速安装脚本方便地将 Deno 安装到不同操作系统上。

Mac 和 Linux 平台

代码语言:javascript复制
curl -fsSL https://deno.land/x/install/install.sh | sh

Windows 平台(使用 Powershell)

代码语言:javascript复制
iwr https://deno.land/x/install/install.ps1 -useb | iex

第一个 Deno 脚本

安装好 Deno 后,我们就可以直接在终端里使用 Deno 运行 Javascript/Typescript 脚本。

代码语言:javascript复制
$ deno run https://deno.land/std/examples/welcome.ts
Download https://deno.land/std/examples/welcome.ts
Warning Implicitly using latest version (0.91.0) for http://deno.land/std/examples/welcome.ts
Download https://deno.land/std@0.91.0/examples/welcome.ts
Check https://deno.land/std/examples/welcome.ts
Welcome to Deno!

有例子中我们可以知道deno run可以直接运行远程脚本,当然运行本地脚本也不是问题。

代码语言:javascript复制
$ echo 'console.log("Hello, World!")' > hello.ts
$ deno run .hello.ts
Check file:///C:/Users/duyix/lab/deno-code/hello.ts
Hello, World!

使用 Deno 运行 HTTP Server

我们再运行一下 Deno 官网示例中的启动 HTTP Server 的程序。

代码语言:javascript复制
import { serve } from "https://deno.land/std@0.90.0/http/server.ts";
const s = serve({ port: 8000 });
console.log("http://localhost:8000/");
for await (const req of s) {
  req.respond({ body: "Hello Worldn" });
}
代码语言:javascript复制
$ deno run .server.ts
Download https://deno.land/std@0.90.0/http/server.ts
# 省略了一些依赖下载的日志
Check file:///C:/Users/duyix/lab/deno-code/server.ts
error: Uncaught PermissionDenied: network access to "0.0.0.0:8000", run again with the --allow-net flag
    at processResponse (core.js:223:11)
    at Object.jsonOpSync (core.js:246:12)
    at opListen (deno:cli/rt/30_net.js:32:17)
    at Object.listen (deno:cli/rt/30_net.js:207:17)
    at serve (server.ts:304:25)
    at server.ts:2:11

可以看到直接运行server.ts会失败,Deno 报错PermissionDenied。这是 Deno 默认的安全策略的行为,使用 Deno 运行脚本时默认不提供网络连接、文件 IO 等权限,需要显式的在deno run命令中启用对应的权限,在这个例子中我们需要加上--allow-net标志来启用网络权限。

代码语言:javascript复制
$ deno run --allow-net .server.ts
http://localhost:8000/

然后我们就可以访问本地的 8000 端口了。

代码语言:javascript复制
$ http :8000
HTTP/1.1 200 OK
content-length: 12

Hello World

Deno REPL

和 Node 类似,Deno 也有 REPL 模式,直接运行deno repdeno就可以交互式地运行 JavaScript 脚本了,Deno 的 REPL 模式暂时还不提供 Typescript 支持。

代码语言:javascript复制
$ deno
Deno 1.5.4
exit using ctrl d or close()
> let resp = await fetch('https://httpbin.org/headers')
undefined
> await resp.json()
{
  headers: {
    Accept: "*/*",
    "Accept-Encoding": "gzip, br",
    Host: "httpbin.org",
    "User-Agent": "Deno/1.5.4",
    "X-Amzn-Trace-Id": "Root=1-6058b379-7fd616e829aa462029478558"
  }
}
> close()

我们在 Deno REPL 中使用了 fetch 进行网络请求,Deno 实现了一些常见的Web API。并且可以看到在 REPL 模式中,deno 默认开启了网络权限。

使用 Deno 编写命令行程序

Deno 是编写命令行工具的一个新的选择。

代码语言:javascript复制
// dn-echo.ts
console.log(Deno.args.join(" "));
代码语言:javascript复制
$ deno run ./dn-echo.ts 1 2 3
1 2 3

Deno是 Deno 的一个全局对象,提供了一些系统 API(命令行参数获取、文件读写等)。可以通过Deno.arg获取命令行参数。

使用 Deno 编写命令行程序另一个便捷之处是我们可以很方便地通过deno install命令将本地脚本或网络脚本安装成全局工具。

代码语言:javascript复制
$ deno install .dn-echo.ts
✅ Successfully installed dn-echo
C:Usersduyix.denobindn-echo.cmd

值得注意的是deno install命令也要加上对应的权限选项。

代码语言:javascript复制
$ deno install -n dn-cat --allow-read https://deno.land/std@0.90.0/examples/cat.ts
Download https://deno.land/std@0.90.0/examples/cat.ts
Check https://deno.land/std@0.90.0/examples/cat.ts
✅ Successfully installed dn-cat
C:Usersduyix.denobindn-cat.cmd
C:Usersduyix.denobindn-cat (shell)

$ dn-cat .hello.ts
console.log("Hello, World!")

我们从 url 安装了一个命令行脚本 cat(使用-n dn-cat选项指定了全局工具的名字),并且加上了--allow-read选项来开启读文件权限。

使用 Deno 开发 Web 应用

作为 Node.js 的挑战者,开发 Web 应用自然是 Deno 的看家本领。Deno 标准库http模块的功能已经很齐全了。不过为了更佳的开发体验,我们在这里使用oak框架(借鉴了 Node.js 中的 koa 框架)来开发 web 应用。

代码语言:javascript复制
//app.js
import { Application, Router } from "https://deno.land/x/oak/mod.ts";

const app = new Application();

const port = 8000;

// Logger
app.use(async (ctx, next) => {
  await next();
  const rt = ctx.response.headers.get("X-Response-Time");
  console.log(`HTTP ${ctx.request.method} on ${ctx.request.url}`);
  console.log(`${ctx.request.method} ${ctx.request.url} - ${rt}`);
});

// Timing
app.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  const ms = Date.now() - start;
  ctx.response.headers.set("X-Response-Time", `${ms}ms`);
});

const router = new Router();

router
  .get("/", (ctx) => {
    ctx.response.body = "Hello Deno";
  })
  .get("/:name", (ctx) => {
    ctx.response.body = `Hello ${ctx.params.name}`;
  });

app.use(router.routes());
app.use(router.allowedMethods());

app.addEventListener("listen", () => {
  console.log(`Listening on localhost:${port}`);
});

await app.listen({ port });

app.js中,我们初始化了一个Application对象app,注册了两个中间件(分别用来打日志和记录请求时间)。 然后使用oak框架提供的Router组件实例化了一个router对象,定义了两个接口并将router注册到了app上。 随后在app上注册了一个事件监听,在listen事件发生(也就是应用开始运行)时,输出了相关日志。

我们可以直接运行app.js

代码语言:javascript复制
$ deno run --allow-net ./app.js
Listening on localhost:8000

随后就可以访问接口了。

代码语言:javascript复制
$ http :8000/
HTTP/1.1 200 OK
content-length: 10
content-type: text/plain; charset=utf-8
x-response-time: 1ms

Hello Deno

$ http :8000/mike
HTTP/1.1 200 OK
content-length: 10
content-type: text/plain; charset=utf-8
x-response-time: 0ms

Hello mike

部署 Deno Web 应用

第一次运行app.js的时候 Deno 需要从互联网上下载其相关依赖, 我们可以使用deno cache命令手动将指定脚本的依赖缓存到本地(使用-r选项可以强制重新加载)。

代码语言:javascript复制
$ deno cache -r ./app.js

deno cache命令在构建镜像时还是很必要的,因为无法保证容器在每次运行的时候都可以顺利下载好依赖。直接在构建阶段将依赖准备好是一个更稳妥的选择。

一个常见的 Deno Web 应用的 Dockerfile 如下:

代码语言:javascript复制
FROM hayd/alpine-deno:1.8.2

# The port that your application listens to.
EXPOSE 1993

WORKDIR /app

# Prefer not to run as root.
USER deno

# Cache the dependencies as a layer (the following two steps are re-run only when deps.ts is modified).
# Ideally cache deps.ts will download and compile _all_ external files used in main.ts.
COPY deps.ts .
RUN deno cache deps.ts

# These steps will be re-run upon each file change in your working directory:
ADD . .
# Compile the main app so that it doesn't need to be compiled each startup/entry.
RUN deno cache main.ts

CMD ["run", "--allow-net", "main.ts"]

打包脚本

Deno CLI 提供deno bundle命令可以将指定脚本及其依赖打包成一个 javascript 文件(类似 webpack),提供了另外一种分发应用的方式。

代码语言:javascript复制
$ deno bundle ./app.js app.bundle.js
Bundle file:///C:/Users/duyix/lab/deno-code/app.js
Check file:///C:/Users/duyix/lab/deno-code/app.js
Emit "app.bundle.js" (402.67KB)

可以看到打包生成的脚本还是很大的,不过根据我的观察app.bundle.js其实是格式化良好的,并且里面的函数名和参数名等都没有缩减过。deno bundle的输出文件大小精剪还是有很大优化空间的。

我个人还是更推荐使用容器镜像运行 Deno 应用,更规范,也更容易维护应用源代码和依赖。

总结

Deno 毕竟还是一个很年轻的项目,稳定性和配套的资源和支持与 Node.js 相比还是有很大差距的。但正如 Ryan Dahl 所说,Node.js 有其固有的一些历史遗留的设计问题。Deno 的优秀理念正在吸引越来越多的开发者使用并加入社区,相信 Deno 会成为服务端 Web 开发领域的新秀。

0 人点赞