deno.com
本页内容

模块与依赖

Deno 使用 ECMAScript 模块作为其默认模块系统,以与现代 JavaScript 标准保持一致,并促进更高效和一致的开发体验。它是 JavaScript 模块的官方标准,允许更好的摇树优化(tree-shaking)、改进的工具集成以及跨不同环境的原生支持。

通过采用 ECMAScript 模块,Deno 确保了与不断发展的 JavaScript 生态系统的兼容性。对于开发人员来说,这意味着一个简化且可预测的模块系统,避免了与 CommonJS 等传统模块格式相关的复杂性。

导入模块 跳转到标题

在此示例中,add 函数从本地的 calc.ts 模块导入。

calc.ts
export function add(a: number, b: number): number {
  return a + b;
}
main.ts
// imports the `calc.ts` module next to this file
import { add } from "./calc.ts";

console.log(add(1, 2)); // 3

您可以在包含 main.tscalc.ts 的目录中调用 deno run main.ts 来运行此示例。

对于 ECMAScript 模块,本地导入说明符必须始终包含完整的文件扩展名。不能省略。

example.ts
// WRONG: missing file extension
import { add } from "./calc";

// CORRECT: includes file extension
import { add } from "./calc.ts";

导入属性 跳转到标题

Deno 支持 with { type: "json" } 导入属性语法来导入 JSON 文件

import data from "./data.json" with { type: "json" };

console.log(data.property); // Access JSON data as an object

从 Deno 2.4 开始,也可以导入 textbytes 模块。

信息

对导入 textbytes 模块的支持是实验性的,需要 --unstable-raw-imports CLI 标志或 deno.json 中的 unstable.raw-import 选项。

import text from "./log.txt" with { type: "text" };

console.log(typeof text === "string");
// true
console.log(text);
// Hello from a text file
import bytes from "./image.png" with { type: "bytes" };

console.log(bytes instanceof Uint8Array);
// true
console.log(bytes);
Uint8Array(12) [
//    72, 101, 108, 108, 111,
//    44,  32,  68, 101, 110,
//   111,  33
// ]

WebAssembly 模块 跳转到标题

Deno 支持直接导入 Wasm 模块

import { add } from "./add.wasm";

console.log(add(1, 2));

要了解更多信息,请访问 WebAssembly 部分

Data URL 导入 跳转到标题

Deno 支持导入数据 URL,这允许您导入不在单独文件中的内容。这对于测试、原型开发或需要以编程方式生成模块时非常有用。

您可以使用 data: URL 方案即时创建模块

// Import a simple JavaScript module from a data URL
import * as module from "data:application/javascript;base64,ZXhwb3J0IGNvbnN0IG1lc3NhZ2UgPSAiSGVsbG8gZnJvbSBkYXRhIFVSTCI7";
console.log(module.message); // Outputs: Hello from data URL

// You can also use the non-base64 format
const plainModule = await import(
  "data:application/javascript,export function greet() { return 'Hi there!'; }"
);
console.log(plainModule.greet()); // Outputs: Hi there!

// A simpler example with text content
const textModule = await import(
  "data:text/plain,export default 'This is plain text'"
);
console.log(textModule.default); // Outputs: This is plain text

数据 URL 格式遵循此模式

data:[<media type>][;base64],<data>

对于 JavaScript 模块,使用 application/javascript 作为媒体类型。TypeScript 也支持 application/typescript。此功能对于在隔离环境中测试模块以及在测试期间创建模拟模块特别有用。

导入第三方模块和库 跳转到标题

在 Deno 中使用第三方模块时,请使用与本地代码相同的 import 语法。第三方模块通常从远程注册表导入,并以 jsr:npm:https:// 开头。

main.ts
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 包支持的更多信息

管理第三方模块和库 跳转到标题

当在多个文件中导入模块时,输入带有完整版本说明符的模块名称可能会变得乏味。您可以使用 deno.json 文件中的 imports 字段集中管理远程模块。我们将此 imports 字段称为导入映射,它基于 导入映射标准

deno.json
{
  "imports": {
    "@luca/cases": "jsr:@luca/cases@^1.0.0",
    "cowsay": "npm:cowsay@^1.6.0",
    "cases": "https://deno.land/x/case/mod.ts"
  }
}

使用重映射的说明符,代码看起来更简洁

main.ts
import { camelCase } from "@luca/cases";
import { say } from "cowsay";
import { pascalCase } from "cases";

重映射的名称可以是任何有效的说明符。这是 Deno 中一个非常强大的功能,可以重映射任何内容。在此处了解有关导入映射功能的更多信息 此处

区分 deno.json 中的 importsimportMap--import-map 选项 跳转到标题

导入映射标准要求每个模块有两个条目:一个用于模块说明符,另一个用于带有尾随 / 的说明符。这是因为标准允许每个模块说明符只有一个条目,并且尾随 / 表示说明符引用的是一个目录。例如,当使用 --import-map import_map.json 选项时,import_map.json 文件必须包含每个模块的两个条目(注意使用 jsr:/@std/async 而不是 jsr:@std/async

import_map.json
{
  "imports": {
    "@std/async": "jsr:@std/async@^1.0.0",
    "@std/async/": "jsr:/@std/async@^1.0.0/"
  }
}

deno.jsonimportMap 字段引用的 import_map.json 文件行为与使用 --import-map 选项完全相同,并且对于每个模块都必须包含上述两个条目。

相比之下,deno.json 扩展了导入映射标准。当您在 deno.json 中使用 imports 字段时,您只需要指定不带尾随 / 的模块说明符

deno.json
{
  "imports": {
    "@std/async": "jsr:@std/async@^1.0.0"
  }
}

使用 deno add 添加依赖 跳转到标题

使用 deno add 子命令可以轻松完成安装过程。它将自动将您请求的最新版本包添加到 deno.json 中的 imports 部分。

# Add the latest version of the module to deno.json
$ deno add jsr:@luca/cases
Add @luca/cases - jsr:@luca/cases@1.0.0
deno.json
{
  "imports": {
    "@luca/cases": "jsr:@luca/cases@^1.0.0"
  }
}

您也可以指定精确版本

# Passing an exact version
$ 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
deno.json
{
  "imports": {}
}

deno remove 参考中阅读更多信息。

包版本 跳转到标题

可以为您要导入的包指定版本范围。这通过使用 @ 符号后跟版本范围说明符来完成,并遵循 semver 版本控制方案。

例如

@scopename/mypackage           # highest version
@scopename/mypackage@16.1.0    # exact version
@scopename/mypackage@16        # highest 16.x version >= 16.0.0
@scopename/mypackage@^16.1.0   # highest 16.x version >= 16.1.0
@scopename/mypackage@~16.1.0   # highest 16.1.x version >= 16.1.0

以下是您可以指定版本或范围的所有方式的概述

符号 描述 示例
1.2.3 精确版本。只使用此特定版本。 1.2.3
^1.2.3 与版本 1.2.3 兼容。允许不更改最左侧非零数字的更新。
例如,允许 1.2.41.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.01.2.1 等。 1.2.x
1.x 主版本 1 中的任何次要和补丁版本。例如,1.0.01.1.01.2.0 等。 1.x
* 允许任何版本。 *

HTTPS 导入 跳转到标题

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 文件。但是,不建议在大型应用程序中使用这种导入方式,因为您最终可能会遇到版本冲突(不同文件使用不同版本说明符)。deno add/deno install 命令不支持 HTTP 导入。

信息

请谨慎使用 HTTPS 导入,并且仅从受信任的来源导入。如果服务器受到威胁,它可能会向您的应用程序提供恶意代码。如果您在不同文件中导入不同版本,它们还可能导致版本问题。HTTPS 导入仍然受支持,但我们建议使用包注册表以获得最佳体验。

覆盖依赖 跳转到标题

Deno 提供了覆盖依赖的机制,使开发人员能够在开发或测试期间使用自定义或本地版本的库。

注意:如果您需要本地缓存和修改依赖以供跨构建使用,请考虑预置远程模块

覆盖本地 JSR 包 跳转到标题

对于熟悉 Node.js 中 npm link 的开发人员,Deno 通过 deno.json 中的 patch 字段为本地 JSR 包提供了类似的功能。这允许您在开发期间使用本地版本覆盖依赖,而无需发布它们。

示例

deno.json
{
  "patch": [
    "../some-package-or-workspace"
  ]
}

关键点

  • patch 字段接受包含 JSR 包或工作区的目录路径。如果您引用工作区中的单个包,则整个工作区都将被包含。
  • 此功能仅在工作区根目录中受尊重。在其他地方使用 patch 将触发警告。
  • 目前,patch 仅限于 JSR 包。尝试修补 npm 包将导致警告且无效。

限制

  • npm 包覆盖尚不支持。这已计划在未来的更新中实现。
  • 基于 Git 的依赖覆盖不可用。
  • patch 字段需要在工作区根目录中进行正确配置。
  • 此功能是实验性的,可能会根据用户反馈进行更改。

覆盖 NPM 包 跳转到标题

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 导入 跳转到标题

Deno 还允许通过 deno.json 中的 scopes 字段覆盖 HTTPS 导入。此功能在调试或临时修复时用本地修补版本替换远程依赖时特别有用。

示例

deno.json
{
  "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 字段允许您将特定导入重定向到替代路径。
  • 这通常用于在测试或开发目的中用本地文件覆盖远程依赖项。
  • 作用域仅适用于您项目的根目录。依赖项中的嵌套作用域将被忽略。

预置远程模块 跳转到标题

如果您的项目有外部依赖项,您可能希望将它们本地存储,以避免每次构建项目时都从互联网下载。这在 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

预置后,您可以使用 --cached-only 标志在没有互联网访问的情况下运行 main.ts,该标志强制 Deno 仅使用本地可用的模块。

有关更高级的覆盖,例如在开发过程中替换依赖项,请参阅 覆盖依赖项

发布模块 跳转到标题

任何定义了导出的 Deno 程序都可以作为模块发布。这允许其他开发人员在自己的项目中导入和使用您的代码。模块可以发布到

  • JSR - 推荐,原生支持 TypeScript 并为您自动生成文档
  • npm - 使用 dnt 创建 npm 包
  • deno.land/x - 对于 HTTPS 导入,如果可能,请改用 JSR

重新加载模块 跳转到标题

默认情况下,Deno 使用一个全局缓存目录(DENO_DIR)来存储下载的依赖项。此缓存会在所有项目之间共享。

您可以使用 --reload 标志强制 Deno 重新获取并重新编译模块到缓存中。

# Reload everything
deno run --reload my_module.ts

# Reload a specific module
deno run --reload=jsr:@std/fs my_module.ts

仅限开发依赖 跳转到标题

有时,依赖项仅在开发期间需要,例如测试文件或构建工具的依赖项。在 Deno 中,运行时不需要您区分开发依赖项和生产依赖项,因为运行时只会加载和安装实际在执行代码中使用的依赖项

但是,标记开发依赖项以帮助阅读您的包的人员会很有用。当使用 deno.json 时,约定是在任何“仅开发”依赖项之后添加 // dev 注释

deno.json
{
  "imports": {
    "@std/fs": "jsr:@std/fs@1",
    "@std/testing": "jsr:@std/testing@1" // dev
  }
}

当使用 package.json 文件时,开发依赖项可以添加到单独的 devDependencies 字段中

package.json
{
  "dependencies": {
    "pg": "npm:pg@^8.0.0"
  },
  "devDependencies": {
    "prettier": "^3"
  }
}

为什么 Deno 没有 devImports 字段? 跳转到标题

要理解为什么 Deno 不在包清单中分离开发依赖项,了解开发依赖项试图解决什么问题很重要。

部署应用程序时,您通常希望只安装实际在执行代码中使用的依赖项。这有助于加快启动时间并减小部署应用程序的大小。

历史上,这是通过将开发依赖项分离到 package.json 中的 devDependencies 字段来完成的。部署应用程序时,不安装 devDependencies,只安装依赖项。

这种方法在实践中已被证明存在问题。当依赖项从运行时依赖项变为开发依赖项时,很容易忘记将其从 dependencies 移动到 devDependencies。此外,一些在语义上是“开发时”依赖项的包,如 (@types/*),通常在 package.json 文件中定义在 dependencies 中,这意味着即使不需要它们,它们也会在生产环境中安装。

因此,Deno 采用了一种不同的方法来安装仅生产依赖项:运行 deno install 时,您可以传递 --entrypoint 标志,这会导致 Deno 仅安装指定入口文件实际(传递地)导入的依赖项。由于这是自动的,并且基于实际执行的代码工作,因此无需在单独的字段中指定开发依赖项。

仅使用缓存模块 跳转到标题

要强制 Deno 仅使用先前已缓存的模块,请使用 --cached-only 标志

deno run --cached-only mod.ts

如果 mod.ts 的依赖树中有任何尚未缓存的依赖项,这将失败。

完整性检查和锁定文件 跳转到标题

想象一下您的模块依赖于位于 https://some.url/a.ts 的远程模块。当您第一次编译模块时,a.ts 被获取、编译并缓存。此缓存版本将一直使用,直到您在不同的机器上运行模块(例如在生产环境中)或手动重新加载缓存(使用 deno install --reload 等命令)。

但是,如果 https://some.url/a.ts 的内容发生变化怎么办?这可能导致您的生产模块运行的依赖代码与您的本地模块不同。为了检测这种情况,Deno 使用完整性检查和锁定文件。

Deno 使用 deno.lock 文件来检查外部模块的完整性。要选择使用锁定文件,请执行以下操作之一:

  1. 在当前目录或父目录中创建 deno.json 文件,这将自动在 deno.lock 处创建增量锁定文件。

    请注意,这可以通过在您的 deno.json 中指定以下内容来禁用

    deno.json
    {
      "lock": false
    }
    
  2. 使用 --lock 标志启用并指定锁定文件检查。

冻结锁定文件 跳转到标题

默认情况下,Deno 使用增量锁定文件,其中新依赖项会添加到锁定文件中,而不是报错。

在某些情况下(例如 CI 流水线或生产环境)这可能不是您想要的,您宁愿让 Deno 在遇到从未见过的依赖项时报错。要启用此功能,您可以指定 --frozen 标志或在 deno.json 文件中设置以下内容

deno.json
{
  "lock": {
    "frozen": true
  }
}

当使用冻结锁定文件运行 deno 命令时,任何尝试使用新内容更新锁定文件的操作都将导致命令以错误退出,并显示将进行的修改。

如果您希望更新锁定文件,请在命令行上指定 --frozen=false 以暂时禁用冻结锁定文件。

更改锁定文件路径 跳转到标题

可以通过指定 --lock=deps.lock 或在 Deno 配置文件中指定以下内容来配置锁定文件路径

deno.json
{
  "lock": {
    "path": "deps.lock"
  }
}

私有仓库 跳转到标题

注意

如果您正在寻找私有 npm 注册表和 .npmrc 支持,请访问 npm 支持页面。

有时您可能需要加载位于私有仓库中的远程模块,例如 GitHub 上的私有仓库。

Deno 支持在请求远程模块时发送不记名令牌。不记名令牌是 OAuth 2.0 中使用的主导访问令牌类型,并得到托管服务(例如 GitHub、GitLab、Bitbucket、Cloudsmith 等)的广泛支持。

DENO_AUTH_TOKENS 跳转到标题

Deno CLI 将查找名为 DENO_AUTH_TOKENS 的环境变量,以确定在请求远程模块时应考虑使用哪些身份验证令牌。环境变量的值以分号 (;) 分隔的 n 个令牌的形式,其中每个令牌可以是

  • 格式为 {token}@{hostname[:port]} 的不记名令牌 或
  • 格式为 {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 跳转到标题

要访问 GitHub 上的私有仓库,您需要为自己颁发一个个人访问令牌。您可以通过登录 GitHub 并进入设置 -> 开发者设置 -> 个人访问令牌来完成此操作

Personal access tokens settings on GitHub

然后,您将选择生成新令牌,并为您的令牌提供描述和对 repo 范围的适当访问权限。repo 范围将启用读取文件内容(更多信息请参阅GitHub 文档中的范围

Creating a new personal access token on GitHub

创建后,GitHub 将只显示新令牌一次,其值就是您要在环境变量中使用的值

Display of newly created token on GitHub

为了访问 GitHub 私有仓库中包含的模块,您需要在 DENO_AUTH_TOKENS 环境变量中使用生成的令牌,并将其作用域限定为 raw.githubusercontent.com 主机名。例如

DENO_AUTH_TOKENS=a1b2c3d4e5f6@raw.githubusercontent.com

这应该允许 Deno 访问令牌所颁发的用户有权访问的任何模块。

当令牌不正确,或者用户无权访问模块时,GitHub 将发出 404 Not Found 状态,而不是未授权状态。因此,如果您在命令行上收到模块未找到的错误,请检查环境变量设置和个人访问令牌设置。

此外,deno run -L debug 应该会打印出关于从环境变量中解析出的令牌数量的调试信息。如果它认为任何令牌格式不正确,它将打印错误消息。出于安全目的,它不会打印令牌的任何详细信息。

您找到所需内容了吗?

隐私政策