跳到主要内容
Deno 2 终于来了 🎉️
了解更多

为什么我们在 Deno 中添加了 package.json 支持

Deno 的最新版本 引入了一个重大变化:通过 package.json 支持提供增强的 Node 和 NPM 兼容性。此更新引发了关于我们优先级是否发生变化的问题,因为 Deno 长期以来一直与开辟一条与 Node 不同的道路相关联。事实上,package.json 在Deno 的首次演示中被明确提及为遗憾之处。因此,许多用户对这一发展感到惊讶。

在这篇文章中,我们将解决这些问题,分享我们不断变化的想法的见解,并概述我们的未来目标。

简化和加速 JavaScript 开发

Deno 是为了简化和加速 JavaScript 开发而创建的。核心功能包括原生 TypeScript 支持、内置工具、默认情况下无需配置以及 Web 标准 API。从历史上看,这也意味着一个独立于 NPM 生态系统的极简、分散的模块系统,它基于 Web 标准 HTTP 导入。

Deno 的主要目标是使编程既简单又快速。TypeScript、Web 标准 API 和许多其他功能都有助于实现这一目标,但 Deno 的极简模块系统是否使编程简单且快速,这一点越来越不清楚。

依赖管理梦想

JavaScript 生态系统对单个集中式模块注册表的依赖与 Web 的分散性质相冲突。随着ES 模块的引入,现在存在一个加载远程模块的标准,该标准看起来与 NPM 的模块加载方式完全不同。Deno 通过 HTTP URL 实现加载 ES 模块 - 允许任何人通过运行文件服务器在任何域上托管代码。

这种方法提供了巨大的优势。单个文件程序可以访问强大的工具,而无需依赖于依赖关系清单(一个列出依赖关系及其获取位置的文件,例如 package.jsonimport_map.jsonCargo.tomlGemfile)。此外,Deno 程序受益于更小的下载量,因为只有所需的文件会被下载,而不是包含不必要工件的大型 npm 包压缩包。此外,使用 HTTP 进行模块链接为创新打开了大门,例如根据 accept 标头显示 HTML 文档,如deno.land 注册表所示。

我们一直致力于使用 HTTP 导入作为 Deno 模块系统的基础。我们在 deno.land 注册表中构建了各种有用的功能,例如发布到标签的 GitHub 集成、不可变缓存以及自动生成的文档。像skypackesm.sh 这样的第三方注册表已使 NPM 包中的单个文件可作为使用 HTTP 导入的 ES 模块访问。我们已经建立了像deps.ts 这样的约定,用于在一个方便的位置集中依赖关系。

挥之不去的问题

根据上下文,像 https://deno.land/[email protected]/uuid/mod.ts 这样的模块 URL 有时过于具体。它们不仅标识了包 (std/uuid/mod.ts),还指定了版本 (0.179.0) 和从中获取包的服务器 (deno.land)。当程序包含相似但略有不同的模块时,就会出现问题 - 如果另一个模块导入引用略微不同版本的 URL,例如 https://deno.land/[email protected]/uuid/mod.ts,则这两个模块版本将包含在模块图中,尽管它们几乎是相同的代码。这被称为重复依赖问题(单击链接查看此问题的更具体示例)。

在我们开发的库代码中,我们已经开发了deps.ts 这样的模式来管理远程依赖关系。但是,deps.ts 不是特别符合人体工程学 - 它需要展平并重新导出依赖的每个符号。(这可以通过模块声明导入反射 TC39 提案在未来得到改进。)deps.ts 通常比带有裸规范的 package.json 更冗长且语法混乱。

理想情况下,导入映射 可以通过允许用户在他们的代码中使用裸规范并在导入映射中指定 URL 来解决这个问题。然而,导入映射不可组合:已发布的库不能同时使用裸规范和导入映射,因为导入该库的顶层项目无法将其导入映射与其自己的导入映射组合在一起。

esm.shskypack 这样的转译服务器非常适合将一些 NPM 模块导入 Deno,但它们有其固有的局限性。例如,如果 NPM 模块在运行时加载数据文件,这些转译服务器将无法提供兼容的版本。像这样的问题会导致开发者体验不佳。

裸规范的力量

使用裸规范的导入语句,例如下面的语句,简洁且熟悉

import express from "express";

裸规范 ("express") 提供了一个对依赖关系的有用模糊引用,这允许通过语义版本控制解析来解决重复依赖问题。但是,如果库使用裸规范编写,则必须存在一个依赖关系清单来阐明这些裸规范指的是什么。

通过兼容性简化和加速

我们希望让 Deno 用户比使用 Node 更有效地工作。开发人员希望导入库并轻松使用它。因此,我们的 npm 规范支持。除了库之外,开发人员希望直接在 Deno 中运行现有的 Node 项目。因此,新增了 package.json 支持。

Deno 的向后兼容性将 Node 和 NPM 的不太理想的遗留功能保持在一定距离。例如,Deno 仅通过 NPM 导入支持 CommonJS。此外,Deno 不允许用户使用裸规范 ("fs") 引用内置的 Node 模块,除非在 NPM 依赖项之外,而是必须使用 "node:fs"。或者,例如,在 Deno 中,setTimeout 将返回一个数字,符合 Web 标准(与 Node 不同)。Deno 不会任意运行安装后脚本,并强制执行用户空间安全权限。向后兼容性并不意味着 JavaScript 生态系统无法发展和改进。

需要特别注意的是,Deno 将始终支持通过 URL 链接代码,继续像使用 HTTP 导入的浏览器一样运行。使用 https: URL 现在只是 Deno 中链接代码的一种方法。自 Deno 1.28 以来,我们拥有 npm: URL,并且很容易想象其他可以提高开发速度的 URL,例如 github: URL。

一个新的主要版本

我们正在努力在未来几个月内发布 Deno 的一个新的主要版本。这个即将发布的版本的主要主题是鼓励在 Deno 工作流程中使用裸规范。

尽管 Deno 与 NPM 模块具有出色的向后兼容性,但我们不会建议将为 Deno 用户分发的 Deno 代码发布到 NPM 上。如果需要与 Node 和 NPM 项目共享代码,我们建议使用我们的官方 Deno 到 NPM 编译器 DNT,它会输出高质量的 NPM 包,其中包含从原始 TypeScript 派生的已转译的 Node 兼容 JavaScript 和 TypeScript 声明文件。但是,为了在一个真正以 TypeScript 为首的世界上生存,最好分发并链接到实际的 TypeScript 代码,而不是一些编译后的输出。

为了解决重复依赖问题并提高使用 TypeScript优先的 Deno 模块注册表的易用性,Deno 的下一个主要版本将引入 deno: URL 方案。通过使用这些特殊的 URL,而不是 HTTP URL,Deno 运行时能够进行语义版本解析和模块去重。此外,它消除了编写完整 URL 的必要性。

例如,导入 Oak 将如下所示

import oak from "deno:oak@12";

请注意,只有主版本号被指定——运行时将具有特殊的语义版本解析逻辑来找到与之匹配的正确版本的 Oak。

下一个主要版本还将倡导使用导入映射,作为 package.json 工作流程的现代替代方案。Deno 中的导入映射在自动发现的 deno.json 配置文件中指定。以下是它将是什么样子的示例

{
  "imports": {
    "oak": "deno:oak@12",
    "chalk": "npm:chalk@5"
  }
}

此配置使任何代码能够在整个代码中使用 "oak""chalk" 原始标识符。Oak 来自 Deno 注册表,Chalk 来自 NPM 注册表。代码中的导入将很简单地为

import oak from "oak";
import chalk from "chalk";

无论是使用现代导入映射工作流程还是 Node.js package.json 工作流程,Deno 都旨在成为开发人员用来加速工作的一个可靠工具。JavaScript,世界上的默认编程语言,应该得到这种持续努力来改善其生态系统和工具。



关注最新的发展,请在 Twitter 上关注 @deno_land