跳到主要内容
Since Deno 2, we recommend npm specifiers over esm.sh.

如果您不使用 npm 标识符,那您就用错了

在 Deno 的早期,我们建议通过 HTTP 和诸如 esm.shunpkg.com 等转译服务导入 npm 包。然而,以这种方式导入 npm 包存在局限性,例如缺少安装钩子、重复依赖项解析问题、加载数据文件等。这就是为什么在 Deno 2 发布并添加原生 npm 支持后,我们建议直接使用 npm: 标识符。

// ❌
import React from "https://esm.sh/react@19";

// ✅
import React from "npm:react@19";

在这篇博文中,我们将介绍通过这些转译服务使用 npm 的局限性,以及原生导入 npm 包的所有好处

🚨️ 立即试用 Deno 2。 🚨️

Deno 提供与 Node/npm 的向后兼容性、内置包管理、一体化零配置工具链原生 TypeScript 支持 等。

托管转译服务的局限性

重复依赖项问题

当您导入 https://esm.sh/reacthttps://esm.sh/react-dom 时,后者依赖项可能会导入重复的 react 版本

import react from "https://esm.sh/react";
import reactDom from "https://esm.sh/react-dom"; // <-- no guarantee that this is the same react version

虽然有一些方法可以通过特殊的 esm.sh URL 标志来管理这个问题,但它们可能很繁琐且难以处理。从 esm.sh 导入缺少语义版本控制,这使得重复数据删除依赖项变得困难。

相反,通过 npm: 标识符从 npm 原生导入允许 Deno 理解您的依赖项的语义版本。这就像使用 npm 和 Node 一样,并且会按预期工作。这意味着更小的模块图和更少的加载模块数量。

没有安装钩子和原生插件

某些 npm 包需要在安装时编译原生插件。当使用 npm 原生导入时,安装钩子将运行一个安装脚本,该脚本调用 node-gyp 来构建插件。

不幸的是,通过 HTTP 导入 npm 包时没有安装钩子,因此某些需要单独安装步骤的 npm 包无法完全安装。

Deno 的原生 npm 支持允许使用 --allow-scripts 标志运行安装钩子

deno install --allow-scripts=npm:duckdb

没有数据文件

某些 npm 包附带非 JavaScript 文件,例如文本文件、csv、json 等,这些文件将在运行时加载。然而,这些转译服务无法提供兼容版本,这会导致异常错误和整体较差的开发者体验。

在 Deno 中原生导入 npm 包与在 Node 中导入相同 — 数据文件会被下载,并且可以在运行时从模块访问,这是预期的行为。

原生导入 npm 包的优势

没有 node_modules

编程是与复杂性作斗争。任何多余的代码、配置、文件夹、进程等都会分散对关键业务逻辑的注意力和精力。这就是为什么 Deno 零配置(具有合理的默认值),并且拥有 完整的内置工具链,因此您可以直接投入编写代码。

您可以在 Deno 中使用 npm: 标识符来安装 npm 包,而无需在目录中创建 node_modules 文件夹。这是因为 Deno 会将您的 npm 包安装到全局缓存中

No node_modules folder

您甚至可以在 deno.json 中使用 npm: 标识符

No node_modules folder

请注意,如果存在 package.json,Deno 将自动默认创建 node_modules 文件夹,因为许多 npm 包都期望并需要它。但是,您可以使用 deno.json 中的 nodeModulesDir 属性来控制是否创建 node_modules

没有 package.json

有时您想编写一个简单的 JavaScript 或 TypeScript 程序,并运行和共享它,而无需额外的代码或步骤。

在 Node 中,package.json 是依赖项清单,如果您有任何依赖项,则必须随程序一起提供。如果您要与其他人共享您的程序,他们将需要 package.json,以及使用额外的步骤来安装这些依赖项,然后才能运行您的程序。

在 Deno 中,您可以将 npm: 标识符内联到您的 import 语句中(以及包版本),这样您根本不需要 package.json。您可以通过共享您的 JS 或 TS 文件来共享您的代码。如果其他人使用 Deno 运行它,Deno 将自动下载正确的依赖项,并且您的程序将以与您机器上相同的方式运行。更少的文件、步骤和挫败感。

实际上,将没有依赖项清单的单文件脚本视为“不可变脚本” 可以引导创建可组合程序的生态系统。

Windmill.dev example with immutable scripts

Windmill.dev 使用 Deno 的可选依赖项清单来创建不可变脚本,这是其用户生成工作流程的基础构建块。

当然,在您的依赖项可能需要 package.json 的情况下,您始终可以使用它。

建议在任何 Deno 程序(CLI、服务器、库等)中导入 npm 和 jsr 包 — 即使在其他使用 Deno 的环境中,例如 Jupyter 笔记本和 REPL。让我们接下来看看。

Jupyter 笔记本和 REPL

Deno 的 jupyter 支持非常适合在 JavaScript/TypeScript 中探索数据集,甚至可以用作 REPL(尽管您也可以使用 deno repl — 稍后详述)。

您可以直接在 Jupyter 笔记本中使用 npm:,这样您就可以导入用于数据探索、分析甚至图表的关键 npm 模块。这是一个在 Jupyter 笔记本中使用 npm:polars 的示例

Jupyter notebooks with Deno

使用 JavaScript 和 TypeScript 处理数据集不仅拥有 Python 中可用的许多数据库,而且还允许您轻松地将分析渲染为 HTML。这是一个使用 npm:@observablehq/plot 的示例

Rendering HTML chart with JavaScript in Deno

通过 npm:@observablehq/plotjsr:@ry/jupyter-helper 使用 JavaScript 渲染 HTML 图表。

有兴趣使用 JS/TS 在 Deno jupyter 中探索数据集吗?以下是一些很棒的库(及其 Python 对等物)可以帮助您入门

Python JS/TS
polars npm:polars
ipyaggrid npm:ag-grid-community
ipychart npm:@observablehq/plot
bqplot npm:@observablehq/plot
anywidget jsr:@anywidget

您也可以在 deno repl 中使用 npm:

Importing npm packages in Deno repl

请注意,与任何其他 Deno 程序一样,Jupyter 笔记本和 REPL 都使用您的全局缓存来存储依赖项。这不仅意味着您的目录中减少了供应商混乱,而且一旦您的依赖项被缓存,执行速度也会更快。

改进的安全性

在 Node 中,当您导入 npm 模块时,该模块可以不受限制地访问一切。由于 npm 模块的高度可组合性,已经报告了数十起恶意 npm 模块的安全漏洞,这些漏洞窃取了表单中的用户数据执行了 shell 注入攻击在您的机器上安装了恶意软件等等。

Deno 从一开始就考虑到安全性而设计,它使用选择加入权限模型,当您的任何依赖项请求访问任何敏感内容时,它会提醒您。例如,npm:chalk 需要访问多个环境变量

除了通过 Deno 的权限系统获得额外的安全层之外,Deno 还要求您选择允许在 npm 安装过程中使用预安装和后安装脚本。虽然某些 npm 包需要这些生命周期安装脚本才能正常运行,但它们也拥有对您系统的完全访问权限。这意味着本质上允许包作者在您的机器、CI 环境等上运行任何脚本,如果您不清楚正在安装哪些包,这将是危险的。

在 Deno 中,如果您安装的 npm 包需要执行生命周期安装脚本,您将收到以下警告消息

Permission prompt for lifecycle install scripts during npm install

要启用安装脚本,您可以使用 --allow-scripts 标志。请注意,此标志还可以接受特定包名称的参数,从而不仅为您提供更高的可见性,还提供更精细的控制。

私有 npm 注册表

许多大型组织托管自己的私有 npm 注册表来管理内部包。Deno 以与 Node 相同的方式支持这一点 — 使用 .npmrc 文件来配置 Deno 从此私有注册表获取包

// .npmrc
@mycompany:registry=http://mycompany.com:8111/
//mycompany.com:8111/:_auth=secretToken
// deno.json
{
  "imports": {
    "@mycompany/package": "npm:@mycompany/[email protected]"
  }
}
// main.ts
import { hello } from "@mycompany/package";

console.log(hello());
$ deno run main.ts
Hello world!

您还可以在您的 package.json 文件中使用私有 npm 包

// package.json
{
  "dependencies": {
    "@mycompany/package": "1.0.0"
  }
}
// main.ts
import { hello } from "@mycompany/package";

console.log(hello());
$ deno run main.ts
Hello world!

下一步是什么

虽然 Deno 将始终提供 HTTP 导入,因为它基于 Web 原生协议,但我们建议 Deno 2 及更高版本默认使用 npm:jsr: 标识符。

要了解有关您可以与 Deno 2 一起使用的 npm 包和框架的更多信息,请查看我们的教程