在上一篇文章中我们学习了 Deno 的组成、Deno 的基础功能和核心功能、Deno 的官方库和第三方库以及 Deno 的测试。
如果你对 Deno 还不够熟悉,推荐你去读我的上一篇文章:一文读懂 Deno。
这一篇我们将会更进一步,学习 Deno 的 打包、编译、安装、Web API、REST API、调试和部署。最后会使用 fresh 框架开发一个 TodoList 应用,并部署上线。
打包、编译和安装
Deno 具有一些内置函数,这些函数能够帮助我们将多个文件分组到单个包或者可执行脚本中。这些功能在我们的开发过程中和准备部署到生产环境时非常有用。
打包
打包是一种非常典型的 Web 开发优化技术,可以减少 JavaScript、CSS 以及其他资源的请求数量和请求体积。无论是 JavaScript、CSS,还是其他资源,打包过程中都会尽可能尝试将多个文件合并到一个文件中。
Deno 通过 bundle 命令进行打包。
deno bundle [OPTIONS] <source_file> [<output>]
1.5 版本以后,添加了 Tree Shaking 功能,可以帮我们删除没有使用到的代码,最终生成只包含我们实际使用的代码,以此来减小包的体积。
我们使用官方库中的 cat.ts 进行简单的打包测试。
源代码如下:
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. import { copy } from "../streams/conversion.ts"; const filenames = Deno.args; for (const filename of filenames) { const file = await Deno.open(filename); await copy(file, Deno.stdout); file.close(); }
其中它导入了 ../streams/conversion.ts 文件。
我们使用 bundle 命令进行打包后,会将这个文件以及这个文件所依赖的文件全部打包到最终生成的文件中。
我们运行以下命令进行测试:
deno bundle https://deno.land/std@0.123.0/examples/cat.ts bundle-cat-example.js
它会生成一个 bundle-cat-example.js 文件,我们可以把这个文件当作常规的脚本文件来运行。
运行它的命令是 deno run。
deno run bundle-cat-example.js test.txt
我们打包后的文件除了可以独立运行以外,还可以在浏览器中导入或者被其他模块导入。
在浏览器中导入:
<script type="module" src="bundle.js"></script>
在其他模块中导入:
import * as lib from 'bundle.js'
编译
我们使用 compile 命令将脚本编译为独立的可执行文件。
deno compile [--output <OUT>] <SRC>
在编译时,我们需要设置脚本运行时所需要的权限 flag。
下面是一个脚本代码,作用是读取相同目录下的 README.md 文件并将它的内容打印到控制台。
try { const decoder = new TextDecoder("utf-8"); const data = await Deno.readFile("README.md"); console.log(decoder.decode(data)); } catch (e) { console.error("An error occurrred while reading the file: ", e); }
我们运行以下命令对它进行编译:
deno compile --allow-read main.ts
我们没有使用 --output 指定编译后的文件名,Deno 会创建一个叫做 usercode 的可执行文件。
现在我们就可以执行这个 usercode 文件了。
./usercode
我们可以把这个可执行文件发给任何人,他不需要安装 Deno 环境就可以直接运行这个可执行文件。
这看上去非常灵活,但这种灵活性是有代价的,因为它会把 Deno 也集成到可执行文件中,即使对于这么小的文件来说,文件体积依旧超过了 50 M。
安装
Deno 还提供了一种安装脚本的方法。它可以创建一个可执行的 shell 脚本。
安装脚本的命令是 install,下面是示例。
deno install -n copyText --root ./ --allow-read main.ts
我们来解释上面的命令。
我们使用 -n 或者 --name 来指定可执行文件的名字。
使用 --root 指定根路径,如果不指定,它会使用环境变量 DENO_INSTALL_ROOT 作为默认值。
install 命令会创建一个 bin 文件夹,其中包含一个 copyText 的文件。
我们可以进入到 bin 文件夹中,查看 copyText 文件。
运行 copyText 文件也非常简单,因为它是 shell 文件。
./copyText
对比
我们已经了解了 Deno 的打包(bundle)、编译(build)和安装(install),下面是它们之间的区别。
命令 | 生成的文件 | Deno 嵌入式 | 独立运行 |
bundle | JS | 否 | 是 |
build | 二进制 | 是 | 是 |
install | shell | 否 | 否 |
调试
调试是一项非常重要的功能。
对 Deno 程序进行调试,需要在运行命令行中添加 --inspect 或者 --inspect-brk。这和 Node.js 是一致的。
- inspect 允许我们在任何时间点设置调试器。
- inspect-brk 会等待调试器,并暂停第一行代码的执行。
如果我们的脚本内容很短,那么使用 inspect-brk 是更合适的选择,因为它可以让我们有时间暂停并继续执行代码,否则程序可能会在我们开始调试之前就执行完了。
Deno 支持 V8 的 Inspector 协议,它提供了丰富的调试功能,我们可以使用任何 Chromium 开发工具(不仅仅是 Chrome)或任何支持该协议的 IDE(比如 VSCode)来调试我们的脚本。
使用 VSCode 进行调试
首先要为 VSCode 安装 Deno 扩展。
扩展下载地址:marketplace.visualstudio.com/items?itemN…
接下来要修改 Deno 的设置,建议设置工作区配置,这样不会影响其它项目。
Mac 系统使用 Cmd+Shift+p 来打开 setting.json,Windows 使用 Ctrl 替换 Cmd。
添加下面的配置。
{ "deno.enable": true, "deno.lint": true, "deno.unstable": true }
这样 Deno 的关键字将不再显示错误。
接下来我们要创建启动配置文件。
创建启动配置文件有两种方式,第一种是手动创建 .vscode/launch.json 文件。第二种是利用 VSCode 扩展来创建。这里讲一下第二种情况。
选择左侧菜单的 Run and Debug 菜单,选择 create a launch.json file。
在弹出的选择框中选择 Deno。
这样就创建好了 .vscode/launch.json 文件,下面是创建出来的默认内容。
{ // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "request": "launch", "name": "Launch Program", "type": "pwa-node", "program": "${workspaceFolder}/main.ts", "cwd": "${workspaceFolder}", "runtimeExecutable": "/opt/homebrew/bin/deno", "runtimeArgs": [ "run", "--inspect", "--allow-all" ], "attachSimplePort": 9229 } ] }
其中 runtimeArgs 可以在运行时传递一些参数给 Deno,默认的参数有:
- --inspect:允许在代码任意位置中设置断点。
- --allow-all:允许所有权限。
现在就可以按下 F5 进行调试了。
使用 Chrome 进行调试
简单起见,我们可以使用 Deno 标准库中的聊天服务器程序。
运行以下命令,启动聊天服务器。
deno run --inspect-brk --allow-net https://deno.land/std@0.125.0/examples/chat/server.ts
在浏览器中输入:chrome://inspect。可以看到聊天服务器。
点击 inspect,弹出 DevTools。
现在就可以进行调试了。
不过现在只是显示了一个文件,我们可以按住 cmd+p/ctrl+p 来现实所有的文件。
Web API
Deno 提供了一些 Web API,帮助我们处理一些常见的任务。
Base64 编解码
atob 和 btoa 函数可以对 base64 格式字符串进行编码/解码。
const encoded = btoa('Hello,Deno!') console.log('编码: ', encoded) const decoded = atob(encoded) console.log('解码: ', decoded)
二进制编解码
和 Base64 的编解码类似,Deno 提供了 TextEncoder 和 TextDecoder 两个 API 来实现二进制的编解码。
const stringToEncode = "Hello Deno!"; const textEncoder = new TextEncoder(); const encodedBytes = textEncoder.encode(stringToEncode); console.log("编码: ", encodedBytes); const textDecoder = new TextDecoder(); const plainText = textDecoder.decode(encodedBytes); console.log("解码: ", plainText);
密码学加密
Deno 还实现了 Web Cryptography API,它是标准的 JS API,用于加密和解密。
const uuid = crypto.randomUUID(); console.log('UUID: ', uuid); console.log('\n---------------------------------------------------------------') const bytes = await crypto.getRandomValues(new Uint8Array(16)); console.log('随机整数数组: ', bytes); console.log('\n---------------------------------------------------------------'); const privateKey = await crypto.subtle.generateKey( { name: "HMAC", "hash": "SHA-256" }, // 算法 true, // 是否可提取 ["sign", "verify"] // 用途 ); console.log('密钥: ', privateKey); const exportedKey = await crypto.subtle.exportKey( "raw", privateKey ); // 将密钥导出为 ArrayBuffer const exportedKeyBuffer = new Uint8Array(exportedKey); const keyAsString = String.fromCharCode.apply(null, exportedKeyBuffer as any); const exportedBase64 = btoa(keyAsString); const pem = ` -----BEGIN PRIVATE KEY----- ${exportedBase64} -----END PRIVATE KEY-----`; console.log('导出的 PEM 密钥: ', pem);
性能评估
在 Web 中,我们有很多种方式来测试完成某项个函数或某项功能所需要的时间。但最精准、最实用的莫过于 Performance API。
performance.now 可以获取自程序执行以来到现在的时间,单位是毫秒。
还可以通过 performance.mark 和 performance.measure 来使用标记的方式创建时间戳,从而更轻松的进行性能评估。
const tartgetSite = "https://meta.com"; const start = performance.now(); await fetch(tartgetSite); const end = performance.now(); console.log(`访问网址: ${tartgetSite} 耗费时间: ${end - start} ms`); // ---------------------------------------------------------------------------- // 创建一个标记 const task1 = performance.mark('task1'); // 执行一些任务 for(let i=0; i<10000000; i++){}; // 创建第二个标记 const task2 = performance.mark('task2'); // 计算两个标记之间的差距 const measure = performance.measure('Task_Measure', 'task1', 'task2'); const res = performance.getEntriesByName("Task_Measure", "measure"); console.log('性能指标:', res)
深度克隆
在不使用第三方库的情况下,JavaScript 中最常见的深度克隆方式是使用 JSON API。
JSON.parse(JSON.stringify(object))
它很简单有效。但是它也不是完美的,当我们的对象中具有正则表达式、函数、日期、Map、Set 和 Blob 等复杂类型时,它会丢失数据。
Deno 使用了和 Web API 一致的 Structured Clone API,在最新版本的 Chrome 浏览器中已经可以使用。通过这种方式进行深度克隆不会丢失任何数据。
const user = { name: "章三", age: 45, codes: new Map().set('code', 1), }; console.log('原始对象: ', user); const jsonClone = JSON.parse(JSON.stringify(user)); // 使用 JSON API 会丢失 codes console.log('\nJSON Clone: ', jsonClone); const deepClone = structuredClone(user); console.log('\ndeep Clone: ', deepClone); console.log('\n比较: ', user === deepClone); deepClone.age = 18; console.log('\nuser.age: ', user.age); console.log('\nstructuredClone.age: ', deepClone.age);