共计 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 须要做哪些事件,现实的开发流程是什么样的。
- SDK 以 NPM 包的模式公布,给调用者应用
- SDK 次要提供一些封装办法,比方:网络申请、事件公布订阅零碎等
- SDK 的代码通常不依赖 DOM 接口,并且调用的宿主环境办法与 Deno 兼容
- 测试用例不须要在浏览器外面跑,应用 Deno 在命令行中自动化实现
- 如果能够最好能做到浏览器应用能够独立打包成 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.enable
为 true
。你的目录构造应该是这样的:
├── 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"]
}
留神指定 outDir
为 build
不便咱们将编译完的 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 再进行公布。
公布这里咱们的需要有两点:
- 能够将最终的代码包合成到一个文件中编译成 UMD,浏览器引入这个脚本能够通过全局变量
window.MySDK
拜访到 - 通过 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% 平安的