On this page
模块与依赖
Deno 使用 ECMAScript 模块 作为其默认模块系统,以符合现代 JavaScript 标准并提升更高效和一致的开发体验。这是 JavaScript 模块的官方标准,允许更好的树摇优化,改进的工具集成,以及在不同环境中的原生支持。
通过采用 ECMAScript 模块,Deno 确保与不断发展的 JavaScript 生态系统的兼容性。对开发者而言,这意味着一个流畅且可预测的模块系统,避免了与遗留模块格式(如 CommonJS)相关的复杂性。
导入模块 Jump to heading
在这个例子中,add
函数是从本地的 calc.ts
模块导入的。
export function add(a: number, b: number): number {
return a + b;
}
// 导入与此文件相邻的 `calc.ts` 模块
import { add } from "./calc.ts";
console.log(add(1, 2)); // 3
你可以在包含 main.ts
和 calc.ts
的目录中运行这个例子,通过调用 deno run main.ts
。
使用 ECMAScript 模块时,本地导入规范必须始终包含完整的文件扩展名,不能省略。
// 错误:缺少文件扩展名
import { add } from "./calc";
// 正确:包含文件扩展名
import { add } from "./calc.ts";
导入属性 Jump to heading
Deno 支持 with { type: "json" }
导入属性语法以导入 JSON 文件:
import data from "./data.json" with { type: "json" };
console.log(data.property); // 访问 JSON 数据作为对象
这是当前 Deno 中唯一支持的导入属性类型。对 type: text
和 type: bytes
的支持正在考虑未来更新中,并且目前在等待
模块和谐提案。
导入第三方模块和库 Jump to heading
在 Deno 中处理第三方模块时,使用与本地代码相同的 import
语法。第三方模块通常是从远程注册中心导入,并以 jsr:
、npm:
或 https://
开头。
import { camelCase } from "jsr:@luca/cases@1.0.0";
import { say } from "npm:cowsay@1.6.0";
import { pascalCase } from "https://deno.land/x/case/mod.ts";
Deno 推荐使用 JSR,这是一个现代的 JavaScript 注册中心,用于第三方模块。在那里,你将会找到许多文档完善的 ES 模块供你的项目使用,包括 Deno 标准库。
你可以 在这里阅读更多关于 Deno 对 npm 包支持的内容。
管理第三方模块和库 Jump to heading
在多个文件中导入模块时,输入模块名称和完整版本说明符可能会变得繁琐。你可以通过在 deno.json
文件中使用 imports
字段来集中管理远程模块。我们将这个 imports
字段称为 导入映射(import map),它基于 导入映射标准。
{
"imports": {
"@luca/cases": "jsr:@luca/cases@^1.0.0",
"cowsay": "npm:cowsay@^1.6.0",
"cases": "https://deno.land/x/case/mod.ts"
}
}
使用重新映射的说明符,代码看起来更加简洁:
import { camelCase } from "@luca/cases";
import { say } from "cowsay";
import { pascalCase } from "cases";
重新映射名称可以是任何有效的说明符。这是 Deno 中一个非常强大的功能,可以重新映射任何内容。了解更多关于导入映射可以做什么的信息 这里。
区分 deno.json
中的 imports
或 importMap
选项和 --import-map
选项 Jump to heading
[导入映射标准]要求每个模块有两个条目:一个是模块说明符,另一个是带有尾随 /
的说明符。这是因为该标准只允许每个模块说明符一个条目,而尾随的 /
表示该说明符指代一个目录。例如,当使用 --import-map import_map.json
选项时,import_map.json
文件必须为每个模块包含这两个条目(注意使用 jsr:/@std/async
而不是 jsr:@std/async
):
{
"imports": {
"@std/async": "jsr:@std/async@^1.0.0",
"@std/async/": "jsr:/@std/async@^1.0.0/"
}
}
deno.json
中 importMap
字段引用的 import_map.json
文件的行为与使用 --import-map
选项完全相同,包含的每个模块的条目要求与上面所示相同。
相比之下,deno.json
扩展了导入映射标准。当你在 deno.json
中使用 imports 字段或通过 importMap
字段引用 import_map.json
文件时,你只需要指定模块说明符而不带尾随 /
:
{
"imports": {
"@std/async": "jsr:@std/async@^1.0.0"
}
}
使用 deno add
添加依赖 Jump to heading
使用 deno add
子命令可以轻松完成安装过程。它将自动将你请求的包的最新版本添加到 deno.json
的 imports
部分。
# 将模块的最新版本添加到 deno.json
$ deno add jsr:@luca/cases
Add @luca/cases - jsr:@luca/cases@1.0.0
{
"imports": {
"@luca/cases": "jsr:@luca/cases@^1.0.0"
}
}
你也可以指定确切的版本:
# 传入确切版本
$ deno add jsr:@luca/cases@1.0.0
Add @luca/cases - jsr:@luca/cases@1.0.0
在 deno add
参考文档 中阅读更多内容。
你也可以使用 deno remove
移除依赖项:
$ deno remove @luca/cases
Remove @luca/cases
{
"imports": {}
}
在 deno remove
参考文档 中阅读更多内容。
包版本 Jump to heading
你可以为正在导入的包指定一个版本范围。这通过 @
符号后跟版本范围说明符来完成,并遵循 semver 版本方案。
例如:
@scopename/mypackage # 最高版本
@scopename/mypackage@16.1.0 # 精确版本
@scopename/mypackage@16 # 最高的 16.x 版本 >= 16.0.0
@scopename/mypackage@^16.1.0 # 最高的 16.x 版本 >= 16.1.0
@scopename/mypackage@~16.1.0 # 最高的 16.1.x 版本 >= 16.1.0
以下是所有你可以指定版本或范围的方式的概述:
符号 | 描述 | 示例 |
---|---|---|
1.2.3 |
精确版本。仅使用此特定版本。 | 1.2.3 |
^1.2.3 |
兼容版本 1.2.3。允许更新而不改变最左侧的非零数字。 例如, 1.2.4 和 1.3.0 是允许的,但 2.0.0 不是。 |
^1.2.3 |
~1.2.3 |
大约等价于版本 1.2.3。允许对补丁版本进行更新。 例如, 1.2.4 是允许的,但 1.3.0 不是。 |
~1.2.3 |
>=1.2.3 |
大于或等于版本 1.2.3。任何版本 1.2.3 或更高的版本都是允许的。 |
>=1.2.3 |
<=1.2.3 |
小于或等于版本 1.2.3。任何版本 1.2.3 或更低的版本都是允许的。 |
<=1.2.3 |
>1.2.3 |
大于版本 1.2.3。仅允许版本高于 1.2.3 的版本。 |
>1.2.3 |
<1.2.3 |
小于版本 1.2.3。仅允许低于 1.2.3 的版本。 |
<1.2.3 |
1.2.x |
在次版本 1.2 中的任何补丁版本。例如,1.2.0 、1.2.1 等。 |
1.2.x |
1.x |
在主版本 1 中的任何次版本和补丁版本。例如,1.0.0 、1.1.0 、1.2.0 等。 |
1.x |
* |
任何版本都是允许的。 | * |
HTTPS 导入 Jump to heading
Deno 还支持引用 HTTP/HTTPS URL 的导入语句,可以直接:
import { Application } from "https://deno.land/x/oak/mod.ts";
或作为你的 deno.json
导入映射的一部分:
{
"imports": {
"oak": "https://deno.land/x/oak/mod.ts"
}
}
支持 HTTPS 导入使我们能够支持以下 JavaScript CDN,因为它们提供对 JavaScript 模块的 URL 访问:
HTTPS 导入在你的项目是一个小型且通常是单文件的 Deno 项目时非常有用,因为不需要其他配置。使用 HTTPS 导入,你可以完全避免拥有 deno.json
文件。然而,在更大的应用中建议不要使用这种导入方式,因为你可能会遇到版本冲突(不同的文件使用不同的版本说明符)。
使用 HTTPS 导入时请谨慎,并且仅从受信任的来源。如果服务器被攻破,它可能会向你的应用程序提供恶意代码。如果你在不同的文件中导入不同版本,可能还会导致版本问题。HTTPS 导入仍然被支持,但我们建议使用包注册中心以获得更好的体验。
覆盖依赖 Jump to heading
Deno 提供机制来覆盖依赖项,使开发者在开发或测试期间使用自定义或本地版本库。
注意:如果你需要在构建之间缓存和修改本地依赖项,考虑对远程模块进行 vendoring。
覆盖本地 JSR 包 Jump to heading
对于熟悉 Node.js 中 npm link
的开发者,Deno 通过 deno.json
中的 patch
字段提供了类似的功能。这允许你在开发期间使用本地版本覆盖依赖项,而不需要发布它们。
示例:
{
"patch": [
"../some-package-or-workspace"
]
}
关键点:
patch
字段接受包含 JSR 包或工作区的目录路径。如果你在工作区中引用单个包,整个工作区将被包括在内。- 此功能仅在工作区根目录中被尊重。在其他位置使用
patch
将触发警告。 - 目前,
patch
仅限于 JSR 包。尝试覆盖npm
包将导致警告而无效。
限制:
- 目前不支持
npm
包的覆盖。这计划在未来的更新中实现。 - 不支持基于 Git 的依赖项覆盖。
patch
字段要求在工作区根目录中进行适当配置。- 此功能为实验性质,可能会根据用户反馈进行更改。
覆盖 NPM 包 Jump to heading
Deno 支持通过本地版本覆盖 npm 包,类似于如何覆盖 JSR 包。这允许你在开发期间使用本地 npm 包副本而无需发布它。
要使用本地 npm 包,请在你的 deno.json
中配置 patch
字段:
{
"patch": [
"../path/to/local_npm_package"
],
"unstable": ["npm-patch"]
}
此功能需要一个 node_modules
目录,并且根据你的 nodeModulesDir
设置具有不同的行为:
- 使用
"nodeModulesDir": "auto"
:该目录在每次运行时重新创建,这略微增加启动时间,但确保始终使用最新版本。 - 使用
"nodeModulesDir": "manual"
(使用 package.json 时的默认设置):更新包后必须运行deno install
以将更改应用到工作区的node_modules
目录。
限制:
- 指定本地 npm 包副本或更改其依赖项将从锁文件中清除 npm 包,这可能导致 npm 解析工作方式不同。
- 即使使用本地副本,npm 包名称必须在注册表中存在。
- 此功能目前处于
unstable
标志后面。
覆盖 HTTPS 导入 Jump to heading
Deno 还允许通过 deno.json
中的 importMap
字段覆盖 HTTPS 导入。此功能在调试或临时修复中用本地修补版本替换远程依赖项时特别有用。
示例:
{
"imports": {
"example/": "https://deno.land/x/example/"
},
"scopes": {
"https://deno.land/x/example/": {
"https://deno.land/x/my-library@1.0.0/mod.ts": "./patched/mod.ts"
}
}
}
关键点:
- 导入映射中的
scopes
字段允许你将特定导入重定向到其他路径。 - 这通常用于使用本地文件覆盖远程依赖项以进行测试或开发目的。
- 导入映射仅适用于项目的根目录。依赖项中的嵌套导入映射会被忽略。
Vendoring 远程模块 Jump to heading
如果你的项目具有外部依赖项,你可能希望将它们存储在本地,以避免每次构建项目时都从互联网下载它们。这在你在 CI 服务器或 Docker 容器中构建项目时尤其有用,或者需要修补或以其他方式修改远程依赖项。
Deno 通过在 deno.json
文件中设置以下功能来提供此功能:
{
"vendor": true
}
将上述代码段添加到你的 deno.json
文件中,Deno 将在项目运行时将所有依赖项缓存到 vendor
目录中,或者你可以选择运行 deno install --entrypoint
命令以立即缓存依赖项:
deno install --entrypoint main.ts
然后你可以像往常一样使用 deno run
运行应用程序:
deno run main.ts
在进行 vendoring 后,你可以使用 --cached-only
标志在没有互联网访问的情况下运行 main.ts
,这会强制 Deno 仅使用本地可用的模块。
有关更加高级的覆盖,例如在开发期间替代依赖项,请参见 覆盖依赖。
发布模块 Jump to heading
任何定义导出的 Deno 程序都可以作为模块发布。这允许其他开发人员在自己的项目中导入和使用你的代码。模块可以发布到:
- JSR - 推荐,原生支持 TypeScript,并为你自动生成文档
- npm - 使用 dnt 创建 npm 包
- deno.land/x - 用于 HTTPS 导入,尽可能使用 JSR,而不是
重新加载模块 Jump to heading
默认情况下,Deno 使用全局缓存目录(DENO_DIR
)来存储下载的依赖项。这个缓存共享于所有项目。
你可以使用 --reload
标志强制 Deno 重新获取并重新编译模块到缓存中。
# 重新加载所有内容
deno run --reload my_module.ts
# 重新加载特定模块
deno run --reload=jsr:@std/fs my_module.ts
只使用缓存模块 Jump to heading
要强制 Deno 只使用之前已缓存的模块,请使用 --cached-only
标志:
deno run --cached-only mod.ts
如果有任何依赖于 mod.ts 的依赖树中的依赖项尚未缓存,这将失败。
完整性检查和锁文件 Jump to heading
想象一下,你的模块依赖于位于 https://some.url/a.ts 的远程模块。当你第一次编译模块时,a.ts
被获取、编译并缓存。此缓存版本将在你在不同机器上运行模块(例如在生产环境中)或手动重新加载缓存(使用命令,如 deno install --reload
)之前使用。
但是如果 https://some.url/a.ts
的内容发生变化呢?这可能导致你的生产模块与本地模块运行的依赖代码不同。为了检测这一点,Deno 使用完整性检查和锁文件。
Deno 使用 deno.lock
文件检查外部模块的完整性。要启用锁文件,应该做到以下两点之一:
-
在当前或祖先目录中创建
deno.json
文件,这将自动创建一个附加锁文件deno.lock
。注意,可以通过在 deno.json 中指定以下内容来禁用此功能:
deno.json{ "lock": false }
-
使用
--lock
标志以启用并指定锁文件检查。
冻结锁文件 Jump to heading
默认情况下,Deno 使用附加锁文件,新的依赖项将被添加到锁文件中,而不是发生错误。
在某些情况下(例如 CI 流水线或生产环境),这可能并不是所希望的,在这些情况下你希望 Deno 在遇到从未见过的依赖项时报错。为此,你可以指定 --frozen
标志,或在 deno.json 文件中设置以下内容:
{
"lock": {
"frozen": true
}
}
在使用冻结的锁文件运行 deno 命令时,任何尝试用新内容更新锁文件的操作都会导致命令退出,并显示将要进行的修改的错误信息。
如果你希望更新锁文件,可以在命令行中指定 --frozen=false
,以暂时禁用冻结锁文件。
更改锁文件路径 Jump to heading
可以通过指定 --lock=deps.lock
或在 Deno 配置文件中设置以下内容来配置锁文件路径:
{
"lock": {
"path": "deps.lock"
}
}
私有仓库 Jump to heading
可能会有一些情况,你希望加载位于 私有 仓库中的远程模块,例如 GitHub 中的私有仓库。
Deno 支持在请求远程模块时发送 bearer token。Bearer tokens 是与 OAuth 2.0 一起使用的主流访问令牌类型,并且广泛得到托管服务的支持(例如 GitHub、GitLab、Bitbucket、Cloudsmith 等)。
DENO_AUTH_TOKENS Jump to heading
Deno CLI 将寻找名为 DENO_AUTH_TOKENS
的环境变量,以确定应考虑使用哪些身份验证令牌来请求远程模块。环境变量的值格式为 n 数量的令牌,由分号(;
)分隔,其中每个令牌可以是:
- 格式为
{token}@{hostname[:port]}
的 bearer token,或者 - 格式为
{username}:{password}@{hostname[:port]}
的基本认证数据
例如,单个令牌对于 deno.land
看起来像这样:
DENO_AUTH_TOKENS=a1b2c3d4e5f6@deno.land
或者:
DENO_AUTH_TOKENS=username:password@deno.land
多个令牌则看起来像这样:
DENO_AUTH_TOKENS=a1b2c3d4e5f6@deno.land;f1e2d3c4b5a6@example.com:8080;username:password@deno.land
当 Deno 前往获取一个远程模块时,如果主机名与远程模块的主机名匹配,Deno 将把请求的 Authorization
头设置为 Bearer {token}
或 Basic {base64EncodedData}
的值。这允许远程服务器识别请求是与特定已认证用户关联的授权请求,并提供对服务器上适当资源和模块的访问。
GitHub Jump to heading
要访问 GitHub 上的私有仓库,你需要为自己发放一个 个人访问令牌。你可以通过登录 GitHub 并进入 设置 -> 开发者设置 -> 个人访问令牌:
然后选择 生成新令牌,为你的令牌提供描述,并相应地授予 repo
权限。repo
权限将使你能够读取文件内容(有关 GitHub 中作用域的更多信息):
创建后,GitHub 将仅显示新令牌一次,此时你要在环境变量中使用的值:
为了访问包含在 GitHub 私有仓库中的模块,你需要在环境变量 DENO_AUTH_TOKENS
中使用发放的令牌,作用域为 raw.githubusercontent.com
主机名。例如:
DENO_AUTH_TOKENS=a1b2c3d4e5f6@raw.githubusercontent.com
这应该允许 Deno 访问任何由该令牌发放用户可以访问的模块。
当令牌不正确或用户没有访问模块的权限时,GitHub 将发出 404 Not Found
状态,而不是未授权状态。因此,如果你在命令行中收到访问模块未找到的错误,请检查环境变量设置和个人访问令牌设置。
此外,deno run -L debug
应该打印出关于从环境变量解析出的令牌数量的调试信息。如果它觉得任何令牌格式错误,它会打印错误消息。但出于安全原因,它不会打印任何令牌的详细信息。