Deno 常见问题
如果 Deno 官网挂了怎么办?
如果 https://deno.land/
宕机怎么办?
我们写代码时候很多都是依赖官方的服务,比如
deno run --allow-read https://deno.land/std/examples/cat.ts /e/deno/axihe.com.md
依赖外部服务,这种方式在开发的时候非常方便,但在生产环境很脆弱,怎么搞呢?
Deno 给出的方案是:生产级软件总是应该打包所有依赖。
在 Deno 中,您应该将 $DENO_DIR
检入版本控制系统,并在运行时指定 $DENO_DIR
环境变量。
Deno 怎么保证 URL 引用的内容一致性?
就像上面一样,我们通过 URL 引用,那么还是有一种风险,就是远程的 URL 代码改变了,我们的项目可能就 BUG 频繁出现了。
可能出现的场景,就是今天我们写好的代码,测试通过了,但是下周另外一位同事把代码拉下来,因为 url 引入的内容改变了,导致项目起不来了。
这是非常容易出现的,Deno 怎么处理这件事情呢?
Deno 给的解释是,使用 --lock
命令行选项,通过一个锁文件 (lock file),您可以确保自己运行的是所期望的代码。
这个就类似 npm 的package.json
,npm 的锁文件参考 NPM package-lock.json
更多信息请看 Deno完整性检查与锁定文件
Deno 如何导入特定版本?
如果是官方的,只需在 URL 中指定版本,举个例子,这个 URL 指定了要运行的版本 https://unpkg.com/liltest@0.0.5/dist/liltest.js
。
结合之前提到的在生产中设置 $DENO_DIR
的方法,您可以完全指定要运行的代码,并且无需访问网络。
Tips: 这种方式还是挺 low 的,只有官方的,可信的才能这么搞。
所有文件都导入 URL 似乎很麻烦
如果其中一个 URL 链接到一个完全不同的库版本,该怎么办?
在大型项目中到处维护 URL 是否容易出错?
解决办法是在一个中心化的 deps.ts
中重新导出所依赖的外部库,它和 Node 的 package.json
具有相同的作用。
举个例子,您正在一个大型项目中使用一个断言库,您可以创建一个 deps.ts
文件来导出第三方代码,而不是到处导入 "https://deno.land/std/testing/asserts.ts"
。
export {
assert,
assertEquals,
assertStrContains,
} from "https://deno.land/std/testing/asserts.ts";
在这个项目中,您可以从 deps.ts
导入,避免对相同的 URL 产生过多引用。
import { assertEquals, runTests, test } from "./deps.ts";
这种设计避免了由包管理软件、集中的代码存储库和多余的文件格式所产生的大量复杂性。
标准库运行错误
标准库中的一些模块使用了不稳定的 Deno API。
不用 --unstable
命令行选项运行这些模块会产生一些 TypeScript 错误,表示 Deno
命名空间中不存在一些 API:
// main.ts
import { copy } from "https://deno.land/std@0.50.0/fs/copy.ts";
copy("log.txt", "log-old.txt");
$ deno run --allow-read --allow-write main.ts
Compile file:///dev/deno/main.ts
Download https://deno.land/std@0.50.0/fs/copy.ts
Download https://deno.land/std@0.50.0/fs/ensure_dir.ts
Download https://deno.land/std@0.50.0/fs/_util.ts
error: TS2339 [ERROR]: Property 'utime' does not exist on type 'typeof Deno'.
await Deno.utime(dest, statInfo.atime, statInfo.mtime);
~~~~~
at https://deno.land/std@0.50.0/fs/copy.ts:90:16
TS2339 [ERROR]: Property 'utimeSync' does not exist on type 'typeof Deno'.
Deno.utimeSync(dest, statInfo.atime, statInfo.mtime);
~~~~~~~~~
at https://deno.land/std@0.50.0/fs/copy.ts:101:10
解决方法是加上 --unstable
选项:
$ deno run --allow-read --allow-write --unstable main.ts
要确定哪些 API 是不稳定的,请查阅类型声明 lib.deno.unstable.d.ts
这个问题会慢慢来解决,毕竟 Deno 还是太年轻啊。
URL 引入的包每次执行都要下载吗?
答:只需要再执行一次就能明白,不需要每次下载。
运行
> deno run index.js
Download https://deno.land/std/fmt/colors.ts
Compile https://deno.land/std/fmt/colors.ts
hello world!
我们看到其有 Download
和 Compile
两个步骤。
当你第二次再运行的时候,就会发现并不会再有 Download
和 Compile
两个步骤了:
> deno run index.js
hello world!
Download 和 Compile 的文件在哪里呢?
我们发现,当前执行的目录,并没有 Download
和 Compile
文件,那文件放在哪里呢;
首先来看一下 deno --help
命令:
> deno --help
SUBCOMMANDS:
# ...
info Show info about cache or info related to source file
# ...
ENVIRONMENT VARIABLES:
DENO_DIR Set deno's base directory (defaults to $HOME/.deno)
deno info 命令展示了依赖关系,类似 package.json。
> deno info index.js
local: /Users/Administrator/Desktop/index.js
type: JavaScript
deps:
file:///Users/Administrator/Desktop/index.js
└── https://deno.land/std/fmt/colors.ts
DENO_DIR
则为实际的安装和编译目录,相当于 node_modules
,默认为 $HOME/.deno
(命令提示是这样的,但实际需要指定一下环境变量 export DENO_DIR=$HOME/.deno),我们看一下:
> tree $HOME/.deno
/Users/Administrator/.deno
├── deps
│ └── https
│ └── deno.land
│ ├── 3574883d8acbaf00e28990ec8e83d71084c4c668c1dc7794be25208c60cfc935
│ └── 3574883d8acbaf00e28990ec8e83d71084c4c668c1dc7794be25208c60cfc935.metadata.json
└── gen
└── https
└── deno.land
└── std
└── fmt
├── colors.ts.js
├── colors.ts.js.map
└── colors.ts.meta
8 directories, 5 files
没网络了怎么办?
有些场景是将本地写好的代码部署到没有网络的服务器,那么当执行 deno run xxx
时,就是提示 error sending request。
应对方法:将上面的缓存目录内容,直接拷贝到服务器并指定环境变量到其目录即可。
依赖代码更新了怎么办?
当依赖模块更新时,我们可以通过 --reload
进行更新缓存
> deno run --reload index.js
我们还可以通过白名单的方式,只更新部分依赖。例如:
> deno run --reload=https://deno.land index.js
仅缓存依赖,不执行代码有办法吗?
解:有的,我们可以通过 deno cache index.js
进行依赖缓存。
多版本怎么处理?
解:暂时没有好的解决方案,只能通过 git tag
的方式区分版本。
标准模块 与 node API 兼容
我们通过第 1 点可以看到,其实 deno 的 API 相对于 node 其实是少一些的,通过其文件大小也能看出来:
> ll /usr/local/bin/node /Users/Administrator/.local/bin/deno
-rwxr-xr-x 1 42M /Users/Administrator/.local/bin/deno
-rwxr-xr-x 1 70M /usr/local/bin/node
那这些少的 API 只能自己写或者求助于社区吗?
deno 对于自身相对于 node 少的和社区中常用的功能,提供了标准模块,其特点是不依赖非标准模块的内容,达到社区内的模块引用最后都收敛于标准模块的效果。
例如:
// 类似 node 中 chalk 包
import { bgRed, white } from "https://deno.land/std/fmt/colors.ts";
// 类似 node 中的 uuid 包
import { v4 } from "https://deno.land/std/uuid/mod.ts";
同时为了对 node 用户友好,提供了 node API 的兼容
import * as path from "https://deno.land/std/node/path.ts";
import * as fs from "https://deno.land/std/node/fs.ts";
console.log(path.resolve('./', './test'))
所以,大家在为 deno 社区做贡献的时候,首先要看一下标准模块有没有提供类似的功能,如果已经提供了可以进行引用。
异步操作
根据 ry 自己是说法,在设计 node 是有人提议 Promise 处理回调,但是他没听,用他自己的话说就是愚蠢的拒绝了。
node 用回调的方式处理异步操作、deno 则选择用 Promise
// node 方式
const fs = require("fs");
fs.readFile("./data.txt", (err, data) => {
if (err) throw err;
console.log(data);
});
另外 deno 支持 top-level-await,所以以上读取文件的代码可以为:
// deno 方式
const data = await Deno.readFile("./data.txt");
console.log(data);
node 关于这方面也在一直改进,例如社区上很多 promisify 解决方案,通过包裹一层函数,实现目的。例如:
// node API promisify
const { promisify } = require("es6-promisify");
const fs = require("fs");
// 没有 top-level-await,只能包一层
async function main() {
const readFile = promisify(fs.readFile);
const data = await readFile("./data.txt");
console.log(data);
}
main();
单文件分发
我们知道 npm 包必须有 package.json
文件,里面不仅需要指明 main
或 module
或 browser
等字段来标明入口文件,还需要指明 name 、license
、description
等字段来说明这个包。
ry 觉得这些字段扰乱了开发者的视听,所以在 deno 中,其模块不需要任何配置文件,直接是 import url 的形式。
去中心化仓库
对于 www.npmjs.com 我们肯定都不陌生,它是推动 node 蓬勃发展的重要支点。
但作者认为它是中心化仓库,违背了互联网去中心化原则。
所以 deno 并没有一个像 npmjs.com 的仓库,通过 import url 的方式将互联网任何一处的代码都可以引用。
PS:deno 其实是有个基于 GitHub 的第三方模块集合。
注意:Npm 也是支持任意合法 Git 仓库引用的。并不能因为 npmjs 官方的强大,就说它是中心的。
去开发依赖
我们在写一个 node 库或者工具时,开发依赖是少不了的,
例如 babel 做转化和打包、jest 做测试、prettier 做代码格式化、eslint 做代码格式校检、gulp 或者 webpack 做构建等等,
让我们在开发前就搞得筋疲力尽。
- deno 通过内置了一些工具,解决上述问题。
- deno bundle:打包命令,用来替换 babel、gulp 一类工具:例如:deno bundle ./mod.ts;
- deno fmt:格式化命令,用来替换 prettier 一类工具,例如:deno fmt ./mod.ts;
- deno test:运行测试代码,用来替换 jest 一类工具,例如 deno test ./test.ts;
- deno lint:代码校检(暂未实现),用来替换 eslint 一类工具,例如:deno lint ./mod.ts。