Built In Modules (aka JS Standard Library) S1
中文标题:内置模块(又称 JavaScript 标准库)
- 阶段: Stage 1
- 状态: 进行中
- ECMAScript 版本: —
- 同步时间: 2026年8月26日
- English original · 官方仓库
该提案通过引入可通过 js: 命名空间和导入语义访问的内置模块机制,解决了 JavaScript 缺乏标准库的问题。它概述了一个类似于 Python 导入系统的模块解析链,包括对填补和填充的支持。该提案处于早期阶段,尚未有实现。
以下 README 来自上游仓库,其中的阶段或状态标注可能滞后;当前信息以提案概览为准。
内置模块提案(又称 JavaScript 标准库)
该提案旨在为 JavaScript 添加一种机制,通过一系列内置模块来实现更广泛的标准库。有了这个基础设施,就可以开始以附加模块的形式迭代标准库功能。
范围
本提案的目标是定义一种机制,使 JavaScript 拥有比现在更广泛的标准库。目前,如果向语言添加新属性,它们会被添加到全局对象的某个位置。
本提案不会改变任何现有代码的行为,也不会添加任何新语法,除非可能需要导入标准库代码的语法。很可能需要添加新的属性和方法来启用此功能。
标准库的内容与本提案无直接关系,将在后续工作中构建和扩展。这样的库只会涵盖在 JavaScript 中普遍有用的功能,而不是与 Web 平台相关的内容。基于 JavaScript 构建的主机环境可以提供自己的库组件。(一个好的启发式方法:如果某功能在 Web 浏览器中有意义,但在 node 或嵌入式设备或机器人中没有意义,那么它可能不在范围内。)关于库的范围和内容的讨论,请参见#16。
动机
大多数编程语言都有核心语言特性(语法、运算符、原语等)以及常用功能的标准库。开发人员可以立即在他们的程序中使用这个库,因为该库与运行时捆绑在一起。
JavaScript 语言没有标准库。 因此,新功能被添加到全局对象中,或者开发人员寻找并采用他们将与应用程序的其余部分捆绑在一起的库。 因为这些库需要包含在每个程序中,而不是由运行时提供服务,所以应用程序的代码大小会增加,用户需要为下载和解析常见库组件付出成本。这可能会增加下载时间,并使 JavaScript 引擎几乎无法缓存公共代码。
JavaScript 程序运行在各种具有不同运行时实现和版本的客户端上。捆绑的库代码很可能需要考虑不同的主机环境,从而增加代码大小。 与 JavaScript 引擎捆绑在一起的库代码可以针对目标主机环境进行优化。
建议的解决方案
为了允许开发人员访问由 JavaScript 引擎提供的常见功能,我们建议创建内置在主机的模块。 这将允许与程序捆绑的库代码转向可在主机 JavaScript 实现上使用。
免责声明:本提案涵盖为 JavaScript 实现添加标准库机制,但未描述其内容。这被认为是次要工作,将在访问它的机制就位后在未来构建和扩展。
在运行时随时可用的标准库意味着程序不必包含标准库中可用的功能,从而减少了程序的下载和启动成本。 该功能也将在各实现中标准化,为开发人员提供质量、行为和速度上的一致性。 对于某些 JavaScript 引擎,内置模块将允许它们减少应用程序的内存占用。
尽管 JavaScript 引擎已经通过全局对象拥有了公共库的概念,但标准库组件可以使用当前异步的 import() API 访问。
模块脚本也可以使用 import 语法访问内置库组件。
传统的脚本代码将能够通过一个新的同步 API importNow() 访问这些相同的标准库组件。
开发人员应该已经熟悉这些机制中的大部分,这使得他们可以轻松地选择使用此内置库功能。
标准库的模块大部分应该能够用纯 JavaScript 编写,但对于热门的代码段,引擎可以提供本地实现。
导入语义
为了从标准库导入模块,引擎必须能够区分标准库模块和其他(用户定义的)模块。为了让引擎做到这一点,标准库模块将在模块标识符字符串中使用前缀。这比其他替代方案更受欢迎,因为它不会引入用于加载标准库模块的新语法,并且与开发人员应该已经熟悉的 import 语句保持一致。
命名空间
js: 前缀将保留为仅用于 JavaScript 语言的命名空间,并由 TC39 管理,使标准库成为真正的 JavaScript 标准库。这将允许委员会在设计和开发标准库时安全地在此命名空间内工作。
用于区分标准模块的替代方案记录在附录 A中。
通过为 JavaScript 标准库专门创建命名空间,开发人员将知道在使用 js: 前缀跨不同实现导入时会得到什么,并且可以确保这些实现(不考虑实现限制、供应商时间表或版本差异)提供相同的模块。
引入由其他标准机构或组织管理的更多命名空间是完全可行的。然而,重要的是这些命名空间相互独立,以避免冲突,防止因外部污染或对其他组织依赖的时间限制而阻碍命名空间内的开发。
用于 JavaScript 标准库的命名空间将向 IANA 注册,以防止将来发生冲突。任何引入命名空间前缀的其他组织也被鼓励这样做。
冻结导出
标准库中所有导出的对象和类都将冻结其原型。这将防止导入对象的原型在模块外部被修改,从而导致原型污染。
过去,委员会在向内置对象添加新功能时必须做出让步以保持 Web 兼容性。通过冻结标准库导出的原型,第三方代码将不再可能以可能不兼容的方式修改或扩展库代码。这将为设计和开发标准库提供更大的灵活性。仍然可以使用 extend 或 Object.create 来扩展标准库类和对象。
我们可以从惯例上对标准库模块导出的对象强制执行 Object.freeze 开始。如果这很难检查和执行,可以创建一个单独的提案来描述在标准库模块的模块边界自动冻结原型。
注意,当前的方向是冻结模块内容,因此需要填充代码来包装模块类和对象。鉴于围绕这一点的讨论,在将内置模块添加到标准之前,它可能会从提案中删除。
模块解析
为了允许 JavaScript 引擎参与模块解析和加载,应将 HostResolveModuleIdentifier 抽象操作更改为允许多个解析器。解析器按链排列,由引擎逐一咨询以解析请求的 ModuleIdentifier。该机制深受 Python 的模块导入系统 的启发。
链的实现将隐藏在引擎内部,并提供与 HostResolveModuleIdentifier 当前相同的保证:
- 它必须返回 ModuleRecord 的实例
- 如果无法创建 ModuleRecord,它必须抛出错误
- 对于 (ModuleIdentifier, ReferencingModule) 对,它必须是幂等的
导入分两个阶段完成:解析和加载。解析通过实现 ResolveModuleIdentifier 操作的对象完成,加载通过实现 LoadModuleIdentifier 操作的对象完成。实现这两种操作的对象称为导入器。

多个导入器可以在引擎中注册,并将被追加到链中。引擎将始终首先注册内部的 JavaScript 标准库导入器,以确保它位于链的前端。
在解析步骤(阶段 1)中,导入器按注册顺序执行 ResolveModuleIdentifier 操作。此操作产生一个 ModuleResolutionRecord,它沿着链传递,可供后续导入器使用。
在解析步骤之后,调用加载步骤(阶段 2)。在此步骤中,以相反顺序访问导入器以执行 LoadModuleIdentifier 操作,尝试加载请求的 ModuleIdentifier。该操作接收来自解析步骤的 ModuleResolutionRecords。当 ModuleIdentifier 可以加载时,将返回完整的 ModuleRecord,并立即退出链。当没有产生 ModuleRecord 时,将咨询下一个导入器,直到链耗尽,导致 ModuleNotFound 错误。
填补 / 填充
在加载步骤(阶段 2)中,链以相反顺序遍历,以允许更高优先级(例如,稍后注册)的导入器可能覆盖较低优先级的导入器。这将允许主机例如覆盖标准库模块并实现填补/填充。
或者,提供一些条款允许代码导入内置模块,对其进行填补,并更新主机模块表以指向已填补的版本。这可以重复进行,以便可以将填补/填充层层叠加。
标准库的填充解决方案应支持三个用例:
- 添加标准库缺失的部分
- 更新不完整的实现
- 编辑库组件的一部分。
填充旨在仅涵盖这三个用例。
对于 Web 平台,可以使用 Import Maps 提案 进行填充。由嵌入器(在这种情况下是 Web 浏览器)注册的解析器可以检查导入映射,以查看标准库 ModuleIdentifier 是否应重定向到另一个实现。
相关工作
这个话题过去已被讨论过,并且有相关工作:
常见问题
敬请期待™
附录
A. 区分标准库模块
标准库的要求之一是能够区分模块是用户定义的还是应从标准库加载。有很多方法可以做到这一点,本节包含在提案前面建议的备选方案。
重要提示:示例中使用的代码示例和模块纯粹用于说明目的
基于标识符
导入模块时,ModuleSpecifier 必须是字符串。目前,字符串内容的解释留给嵌入器,实际上使其始终是用户定义的模块。
为了规避这一点并保留当前行为,可以使用一种特殊的 Identifier 形式的 ModuleSpecifier,而不是字符串字面量:
std 标识符用于以某种方式更改签名,使引擎能够检测到这是一个应从标准库加载的模块。使用 std.________ 的缺点是它开始看起来像一个在其他上下文中也可用的全局对象。
也可以使用专门的标记代替 Identifier 前缀,例如类似于 C/C++:
虽然这使导入标准库模块与用户定义的模块明显不同,但这也是缺点之一。该语法与开发人员应该已经熟悉的导入语法不同,并且动态变体(从语法上讲)会很困难。
不使用前缀也有缺点,即要求标准库中的所有内容都位于同一个空间中,从而创建一个新的“全局”命名空间,并阻止为不同上下文使用多个命名空间。
单独关键字
导入模块时,ModuleSpecifier 必须是字符串。目前,字符串内容的解释留给嵌入器,实际上使其始终是用户定义的模块。
为了规避这一点并保留当前行为,可以引入一个不同于 import 的关键字,专门用于从标准库导入模块:
重要提示:
include关键字不存在,仅用于说明目的
通过将它与 ModuleSpecifier 的 Identifier 组合来强调单独的关键字。单独的关键字已经足以区分模块,因此我们也可以安全地为 ModuleSpecifier 使用字符串字面量:
虽然这使导入标准库模块与用户定义的模块非常明显不同,但这也是其缺点之一。该语法与开发人员应该已经熟悉的 import 语法不同,并且还必须创建该关键字的动态变体。
不使用前缀也有缺点,即要求标准库中的所有内容都位于同一个空间中,从而创建一个新的“全局”命名空间,并阻止为不同上下文使用多个命名空间。
基于 URL
在像 Safari 这样的浏览器中,已经支持使用 <script type="module" ... /> 内的 import 语句从 URL 导入模块。URL 的主机部分可以用作区分标准库模块的前缀,并且非常自然地适合 URL 的概念:
使用带域名的 URL 导入模块在 Web 上下文之外(例如在 Node.js 或嵌入式设备中)并不总是有意义。它还需要长期拥有域名。域名的转移可能使前缀无用或排除许多模块被导入。
NPM 风格
Node 包管理器(简称 NPM)对于导入发布到中央注册表的包有既定的格式。该格式允许包在组织下分组,这可以用作 JavaScript 标准库的前缀:
虽然这种格式在已经使用 NPM 的环境中有意义,但不如 URL 通用。NPM 风格格式也遭受与 基于 URL 方法相同的一些缺点。
这种格式需要在 NPM (https://npmjs.com) 上拥有该组织的所有权,并且考虑到多个命名空间,需要所有人都这样做。但 NPM 只是最常用的注册表,可能还有其他注册表,可能是私有的,不可能获得命名空间的所有权。使用这种格式可能会阻止模块被导入。
利用这种格式最大的缺点是标准库模块看起来与用户定义的模块相同,这可能会导致混淆,例如在 Node.js/NPM 上下文中模块来自哪里。这两个模块的行为不同,不能将相同的假设应用于两者。
规范
实现
- 暂无