关于deno:从实际案例讲-Deno-的应用场景

8次阅读

共计 12659 个字符,预计需要花费 32 分钟才能阅读完成。

此篇文章实际上就是《前端开发的瓶颈与将来》的番外篇。次要想从实用的角度给大家介绍下 Deno 在咱们我的项目中的利用案例,现阶段咱们只关注利用层面的问题,不波及过于底层的常识。

简介

咱们从它的官网介绍外面能够看进去加粗的几个单词:secure, JavaScript, TypeScript。简略译过去就是:

一个 JavaScript 和 TypeScript 的平安运行时

那么问题来了,啥叫运行时(runtime)?能够简略的了解成能够执行代码的一个货色。那么 Deno 就是一个能够执行 JavaScript 和 TypeScript 的货色,浏览器就是一个只能执行 JavaScript 的运行时。

个性

  • 默认是 平安的 ,这意味着初始的状况下你是 不能够 拜访网络、文件系统、环境变量的。
  • 开箱即用的 TypeScript 反对,就是说你能够间接应用 Deno 运行 TypeScript 而 不须要 应用 tsc 编译
  • Deno 的构建版只有一个可执行文件,那么你能够间接下载这个可执行文件到本地执行,而 不须要 编译、装置的操作
  • 内置了一些工具集,比方:依赖查看器、代码格式化。咱们用到的测试框架竟然没有被重点提起
  • 一系列的通过代码 review 的内置模块,这示意当你应用 Deno 的时候,一些罕用的工具办法都内置了,不须要再增加三方依赖
  • 局部浏览器个性兼容,这个并不是官网宣传的个性,然而我认为是很重要的一点。这个我意味着如果设计正当,你的代码即能够跑在 Deno 外面,也能够在浏览器外面。

装置

Mac/Linux 下命令行执行:

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

也能够去 Deno 的官网代码仓库下载对应平台的源(可执行)文件,而后将它放到你的环境变量外面间接执行。如果装置胜利,在命令行外面输出:deno --help 会有如下输入:

➜  ~ deno --help
deno 1.3.0
A secure JavaScript and TypeScript runtime

Docs: https://deno.land/manual
Modules: https://deno.land/std/ https://deno.land/x/
Bugs: https://github.com/denoland/deno/issues
...

当前如果想降级能够应用内置命令 deno upgrade 来主动降级 Deno 版本,相当不便了。

Deno 内置命令

Deno 内置了丰盛的命令,用来满足咱们日常的需要。咱们简略介绍几个:

deno run

间接执行 JS/TS 代码。代码能够是本地的,也能够是网络上任意的可拜访地址(返回 JS 或者 TS)。咱们应用官网的示例来看看成果如何:

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

如果执行胜利就会返回上面的信息:

➜  ~ deno run https://deno.land/std/examples/welcome.ts
Download https://deno.land/std/examples/welcome.ts
Warning Implicitly using latest version (0.65.0) for https://deno.land/std/examples/welcome.ts
Download https://deno.land/std@0.65.0/examples/welcome.ts
Check https://deno.land/std@0.65.0/examples/welcome.ts
Welcome to Deno ????

能够看到这段命令做了两个事件:1. 下载近程文件 2. 执行外面的代码。咱们能够通过命令查看这个近程文件外面内容到底是啥:

➜  ~ curl https://deno.land/std@0.65.0/examples/welcome.ts
console.log("Welcome to Deno ????");

不过须要留神的是下面的近程文件外面没有 显示的 指定版本号,理论下载 std 中的依赖的时候会默认应用最新版,即:std@0.65.0,咱们能够应用 curl 命令查看到源文件是 302 重定向到带版本号的地址的:

➜  ~ curl -i https://deno.land/std/examples/welcome.ts
HTTP/2 302 
date: Fri, 14 Aug 2020 01:53:06 GMT
content-length: 0
set-cookie: __cfduid=d3e9dfbd32731defde31eba271f19933b1597369985; expires=Sun, 13-Sep-20 01:53:05 GMT; path=/; domain=.deno.land; HttpOnly; SameSite=Lax; Secure
location: /std@0.65.0/examples/welcome.ts
x-deno-warning: Implicitly using latest version (0.65.0) for https://deno.land/std/examples/welcome.ts
cf-request-id: 048c44c2dc000019dd710cc200000001
expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
server: cloudflare
cf-ray: 5c270a4afd5719dd-SIN

header 头中的 location 就是理论文件的下载地址:

location: /std@0.65.0/examples/welcome.ts

这就波及到一个问题:理论应用的时候到底应不应该手动增加版本号?一般来说如果是生产环境的我的项目援用肯定要是带版本号的,像这种示例代码外面就不须要了。

下面说到 Deno 也能够执行本地的,那咱们也试一试,写个本地文件,而后 运行它:

➜  ~ echo 'console.log("Welcome to Deno <from local>");' > welecome_local.ts
➜  ~ ls welecome_local.ts 
welecome_local.ts
➜  ~ deno run welecome_local.ts 
Check file:///Users/zhouqili/welecome_local.ts
Welcome to Deno <from local>

能够看到输入了咱们想要的后果。

这个例子太简略了,再来个简单点的吧,用 Deno 实现一个 Http 服务器。咱们应用官网示例中的代码:

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

保留为 test_serve.ts,而后应用 deno run 运行它,你会发现有报错信息:

➜  ~ deno run test_serve.ts 
Download https://deno.land/std@0.65.0/http/server.ts
Download https://deno.land/std@0.65.0/encoding/utf8.ts
Download https://deno.land/std@0.65.0/io/bufio.ts
Download https://deno.land/std@0.65.0/_util/assert.ts
Download https://deno.land/std@0.65.0/async/mod.ts
Download https://deno.land/std@0.65.0/http/_io.ts
Download https://deno.land/std@0.65.0/async/deferred.ts
Download https://deno.land/std@0.65.0/async/delay.ts
Download https://deno.land/std@0.65.0/async/mux_async_iterator.ts
Download https://deno.land/std@0.65.0/async/pool.ts
Download https://deno.land/std@0.65.0/textproto/mod.ts
Download https://deno.land/std@0.65.0/http/http_status.ts
Download https://deno.land/std@0.65.0/bytes/mod.ts
Check file:///Users/zhouqili/test_serve.ts
error: Uncaught PermissionDenied: network access to "0.0.0.0:8000", run again with the --allow-net flag
    at unwrapResponse (rt/10_dispatch_json.js:24:13)
    at sendSync (rt/10_dispatch_json.js:51:12)
    at opListen (rt/30_net.js:33:12)
    at Object.listen (rt/30_net.js:204:17)
    at serve (server.ts:287:25)
    at test_serve.ts:2:11

PermissionDenied 意思是你没有网络拜访的权限,能够应用 --allow-net 的标识来容许网络拜访。这就是文章结尾个性外面提到的默认平安。

默认平安就是说被 Deno 执行的代码会默认被放进一个沙箱中执行,代码应用到的 API 接口都受制于 Deno 的宿主环境,Deno 当然是有网络拜访、文件系统等能力的。然而这些零碎级别的拜访须要 deno 命令的 执行者 受权。

这个权限管制很多人感觉没必要,因为当咱们运行代码时提醒了受限,咱们必定手动增加上容许而后再执行嘛。然而区别是 Deno 把这个受权交给了执行者,益处就是如果执行的代码是第三方的,那么执行者就能够被动回绝一些危险性很高的操作。

比方咱们装置一些命令行工具,而个别命令行工具都是不须要网络的,咱们就能够不给它网络拜访的权限。从而防止了程序偷偷地上传 / 下载文件。

deno eval

执行一段 JS/TS 字符串代码。这个和 JavaScript 中的 eval 函数有点相似。

➜  ~ deno eval "console.log('hello from eval')"
hello from eval

deno install

装置一个 deno 脚本,通常用来装置一个命令行工具。举个例子,在之前的 Deno 版本中有一个命令特地好用:deno xeval 能够按行执行 eval 命令,相似于 Linux 中的 xargs 命令。起初这个内置命令被移除了,然而 deno 的开发人员编写了一个 deno 脚本,咱们能够通过 install 命令装置它。

➜  ~ deno install -n xeval https://deno.land/std@0.65.0/examples/xeval.ts
Download https://deno.land/std@0.65.0/examples/xeval.ts
Download https://deno.land/std@0.65.0/flags/mod.ts
Download https://deno.land/std@0.65.0/io/bufio.ts
Download https://deno.land/std@0.65.0/bytes/mod.ts
Download https://deno.land/std@0.65.0/_util/assert.ts
Check https://deno.land/std@0.65.0/examples/xeval.ts
✅ Successfully installed xeval
/Users/zhouqili/.deno/bin/xeval
➜  ~ xeval
xeval

Run a script for each new-line or otherwise delimited chunk of standard input.

Print all the usernames in /etc/passwd:
  cat /etc/passwd | deno run -A https://deno.land/std/examples/xeval.ts "a = $.split(':'); if (a) console.log(a[0])"

A complicated way to print the current git branch:
  git branch | deno run -A https://deno.land/std/examples/xeval.ts -I 'line' "if (line.startsWith('*')) console.log(line.slice(2))"

Demonstrates breaking the input up by space delimiter instead of by lines:
  cat LICENSE | deno run -A https://deno.land/std/examples/xeval.ts -d """if ($ === 'MIT') console.log('MIT licensed')",

USAGE:
  deno run -A https://deno.land/std/examples/xeval.ts [OPTIONS] <code>
OPTIONS:
  -d, --delim <delim>       Set delimiter, defaults to newline
  -I, --replvar <replvar>   Set variable name to be used in eval, defaults to $
ARGS:
  <code>
[]

-n xeval 示意全局装置的命令行名称,装置完当前你就能够应用 xeval 了。

举个例子,咱们应用 xeval 过滤日志文件,仅仅展现 WARN 类型的行:

➜  ~ cat catalina.out | xeval "if ($.includes('WARN')) console.log($.substring(0, 40)+'...')"
2020-08-12 13:37:39.020  WARN 202 --- [I...
2020-08-12 13:37:39.020  WARN 202 --- [I...
2020-08-12 13:37:39.019  WARN 202 --- [I...
2020-08-12 13:34:42.822  WARN 202 --- [o...
2020-08-12 13:34:42.822  WARN 202 --- [o...
2020-08-12 13:34:42.814  WARN 202 --- [o...
2020-08-12 13:34:42.805  WARN 202 --- [o...

$ 美元符示意以后行,程序会主动按行读取让执行 xeval 命令前面的 JS 代码。

catalina.out 是我本地的一个文本日志文件。你可能会感觉这样挺麻烦的,间接 | grep WARN 不香嘛?然而 xeval 的可编程性就高很多了。

deno test

deno 内置了一个繁难的测试框架,能够满足咱们日常的单元测试需要。咱们写一个简略的测试用例试试,新建一个文件 test_case.ts,保留上面的内容:

import {assertEquals} from "https://deno.land/std/testing/asserts.ts";

Deno.test("1 + 1 在任何状况下都不等于 3", () => {assertEquals(1 + 1 == 3, false)
    assertEquals("1" + "1" == "3", false)
})

应用 test 命令跑这个测试用例:

➜ deno test test_case.ts
Check file:///Users/zhouqili/.deno.test.ts
running 1 tests
test 1 + 1 在任何状况下都不等于 3 ... ok (3ms)

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (3ms)

能够看到测试通过了。

还有其它很多好用的命令,然而在我并没用太多的理论应用教训,就不多介绍了。

实战

下面说了这么多基础知识,终于能够讲点理论利用场景了。咱们在本人的一个 SDK 我的项目中应用了 Deno 来做自动化单元测试的工作。整个流程走下来还是挺晦涩的。代码就不放进去了,我只简略的阐明下这个 SDK 须要做哪些事件,现实的开发流程是什么样的。

  1. SDK 以 NPM 包的模式公布,给调用者应用
  2. SDK 次要提供一些封装办法,比方:网络申请、事件公布订阅零碎等
  3. SDK 的代码通常不依赖 DOM 接口,并且调用的宿主环境办法与 Deno 兼容
  4. 测试用例不须要在浏览器外面跑,应用 Deno 在命令行中自动化实现
  5. 如果能够最好能做到浏览器应用能够独立打包成 UMD 模块,NPM 装置则能够间接援用 ES 版模块

如果你的场景和下面的吻合,那么就能够应用 Deno 来开发。实质上讲咱们开发的时候写的还是 TypeScript,只是须要咱们在公布 NPM 包的时候略微的进行一下解决即可。

咱们以实现一个 fetch 申请的封装办法为例来走通整个流程。

初始化一个 NPM 包

➜  ~ mkdir mysdk
➜  ~ cd mysdk 
➜  mysdk npm init -y

建设好文件夹目录,及次要文件:

➜  mysdk mkdir src tests
➜  mysdk touch src/index.ts
➜  mysdk touch src/request.ts 
➜  mysdk touch tests/request.test.ts

如果你应用的是 vscode 编辑器,能够装置好 deno 插件(denoland.vscode-deno),并且设置 deno.enabletrue。你的目录构造应该是这样的:

├── package.json
├── src
│   ├── index.ts
│   └── request.ts
└── tests
    └── request.test.ts

index.ts 为对外提供的导出 API。

初始化 tsconfig

应用 tsp –init 来初始化我的项目的 typescript 配置:

tsc --init

更新 tsconfig.json 为上面的配置:

{
  "compilerOptions": {
    "target": "ES5",
    "lib": ["es6", "dom", "es2017"],
    "declaration": true,
    "outDir": "./build",
    "strict": true,
    "allowUmdGlobalAccess": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*.ts"]
}

留神指定 outDirbuild 不便咱们将编译完的 JS 对立治理。

编写 request 办法

为了演示,这里就简略写下。request.ts 代码实现如下:

export async function request(url: string, options?: Partial<RequestInit>) {const response = await fetch(url, options)
    return await response.json()}

调用端关闭好 GET/POST 申请的快捷办法,并且从 index.ts 文件导出:

import {request} from "./request.ts";

export async function get(url: string, options?: Partial<RequestInit>) {
    return await request(url, {
        ...options,
        method: "GET"
    })
}

export async function post(url: string, data?: object) {
    return await request(url, {body: JSON.stringify(data),
        method: "POST"
    })
}

tests/request.test.ts 目录写上单元测试用例:

import {assertEquals} from "https://deno.land/std/testing/asserts.ts";
import {get, post} from "../src/index.ts";

Deno.test("request 失常返回 GET 申请", async () => {const data = await get("http://httpbin.org/get?foo=bar");
    assertEquals(data.args.foo, "bar")
})

Deno.test("request 失常返回 POST 申请", async () => {const data = await post("http://httpbin.org/post", {foo: "bar"});
    assertEquals(data.json.foo, "bar")
})

最初在命令行应用 deno test 命令跑测试用例。留神增加 --allow-net 参数来容许代码拜访网络:

➜  mysdk deno test --allow-net tests/request.test.ts
Check file:///Users/zhouqili/mysdk/.deno.test.ts
running 2 tests
test request 失常返回 GET 申请 ... ok (632ms)
test request 失常返回 POST 申请 ... ok (342ms)

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (974ms)

咱们能够看到测试都通过了,上面就能够安心的公布 NPM 包了。

须要留神一点 Deno 写 TypeScript 的时候严格要求导入的 文件门路 必须增加 .ts 后缀。然而 TS 语言并不需要显式的增加这个后缀,TS 认为引入(import)的是一个 模块 而不是文件。这一点 TS 做的比拟极其,tsc 要求你必须删除掉 .ts 后缀能力编译通过,这个我集体认为是十分不合理的。然而 Deno 有它的思考,因为没有严格的文件名后缀引起程序 BUG 我本人也遇到过。

公布 NPM 包

下面的几步都绝对晦涩,唯独到公布 NPM 包这一步就比拟麻烦。因为实质上讲 Deno 只是 TypeScript/JavaScript 的运行时,并不兼容 NPM 这种包管理工具。而且 NPM 是为 Node.JS 设计的,它也没有方法间接公布 TypeScript 的包,咱们只能把 TypeScript 编译成 JavaScript 再进行公布。

公布这里咱们的需要有两点:

  1. 能够将最终的代码包合成到一个文件中编译成 UMD,浏览器引入这个脚本能够通过全局变量 window.MySDK 拜访到
  2. 通过 NPM 装置的最好默认应用 ESModule

第二个简略,咱们间接应用 tsc 的命令就能够实现:

tsc -m esnext -t ES5 --outDir build/esm

这时你会发现我下面提到的问题,tsc 报错了:

➜  mysdk tsc -m esnext -t ES5 --outDir build/esm
src/index.ts:1:23 - error TS2691: An import path cannot end with a '.ts' extension. Consider importing './request' instead.

1 import {request} from "./request.ts";

说我不能应用 `.ts`!这就难堪了,deno 要求我必须增加,TS 又要求我不能增加。你到底想让人家怎么样嘛?而且还有一个问题,咱们当初实现的性能还很简略,引入的文件很少,能够手动批改下。然而当前性能多了怎么办?文件很多手动批改必定不是方法啊。切实不行还是算了,不必 Deno 了?其实嘛,解决办法还是有的,下面咱们不是介绍过 Deno 装置脚本性能了吗。咱们本人写个脚本放在 NPM Script 外面,每次编译公布前这个脚本主动把 `.ts` 去掉,公布完再主动改回来不就好了。于是乎我本人写了一个 Deno 脚本,专门用来给我的项目的文件批量增加或者删除援用门路下面的 `.ts` 后缀:源代码我就不全副贴出来了,简略讲就是用正则匹配出每个 ts 文件中的头部的 import 语句,按命令传入的参数去解决后缀就能够了。代码我放到了 gist 上,有趣味的能够钻研下:> https://gist.github.com/keelii/d95492873f35f96d95f3a169bee934c6

你能够应用上面的命令来装置并应用它:

deno install –allow-read –allow-write -f -n deno_ext https://gist.githubuserconten…


应用 deno_ext 命令即可:

~ deno_ext
✘ error with command.

Remove or restore [.ts] suffix from your import stmt in deno project.

Usage:
deno_ext remove <files>…
deno_ext restore <files>…
Examples:
deno_ext remove */.ts
deno_ext restore src/*.ts


工具通知你如何应用它,remove/restore 两个子命令 + 指标文件即可。咱们配合 `tsc` 能够实现公布时自动更新后缀,公布完还原回去,参考上面的 NPM script:

{
“scripts”: {

"proc:rm_ext": "deno_ext remove src/*.ts",
"proc:rs_ext": "deno_ext restore src/*.ts",
"tsc": "tsc -m esnext -t ES5 --outDir build/esm",
"build": "npm run proc:rm_ext && npm run tsc && npm run proc:rs_ext"

}
}


咱们应用 `npm run build` 命令就能够实现打包 ESModule 的性能:

➜ mysdk npm run build

mysdk@1.0.0 build /Users/zhouqili/mysdk
npm run proc:rm_ext && npm run tsc && npm run proc:rs_ext

mysdk@1.0.0 proc:rm_ext /Users/zhouqili/mysdk
deno_ext remove src/*.ts

Processing remove [/Users/zhouqili/mysdk/src/index.ts]
Processing remove [/Users/zhouqili/mysdk/src/request.ts]

mysdk@1.0.0 tsc /Users/zhouqili/mysdk
tsc -m esnext -t ES5 –outDir build/esm

mysdk@1.0.0 proc:rs_ext /Users/zhouqili/mysdk
deno_ext restore src/*.ts

Processing restore [/Users/zhouqili/mysdk/src/index.ts]
Processing restore [/Users/zhouqili/mysdk/src/request.ts]


最终打包进去的文件都在 build 目录外面:

build
└── esm

├── index.d.ts
├── index.js
├── request.d.ts
└── request.js

接下来咱们还须要将源代码打包成独自的一个 UMD 模块,并展出到全局变量 `window.MySDK` 下面。尽管 TypeScript 是反对编译到 UMD 格局模块的,然而它并不反对将源代码 bundle 到一个文件外面,也不能增加全局变量援用。因为实质上讲 TypeScript 是一个编译器,只负责把模块编译到反对的模块标准,自身没有 bundle 的能力。然而实际上当你抉择 --module=amd 时,TypeScript 其实是能够把文件打包 concat 到一个文件外面的。然而这个 concat 只是简略地把每个 AMD 模块拼装起来,并没有 rollup 这类的专门用来 bundle 模块的高级性能,比方 tree-shaking 什么的。所以想达到咱们指标还得引入模块 bundler 的工具,这里咱们应用 rollup 来实现。什么?你问我为啥不必 webpack?别问,问就是「人生苦短,学不动了」。rollup 咱们也就不搞什么配置文件了,越简略越好,间接装置 devDependencies 依赖:

npm i rollup -D


而后在 package.json 中应用 rollup 把 tsc 编译进去的 esm 模块再次 bundle 成 UMD 模块:

“scripts”: {

"rollup:umd": "./node_modules/.bin/rollup build/esm/index.js --file build/umd/index.bundle.js --format umd --name'MySDK'"

}


而后能够通过执行 `npm run rollup:umd` 来实现打包成 UMD 并将 API 绑定到全局变量  `MySDK` 下面。咱们能够间接将 `build/umd/index.bundle.js` 的代码复制进浏览器控制台执行,而后 看看 window 上有没有这个 `MySDK` 变量,不出意外的话,就会看到了。![mysdk-window-global-ns](https://vip1.loli.net/2020/08/14/NTwQ7oiAmzc6Hyg.png)

咱们在 `index.ts` 文件中 export 了两个 function:get/post 都有了。来试试看能不能运行起来

** 留神 **:有的浏览器可能还不反对 async/await,所以咱们应用了 Promise 来发送申请

![mysdk-get-request](https://vip1.loli.net/2020/08/14/ua4eb2CUyMvm6ki.png)

到此,咱们所有的需要都满足了,至多对于开发一个 SDK 级别的利用应该是没问题了。相干代码能够参考这里:https://github.com/keelii/mysdk

须要留神的几个问题:1. 咱们代码中能应用 fetch 的起因是 Deno 和浏览器都反对这个 API,对于浏览器反对 Deno 不反对的就没方法写测试用例了,比方:LocalStorage 目前 Deno 还不反对
2. 用 Deno 脚本移除 `.ts` 的后缀这个操作是比拟有危险的,如果你的我的项目比拟大,就不倡议间接这么解决了,这个脚本目前也只在咱们一个我的项目外面理论用到过。正则匹配换后缀这种做法总不是 100% 平安的

正文完
 0