一切都应该尽可能简单,但不能过于简单。
阿尔伯特·爱因斯坦
从一开始,HTTP 导入就是 Deno 的一个关键特性。多年来,这是整个模块系统,旨在通过利用 Web 的分布式特性来简化 JavaScript 开发,这与 npm 的集中式注册表不同。
例如,您可以像这样从标准库导入 assertEquals()
函数
import { assertEquals } from "https://deno.land/[email protected]/assert/mod.ts";
assertEquals(1, 2);
这个想法是具有颠覆性的(并且仍然可以)。我们努力追求它,但最终意识到:这个设计决策带来重大的权衡。
让我们探讨一下为什么这种方法不能像我们最初希望的那样随着项目复杂性而扩展,以及 Deno 今天如何建议共享和使用模块来克服这些挑战。
梦想
围绕 HTTP 导入设计 Deno 的模块系统是雄心勃勃的。它旨在用基于 HTTP 的分布式系统取代 npm,与浏览器中 ES 模块的工作方式保持一致。这消除了对 package.json
文件和 node_modules
文件夹的需求,简化了项目结构。Deno 脚本可以缩小到单个文件程序,而无需项目目录或配置。与下载大型 tarball 的 npm 不同,HTTP 导入仅获取必要的源代码。私有注册表只是变成了经过身份验证的代理。
我们将此深度集成到 Deno 的工作流程中,包括缓存、预加载和重新加载。我们还构建了 deno.land/x,一个用于连接 git 仓库并通过 HTTP 共享的注册表,并具有诸如生成文档等功能。
现实
尽管有其前景,但自最初实施以来,HTTP 导入出现了一些问题。
URL 的长度
长 URL 会使代码库变得混乱,尤其是在大型项目中。比较
import express from "express"; // Node
import oak from "https://deno.land/x/[email protected]"; // Deno 1.x
Node 的导入显然更短(也更容易记住)。
依赖管理
随着项目的增长,管理长 URL 和版本变得越来越繁琐。
最初,我们采用了 deps.ts
约定来将依赖项集中在项目中的单个文件中
// deps.ts
export { concat } from "https://deno.land/[email protected]/bytes/mod.ts";
export * as base64 from "https://deno.land/[email protected]/encoding/base64.ts";
然后,可以像这样导入依赖项
import { concat } from "../../deps.ts";
虽然这可行,但与简单的 package.json
文件相比,它很麻烦。
重复依赖项
URL 缺少语义版本控制,使得管理依赖项变得困难。
尽管版本字符串可以嵌入到 URL 中(例如,https://deno.land/[email protected]/fs/copy.ts
),但 HTTP 导入会将您锁定到一个确切的版本,除非您手动更新 URL。在较大的项目中,这意味着您很容易在代码库中得到同一库的多个变体(这在实践中当然很少是必要的或有益的)。
语义版本控制有助于消除重复依赖项,减少加载模块的数量。理想情况下,Deno 应该识别可互换的模块,并使用最新版本。
可靠性
分布式模块系统也造成了可靠性问题。许多模块托管在随机网站或个人服务器上,导致正常运行时间问题。虽然这些服务器宕机不会立即导致 Deno 程序宕机(因为我们缓存远程依赖项),但可能会破坏 CI 和新的部署。虽然 Deno 确保其 deno.land/x
注册表的高可用性,但它无法控制其他主机,使得整体可用性取决于依赖关系图中可靠性最低的主机。
解决方案
为了解决上述所有问题,Deno 引入了两项重大改进:导入映射和 JSR。
通过导入映射和 JSR 扭转局面
让我们明确一点:Deno 不会删除 HTTP 导入。我们仍然相信它们的有用性。然而,越来越明显的是,通常需要更多的结构。
我们致力于通过简化代码的编写和分发方式来改善 JavaScript 生态系统。JavaScript,本质上是默认的编程语言,值得拥有一个出色的模块系统。
该解决方案的一部分是导入映射,这是来自浏览器的另一个 Web 标准,已在 Deno 中实现。导入映射允许您恢复简短且易于记忆的说明符,并在多个文件中管理版本
{
"imports": {
"$ga4": "https://raw.githubusercontent.com/denoland/ga4/main/mod.ts",
"$marked-mangle": "https://esm.sh/[email protected]",
"@astral/astral": "jsr:@astral/astral@^0.4.0",
"@fresh/plugin-tailwind": "./plugin-tailwindcss/src/mod.ts",
"@luca/esbuild-deno-loader": "jsr:@luca/esbuild-deno-loader@^0.10.3"
}
}
然而,就其本身而言,导入映射无法解决语义版本控制问题或可靠性问题——这就是 JSR 的用武之地。
我们创建了 JSR 作为了解 semver 的集中式存储库,以解决剩余的两个问题
- JSR 避免了依赖多个主机来提供模块的可靠性问题;并且
- JSR 使用语义版本控制避免了重复依赖项问题(类似于
package.json
在 Node 中与 npm 的工作方式)。
我们相信这个新的注册表将大大简化 JavaScript 的使用和共享方式。虽然诚然它比 HTTP 导入稍微复杂一些,但我们认为这些好处是值得权衡的。
什么是 JSR?
我们在三月份推出了 JSR。JSR 是一个开源的、跨运行时的代码注册表,允许用户轻松共享现代 JavaScript 和 TypeScript。它被构建为可靠且廉价的托管,本质上充当一个重度缓存的文件服务器,这归功于不变性保证。
JSR 理解并强制执行语义版本控制,解决了重复依赖项问题。集中式存储库还允许我们提供许多其他方式不可能实现的改进,从简单的 TypeScript 发布到鼓励最佳实践的包评分。(您可以在此处阅读有关 JSR 的更多信息,以及在此处阅读我们构建 JSR 的原因。)
在底层,JSR 仍然使用 HTTP 导入。例如,采用这个说明符
jsr:@luca/flag
上述内容实际上可以被认为是到以下内容的智能重定向
https://jsr.deno.org.cn/@luca/flag/1.0.0/mod.ts
这意味着 JSR 继承了 HTTP 导入中真正出色的部分。 例如:仅对实际导入的代码进行细粒度下载(没有大型 tarball!)。但是,由于用户没有直接接触到这些 HTTP 导入,因此长 URL 和手动字符串管理等问题就消失了。
这对现代 Deno 意味着什么
现有的带有 HTTP 导入的 Deno 脚本将继续工作——它们非常适合一定规模的项目。但是,我们现在建议使用导入映射而不是 deps.ts
,以及使用 JSR 而不是 deno.land/x
和/或 npm。
因此,回到上面的 assert
示例:您会发现在这个新系统中它更加简洁。并且由于 semver 解析,依赖项会自动保持最新(只要它们没有被 lockfile 锁定)!
// ❌ Deno 1.x:
import { assertEquals } from "https://deno.land/[email protected]/assert/mod.ts";
// ✅ Deno 2
import { assertEquals } from "jsr:@std/assert@1";
assertEquals(1, 2);
当在更大的项目中使用时,您可以选择添加导入映射,以使导入说明符更短,并更轻松地管理跨文件的版本。然后 assert
示例看起来更加简洁
import { assertEquals } from "@std/assert";
assert(1, 2);
{
"imports": {
"@std/assert": "jsr:@std/assert@1"
}
}
何时或是否选择采用这种方法取决于您。我们重视 Deno 脚本缩小到单个文件的能力(无需 deno.json
配置),因此导入映射完全可选非常重要。
Deno 2 即将到来
JavaScript 应该拥有一个与浏览器标准对齐的简单模块系统。我们希望提升生态系统,并帮助它成为我们认为它将不可避免地成为的行业基石。
为了实现这一目标,我们需要良好的设计,而良好的设计需要迭代——我们必须诚实地检查问题并解决它们。
解决这些问题定义了 Deno 2 中的许多更改
- 使用 JSR 共享模块,而不是随机文件服务器
- 使用 semver 对 Deno 包进行版本控制
- 使用导入映射管理依赖项
Deno 2 还有一些我们尚未讨论的其他特性
- 工作区和 monorepo 支持, 已在 Deno 1.45 中实现
- 深度 Node/npm 兼容性,包括 N-API 支持和与 Next.js 的兼容性
我们将在今年九月发布 Deno 2(这次是真的)。
我很高兴看到人们如何使用下一代尽可能简单但不简化的 JavaScript 工具链。
🚨️ Deno 2 即将到来 🚨️
Deno 2 中有一些小的破坏性更改,但是您可以通过今天使用
DENO_FUTURE=1
标志使您的迁移更顺畅。