乐趣区

关于deno:从node到deno

简介

Deno 简略说是 Node.js 的替代品,是 Node.js 之父 Ryan Dahl 为挽回 Node.js 的谬误而开发的。

Node.js 存在的问题有:

  1. npm 包治理(node_modules)简单。
  2. 历史起因导致的 api 保护,比方晚期变态的 callback 设置。
  3. 没有安全措施,用户只有下载了内部模块,就只好放任他人的代码在本地运行,进行各种读写操作。
  4. 性能不欠缺,导致各种工具层出不穷,比方 webpack、babel 等。

因为下面这些起因,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)。

它的架构如下图所示:

阐明:
1、Rust 是由 Mozilla 主导开发的通用、编译型编程语言。设计准则为“平安、并发、实用”,反对函数式、并发式、过程式以及面向对象的编程格调。Deno 应用 Rust 语言来封装 V8 引擎,通过 libdeno 绑定,咱们就能够在 JavaScript 中调用隔离的性能。
2、Tokio 是 Rust 编程语言的异步运行时,提供异步事件驱动平台,构建疾速,牢靠和轻量级网络应用。利用 Rust 的所有权和并发模型确保线程平安。Tokio 构建于 Rust 之上,提供极快的性能,使其成为高性能服务器应用程序的现实抉择。在 Deno 中 Tokio 用于并行执行所有的异步 IO 工作。
3、V8 是一个由 Google 开发的开源 JavaScript 引擎,用于 Google Chrome 及 Chromium 中。V8 在运行之前将 JavaScript 编译成了机器代码,而非字节码或是解释执行它,以此晋升性能。更进一步,应用了如内联缓存(inline caching)等办法来进步性能。有了这些性能,JavaScript 程序与 V8 引擎的速度媲美二进制编译。在 Deno 中,V8 引擎用于执行 JavaScript 代码。

劣势

Deno 凭什么来吸引开发者改换门庭,转投它的怀抱呢?于我而言,次要有以下几点:

  • 人造反对 ts。如果用 Node.js,须要咱们手动将 ts 编译成 js,或者应用 ts-node 这种第三方工具。Deno 不须要配置,开箱即用。
  • 内存平安。相较于 Node.js,它的性能劣势并不显著,因为就是 js 换了个运行环境而已,Rust 并不见得比 C ++ 更高效,不过 Rust 会人造让它的内存平安和外围代码的健壮性更有保障些。
  • 平安机制。除非明确启用,否则没有文件,网络或环境拜访权限。对于运行权限的划分,能清晰晓得你的程序须要领有哪些权限,对网络资源没有信赖的同学能够释怀了。
  • 没有历史包袱。不像 Node.js 一开始自定义了 commonjs 标准,Deno 间接反对 ES Modules。没有回调天堂,代码构造更清晰,利于 tree shaking;也能够间接应用顶级 await。
  • 去中心化。不必每个工程都装置一大堆 node_modules,有个线上的 url 就能获取代码。
  • 内置浏览器 API。Deno 实现了 fetch、FormData、WebSocket 等浏览器的 API,尽管被某些开发者诟病,但对前端开发者而言是真的敌对,比方进行接口调用,与在浏览器里应用简直没有区别。
  • 全家桶似的服务,比方打包、格式化、代码校验,能够让你更专一业务,而不是成为某工具的配置工程师。

毛病

Deno 的毛病也很显著:

  • 没有针对 Node.js 压倒性的性能劣势,所以对以上劣势不感兴趣的开发者而言,就没有吸引力了。

    Deno 是一个适合的异步服务器,每秒 25k 申请足以满足大多数目标,此外,因为广泛应用 Promise,Deno 有更好的尾部提早。目前 Deno HTTP 服务器每秒解决约 25 000 个申请,最大提早为 1.3 毫秒,与之相比,Node 程序每秒解决 34 000 个申请,最大提早介于 2 到 300 毫秒之间。
    能够看出,Deno 的劣势在于提早低,但并没有压倒性劣势。

  • 无奈齐全继承 Node.js 的生态。尽管 Deno 提供了几款 CDN 服务,能够将旧的 npm 包转换为 Deno 可应用的格局,但并非所有 npm 包都能转换胜利。因为 Node.js 仍有局部 API 官网没有实现,如果某个偏底层的包转换失败,所有依赖它的包都无奈胜利。所以,你很有可能在开发的某个环节须要造轮子。当然,这对于喜爱钻研技术的同学而言反而是个利好。
  • 去中心化后,国内就没有方法像 npm 做镜像,官网搭建 deno.land 来存储 github 上各种资源,但目前只反对 github 上公有资源。想要援用公有文件,就必须本人搭建服务器,在 CICD 中构建的话,又有额定的技术难点须要解决。

    装置

1、Mac/Linux

鉴于国内的网速起因,@justjavac 大佬搞了国内的镜像减速:

curl -fsSL https://x.deno.js.cn/install.sh | sh

我用 Linux 部署后,报没有 glibc-2.18 这个版本依赖。

按网上教程,又装置了一下:

curl -O http://ftp.gnu.org/gnu/glibc/glibc-2.18.tar.gz
tar zxf glibc-2.18.tar.gz
cd glibc-2.18/
mkdir build
cd build/
../configure --prefix=/usr
make -j2
make install

后两步比较慢,不要退出。

2、Windows

https://github.com/denoland/deno/releases

间接在这里下载 exe 文件吧。

当然,Mac 与 Linux 也能够这样装置,但最新的 Mac 零碎可能会因为平安权限而禁止程序应用,须要参考这里配置下。

设置环境变量

你可能还须要设置环境变量。以下是 Mac/Linux 命令行操作:

echo 'export DENO_INSTALL="$HOME/.deno"export PATH="$DENO_INSTALL/bin:$PATH"export DENO_DIR=$HOME/.deno' >> ~/.bash_profile

source ~/.bash_profile

测试

deno --version

降级

deno upgrade

或降级到特定版本,例如v1.3.0

deno upgrade --version 1.3.0

平安机制

Deno 具备安全控制,默认状况下脚本不具备读写权限。如果脚本未受权,就读写文件系统或网络,会报错。

必须应用参数,显式关上权限才能够。

--allow-read:关上读权限,能够指定可读的目录,比方 --allow-read=/temp。--allow-write:关上写权限。--allow-net=google.com:容许网络通信,能够指定可申请的域,比方 --allow-net=google.com。--allow-env:容许读取环境变量。--allow-run:容许运行子过程。

要应用不稳固的新个性,须要这样:

--unstable

还有新加的--allow-ffi--allow-hrtime,个别用不到,这里不再赘述,请情参考官网文档。

运行

应用 deno run 来运行程序,当然,个别须要加上下面的平安机制。
如果你的程序什么权限都没应用,只是上面这种:

console.log('app start');

那么间接用 deno run main.ts 就能够了。
留神,咱们举荐应用 ts,但也能够运行 js。

如果须要读取文件,比方

const decoder = new TextDecoder("utf-8");
const data = await Deno.readFile("hello.txt");
console.log(decoder.decode(data));

天然得这样:deno run –allow-read main.ts

如果须要网络,比方开启一个 tcp:

/**
 * echo_server.ts
 */
import {copy} from "https://deno.land/std@0.106.0/io/util.ts";
const listener = Deno.listen({port: 8080});
console.log("listening on 0.0.0.0:8080");
for await (const conn of listener) {copy(conn, conn).finally(() => conn.close());
}

你必须:deno run –allow-net main.ts

如果懒得管理权限,则应用-Adeno run -A main.ts),那就后果自负了。

当然,也能够运行近程服务器上的文件,比方官网的hello world

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

更新模块

默认代码中援用中的 ts 或 js 文件,会缓存到本地。如果你援用的文件 url 中没有带版本号,那么就是最新版的代码。这时,你想要更新代码,那么你须要这样操作:

deno cache --reload my_module.ts

更新线上与本地文件:

deno cache --reload=https://deno.land/std@0.106.0 my_module.ts

更新线上某个文件与某文件夹:

deno cache --reload=https://deno.land/std@0.106.0/fs/copy.ts,https://deno.land/std@0.106

锁定文件校验

举荐建一个 deps.ts 文件,来治理内部援用的文件。其实就是起到相似 package-lock.json 的作用,治理援用的文件与版本。

// deps.ts
export {xyz} from "https://unpkg.com/xyz-lib@v0.9.0/lib.ts";

而后用 deno cache 生成一个lock.json 文件:

# Create/update the lock file "lock.json".
deno cache --lock=lock.json --lock-write deps.ts

另一台机器克隆下代码,运行后:

# Download the project's dependencies into the machine's cache, integrity
# checking each resource.
deno cache --reload --lock=lock.json deps.ts

# Done! You can proceed safely.
deno test --allow-read src

导入地图

能够应用带有--import-map=<FILE>CLI 标记的导入映射,这样开发时应用体验相似于 Node.js 了。

不过如果是公布给他人应用的模块,不倡议用它,因为这须要他人也把你的 map 文件复制过去。

例:import_map.json

{   
  "imports": {"fmt/": "https://deno.land/std@0.106.0/fmt/"}
}

main.ts:

import {red} from "fmt/colors.ts"; 
console.log(red("hello world"));

而后:

deno run --import-map=import_map.json main.ts

来自 CDN 的包

目前,举荐将共享的代码公布在官网搭建的平台 https://deno.land/x/ 上,它的公布也很简略,在你的 github 仓库里配置一个 web hook,就能够了。

具体详见疏导,非常简单,这里不再赘述。

esm.sh

https://esm.sh/ 是个转换 npm 包为 Deno 应用的线上地址,它应用 esbuild 进行转换,速度很快。比方咱们要应用 React,能够这样间接应用:

import React from "https://esm.sh/react";

export default class A extends React.Component {render() {return <div></div>;}
}

当然也反对版本号:

import React from "https://esm.sh/react@17.0.2";

Skypack

https://www.skypack.dev/ 也是相似:

import React from "https://cdn.skypack.dev/react";

export default class A extends React.Component {render() {return <div></div>;}
}

装置脚本

应用 deno install 能够不便地装置一个脚本,相似于 npm i -g xxx 模块。

$ deno install --allow-net --allow-read https://deno.land/std@0.106.0/http/file_server.ts
[1/1] Compiling https://deno.land/std@0.106.0/http/file_server.ts

✅ Successfully installed file_server.
/Users/deno/.deno/bin/file_server

打包

打包为 js

能够打包线上或本地文件为一个 js 文件:

deno bundle https://deno.land/std@0.106.0/examples/colors.ts colors.bundle.js
deno bundle main.ts main.js

打包后的文件是能够运行的:

deno run -A bundle.js

因为导出的是个 ES 模块化的代码,甚至能够放到浏览器里应用:

<script type="module" src="website.bundle.js"></script>

或者在另一个 js 里援用:

<script type="module">
  import * as website from "website.bundle.js";
</script>

编译为可执行文件

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

也能够通过增加 --targetCLI 标记为其余平台编译二进制文件,比方在 linux 服务器上打包一个 exe 文件供 windows 应用。详见此处。

生成文档

deno doc后跟一个或多个源文件的列表将打印每个模块导出成员的 JSDoc 文档。
例如,给定一个 add.ts 蕴含以下内容的文件:

/**
 * Adds x and y.
 * @param {number} x
 * @param {number} y
 * @returns {number} Sum of x and y
 */
export function add(x: number, y: number): number {return x + y;}

运行 deno doc 命令,将函数的 JSDoc 正文打印到 stdout。

应用 --json 标记以 JSON 格局输入文档。这种 JSON 格局由 deno doc 网站应用,用于生成模块文档。

代码格调

格式化

应用 deno fmt。它会将代码格式化为官网举荐的样子,其实起到的相似eslint --fix 的成果。

# format all JS/TS files in the current directory and subdirectories
deno fmt
# format specific files
deno fmt myfile1.ts myfile2.ts
# check if all the JS/TS files in the current directory and subdirectories are formatted
deno fmt --check
# format stdin and write to stdout
cat file.ts | deno fmt -

代码校验

应用 deno lint。有了它,就不用再应用eslint 了。

# lint all JS/TS files in the current directory and subdirectories
deno lint
# lint specific files
deno lint myfile1.ts myfile2.ts
# print result as JSON
deno lint --json
# read from stdin
cat file.ts | deno lint -

编码格调指南

参见官网指南,举几条对咱们有用的:

  • 应用 TypeScript 而不是 JavaScript
  • 在文件名中应用下划线,而不是破折号
  • 不要应用文件名 index.ts/ index.js。入口文件应用 mod.ts/mod.js
  • 导出的函数:最多 2 个参数,将其余的放入选项对象中
  • 顶级函数不应应用箭头语法。顶级函数应该应用 function 关键字。箭头语法应该仅限于闭包

调试

像 Node.js 一样,Deno 程序也能够在 chrome 浏览器中调试,具体就不介绍了。
次要说下 vscode 中调试。

须要在我的项目根目录下配置 .vscode/launch.json 文件,内容大略如下:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Deno: Run",
            "request": "launch",
            "type": "pwa-node",
            "program": "src/main.ts", // 具体文件门路
            "cwd": "${workspaceFolder}",
            "runtimeExecutable": "deno",
            "runtimeArgs": [
                "run",
                "--unstable",
                "--inspect",
                "--allow-all"
            ],
            "attachSimplePort": 9229
        }
    ]
}

如果你的入口文件有多个,写起来比拟麻烦,能够这样:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Deno",
      "type": "pwa-node",
      "request": "launch",
      "cwd": "${workspaceFolder}",
      "runtimeExecutable": "deno",
      "runtimeArgs": [
        "run",
        "--inspect-brk",
        "-A",
        "--config",
        "tsconfig.json",
        "--unstable",
        "${file}"
      ],
      "attachSimplePort": 9229
    }
  ]
}

详情能够参考这里。

各大支流库代替计划

Electron

咱们晓得,当初 Electron 是个优良的桌面端解决方案,咱们罕用的 vscode 就是用它写的,基于 Node.js + Chromium 的 Electron 来依靠 Web 技术栈创立桌面应用程序。那么咱们能够在 Deno 下应用 Electron 吗?或者还有其它更多抉择吗?
答案是现在的 Electron 还远远不能运行在 Deno 上,咱们必须寻找其它的解决方案。自从 Deno 抉择用 Rust 语言构建其内核后,咱们能够应用 Rust 生态上的 Web View @Bosop/web-view 来在 Deno 上运行桌面利用。
于是 @eliassjogreen/deno_webview 应运而生。

import {Webview} from "https://deno.land/x/webview/mod.ts";

const html = `
  <html>
  <body>
    <h1>Hello from deno v${Deno.version.deno}</h1>
  </body>
  </html>
`;

const webview = new Webview({ url: `data:text/html,${encodeURIComponent(html)}` },
);
await webview.run();

pm2

咱们应用 pm2 来守护 Node.js 过程,能够让代码运行在后盾。事实上,它也能够运行 Node.js 之外的的脚本语言。
所以,如果要运行 deno,须要创立一个 app.sh 文件:

#!/bin/bash
deno run -A main.ts

再应用 pm2 启动:

pm2 start ./app.sh

Express / koa

这俩 Node.js 祖师爷级别的 web 框架,在 deno 里当然少不了,Express 不倡议应用,举荐用 koa 的高仿 oak 吧。

import {Application} from "https://deno.land/x/oak/mod.ts";
const app = new Application();
app.use((ctx) => {ctx.response.body = "Hello World!";});
await app.listen({port: 8000});

nestjs

nestjs 是一个用于构建高效、可扩大的 Node.js 服务器端应用程序的开发框架。它利用 JavaScript 的渐进加强的能力,应用并齐全反对 TypeScript(依然容许开发者应用纯 JavaScript 进行开发),并联合了 OOP(面向对象编程)、FP(函数式编程)和 FRP(函数响应式编程)。从它身上,咱们能看到 Spring 和 Angular 的影子。

咱们能够利用 nestjs 轻松构建一个 REST 服务,以 Controlller 的用法为例:

@Controller('cats')
export class CatsController {@Get()
  findAll(): string {return 'This action returns all cats';}
}

官网目前并没有打算做 deno 的版本,所以我基于 oak 实现了一个简版的oak_nest,包含 Controller、平安守卫和各类参数装璜器。感兴趣的同学能够试用,有问题随时能够找我。

罕用数据库

这里列举俩,其它在这里本人搜寻。

MongoDB

有 deno_mongo,有罕用的 API。当然,它没有 mongoose 那么丰盛,更简单的性能可能就须要你来本人造轮子了。
如果想应用 Schema,能够试用 deno_mongo_schema,它是基于 deno_mongo 的扩大。

Redis

有 deno-redis。

Nodemon

咱们应用 nodemon 来监控 Node.js 程序,能够主动重启服务,在开发阶段十分不便。
deno 中替代品为 denon,咱们须要保护一个 scripts.json,起到 Node.js 中 package.json 里 script 命令的作用。

一个简略的样例领会下:

# 应用 denon dev 启动服务
scripts:
  dev:
    cmd: "deno run example/main.ts"
    desc: "run example main.ts file"
    allow:
      - net
      - env
      - write
    unstable: false
    lock: lock.json
    log: info
    env:
      PORT: "1000"
  build:
    cmd: "deno bundle example/main.ts example/main.js"
    watch: false

测试工具

在 Node.js 里,咱们有 jest、Jasmine 等库,而 deno 能够应用官网测试 std 库测试:https://deno.land/std/testing。

import {assertStrictEq} from 'https://deno.land/std/testing/asserts.ts'
Deno.test('My first test', async () => {assertStrictEq(true, false)
})

运行测试:

➜  deno test

nvm

咱们应用 nvm 来治理 Node.js 版本,在 Deno 中有 dvm。

在 docker 中运行

Dockerhub 现已提供 Deno 的官网 Docker 镜像。

  • Alpine Linux: denoland/deno:alpine
  • Centos: denoland/deno:centos
  • Debian: denoland/deno:debian(默认)
  • Distroless: denoland/deno:distroless
  • Ubuntu: denoland/deno:ubuntu

要在 Docker 外部运行 Deno,咱们能够创立以下 Dockerfile

FROM denoland/deno:1.16.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"]

而后这样应用:

docker build -t app . && docker run -it -p 1993:1993 app

详见这里。

以下是一个咱们我的项目中的样例:

FROM denoland/deno:alpine-1.16.2

EXPOSE 3000

WORKDIR /app

# Prefer not to run as root.
RUN chown -R deno /app
RUN chmod 755 /app

ADD . .

ENV DENO_DIR=deno-dir

CMD deno run --allow-net --allow-env --allow-write --allow-read --config tsconfig.json --unstable mod.ts

这里设置了 DENO_DIR,起因是在后面CICD 的流水线中,曾经缓存了工程的依赖包在 deno-dir 目录下,在这里就不用再下载了。
最佳计划其实是运行打包后的 js 文件,这样服务的启动速度能快许多,遗憾的是官网 bundle 有个全局命名抵触的 bug 仍未修复。

据说 1.17.0 曾经修复了,但因为其它工具包的兼容问题,我还没有验证。

总结

本文次要是为了科普 Deno,介绍了它的劣势与毛病,装置应用与罕用 Node.js 库的代替计划。它相较于 Node.js,没有历史包袱,所以能够轻装上阵。

但想要代替 Node.js 成为前端人员的标配,就不是一年两年能够做到的事件了。除非 Deno 真的推出了 Node.js 无奈复制的弱小性能,那才有可能会扭转游戏规则。

Deno 当初正在致力做的是兼容 Node.js 的 API,升高后者开发者的迁徙难度,这样才有可能将 Node.js 长达十余年的生态继承过去。

尽管 Deno 没有达到石破天惊的成果,但仍是个优良的工具,我看好它。心愿大家都有趣味玩一玩。

参考:

  • 20 分钟入门 deno
  • Deno 手册【官网】
  • 从 Node 到 Deno:摸索各大支流库代替计划
  • 了不起的 Deno 入门与实战
退出移动版