diff --git a/files/zh-cn/web/javascript/guide/modules/index.md b/files/zh-cn/web/javascript/guide/modules/index.md
index db4fe82dddfd74..80fa9b7ed015e7 100644
--- a/files/zh-cn/web/javascript/guide/modules/index.md
+++ b/files/zh-cn/web/javascript/guide/modules/index.md
@@ -1,6 +1,8 @@
---
title: JavaScript 模块
slug: Web/JavaScript/Guide/Modules
+l10n:
+ sourceCommit: 5f76b99045f87349ed030bbd6a3c2e43badb3c22
---
{{jsSidebar("JavaScript Guide")}}{{Previous("Web/JavaScript/Guide/Meta_programming")}}
@@ -11,9 +13,9 @@ slug: Web/JavaScript/Guide/Modules
JavaScript 程序本来很小——在早期,它们大多被用来执行独立的脚本任务,在你的 web 页面需要的地方提供一定交互,所以一般不需要多大的脚本。过了几年,我们现在有了运行大量 JavaScript 脚本的复杂程序,还有一些被用在其他环境(例如 [Node.js](/zh-CN/docs/Glossary/Node.js))。
-复杂的项目需要一种**将 JavaScript 程序拆分为可按需导入的单独模块**的机制。Node.js 已经提供这个能力很长时间了,还有很多的 JavaScript 库和框架已经开始了模块的使用(例如,[CommonJS](https://zh.wikipedia.org/wiki/CommonJS) 和基于 [AMD](https://github.com/amdjs/amdjs-api/blob/master/AMD.md) 的其他模块系统 如 [RequireJS](https://requirejs.org/)、[Webpack](https://webpack.js.org/) 和 [Babel](https://babeljs.io/))。
+复杂的项目需要一种将 JavaScript 程序拆分为可按需导入的单独模块的机制。Node.js 已经提供这个能力很长时间了,还有很多的 JavaScript 库和框架已经开始了模块的使用(例如,[CommonJS](https://zh.wikipedia.org/wiki/CommonJS) 和基于 [AMD](https://github.com/amdjs/amdjs-api/blob/master/AMD.md) 的其他模块系统,如 [RequireJS](https://requirejs.org/)、[webpack](https://webpack.js.org/) 和 [Babel](https://babeljs.io/))。
-目前现代浏览器已原生支持模块化特性,无需额外转译。这是件好事——浏览器可以优化模块加载,这比使用一个单独的库进行额外的客户端处理和额外的网络开销更高效。不过,这并不会使像 Webpack 这类的打包工具过时——打包工具仍然在将代码划分为合理大小的分块方面表现出色,并且能够进行其他优化,如极简化、消除无用代码和摇树优化。
+所有现代浏览器都原生支持模块特性,无需转译。这是一件好事——浏览器可以优化模块的加载,使其比使用库进行所有额外的客户端处理和额外的网络开销更高效。不过,这并不意味着像 webpack 这样的打包工具就过时了——打包工具仍然在将代码分割成合理大小的块方面做得很好,并且能够进行其他优化,如极简化、无用代码消除和摇树优化。
## 介绍一个例子
@@ -41,27 +43,24 @@ modules/
modules 目录下的两个模块的描述如下:
-- `canvas.js` — 包含与设置画布相关的功能:
+- `canvas.js`——包含与设置画布相关的功能:
- - `create()` — 在指定 ID 的包装器 {{htmlelement("div")}} 内创建指定 `width` 和 `height` 的画布,该 ID 本身附加在指定的父元素内。返回包含画布的 2D 上下文和包装器 ID 的对象。
+ - `create()`——在指定 ID 的包装器 {{htmlelement("div")}} 内创建指定 `width` 和 `height` 的画布,该 ID 本身附加在指定的父元素内。返回包含画布的 2D 上下文和包装器 ID 的对象。
- `createReportList()`——创建一个无序列表,并将其添加到指定的包装元素内,该列表可用于输出报告数据。返回列表的 ID。
-- `square.js` — 包含:
+- `square.js`——包含:
- - `name` — 包含字符串 'square' 的常量。
- - `draw()` — 在指定画布上绘制一个正方形,具有指定的大小,位置和颜色。返回包含正方形大小,位置和颜色的对象。
- - `reportArea()` — 在给定长度的情况下,将正方形区域写入特定报告列表。
- - `reportPerimeter()` — 在给定长度的情况下,将正方形的周长写入特定的报告列表。
+ - `name`——包含字符串“square”的常量。
+ - `draw()`——在指定画布上绘制一个正方形,具有指定的大小,位置和颜色。返回包含正方形大小,位置和颜色的对象。
+ - `reportArea()`——在给定长度的情况下,将正方形区域写入特定报告列表。
+ - `reportPerimeter()`——在给定长度的情况下,将正方形的周长写入特定的报告列表。
-> [!NOTE]
-> 在原生 JavaScript 模块中,扩展名 `.mjs` 非常重要,因为使用 MIME-type 为 `javascript/esm` 来导入文件(其他的 JavaScript 兼容 MIME-type 像 `application/javascript` 也可以),它避免了严格的 MIME 类型检查错误,像 "The server responded with a non-JavaScript MIME type"。除此之外,`.mjs` 的扩展名很明了(比如这个就是一个模块,而不是一个传统 JavaScript 文件),还能够和其他工具互相适用。看这个 [Google's note for further details](https://v8.dev/features/modules#mjs)。
-
-## `.mjs` 与 `.js`
+### `.mjs` 与 `.js`
纵观此文,我们使用 `.js` 扩展名的模块文件,但在其他一些文章中,你可能会看到 `.mjs` 扩展名的使用。[V8 推荐了这样的做法](https://v8.dev/features/modules#mjs),比如有下列理由:
- 比较清晰,这可以指出哪些文件是模块,哪些是常规的 JavaScript。
-- 这能保证你的模块可以被运行时环境和构建工具识别,比如 [Node.js](https://nodejs.org/api/esm.html#esm_enabling) 和 [Babel](https://babeljs.io/docs/en/options#sourcetype)。
+- 这能保证你的模块可以被运行时环境和构建工具识别,比如 [Node.js](https://nodejs.org/api/esm.html#esm_enabling) 和 [Babel](https://babeljs.io/docs/options#sourcetype)。
但是我们决定继续使用 `.js` 扩展名,未来可能会更改。为了使模块可以在浏览器中正常地工作,你需要确保你的服务器能够正常地处理 `Content-Type` 标头,其应该包含 JavaScript 的 MIME 类型 `text/javascript`。如果没有这么做,你可能会得到一个严格 MIME 类型检查错误:“The server responded with a non-JavaScript MIME type(服务器返回了非 JavaScript MIME 类型)”,并且浏览器会拒绝执行相应的 JavaScript 代码。多数服务器可以正确地处理 `.js` 文件的类型,但是 `.mjs` 还不行。已经可以正常响应 `.mjs` 的服务器有 [GitHub Pages](https://pages.github.com/) 和 Node.js 的 [`http-server`](https://github.com/http-party/http-server#readme)。
@@ -71,16 +70,16 @@ modules 目录下的两个模块的描述如下:
如果你认为使用 `.mjs` 仅用于模块带来的清晰性非常重要,但不想引入上面描述的相应问题,你可以仅在开发过程中使用 `.mjs`,而在构建过程中将其转换为 `.js`。
-另注意:
+还值得注意的是:
-- 一些工具不支持 `.mjs`,比如 [TypeScript](https://www.typescriptlang.org/)。
+- 一些工具可能不支持 `.mjs`。
- `
+```
+
+导入映射是在一个 `
+```
+
+有了这个映射,我们现在可以在导入模块时使用裸名称:
+
+```js
+import { name as squareName, draw } from "square";
+```
+
+### 重映射模块路径
+
+模块标识符映射条目,其中标识符键和其关联值都带有尾部斜杠(`/`),可以用作路径前缀。这允许将一整组导入 URL 从一个位置重映射到另一个位置。它还可以用于模拟“包和模块”,例如你在 Node 生态系统中可能看到的那样。
+
+> [!NOTE]
+> 尾部 `/` 表示模块标识符键可以作为模块标识符的一部分进行替换。如果没有这个,浏览器将只匹配(并替换)整个模块标识符键。
+
+#### 模块包
+
+以下 JSON 导入映射定义将 `lodash` 作为裸名称,并将模块标识符前缀 `lodash/` 映射到路径 `/node_modules/lodash-es/`(解析为文档基础 URL):
+
+```json
+{
+ "imports": {
+ "lodash": "/node_modules/lodash-es/lodash.js",
+ "lodash/": "/node_modules/lodash-es/"
+ }
+}
+```
+
+有了这个映射,你可以使用裸名称导入整个“包”,并使用路径映射导入其中的模块:
+
+```js
+import _ from "lodash";
+import fp from "lodash/fp.js";
+```
+
+可以在上面导入 `fp` 而不使用 `.js` 文件扩展名,但你需要为该文件创建一个裸模块标识符键,例如 `lodash/fp`,而不是使用路径。如果你只想导入一个模块,这可能是合理的,但如果你希望导入许多模块,这种方法的扩展性较差。
+
+#### 通用 URL 重映射
+
+模块标识符键不一定是路径——它也可以是绝对 URL(或类似 URL 的相对路径,如 `./`、`../`、`/`)。如果你想将具有绝对路径的模块重映射到你自己的本地资源,这可能会很有用。
+
+```json
+{
+ "imports": {
+ "https://www.unpkg.com/moment/": "/node_modules/moment/"
+ }
+}
+```
+
+### 用于版本管理的域限模块
+
+像 Node 这样的生态系统使用 npm 等包管理器来管理模块及其依赖项。包管理器确保每个模块与其他模块及其依赖项分开。因此,虽然一个复杂的应用程序可能在模块图的不同部分多次包含相同的模块,但用户不需要考虑这种复杂性。
+
+> [!NOTE]
+> 你也可以使用相对路径实现版本管理,但这并不理想,因为这会强制你的项目采用特定的结构,并且阻止你使用裸模块名称。
+
+导入映射同样允许你在应用程序中拥有多个版本的依赖项,并使用相同的模块标识符引用它们。你可以通过 `scopes` 键实现这一点,它允许你根据执行导入的脚本路径提供模块标识符映射。下面的示例演示了这一点。
+
+```json
+{
+ "imports": {
+ "cool-module": "/node_modules/cool-module/index.js"
+ },
+ "scopes": {
+ "/node_modules/dependency/": {
+ "cool-module": "/node_modules/some/other/location/cool-module/index.js"
+ }
+ }
+}
+```
+
+有了这个映射,如果一个 URL 包含 `/node_modules/dependency/` 的脚本导入 `cool-module`,将使用 `/node_modules/some/other/location/cool-module/index.js` 中的版本。如果域限映射中没有匹配的作用域,或者匹配的作用域中没有匹配的标识符,则使用 `imports` 中的映射作为回退。例如,如果 `cool-module` 是在不匹配作用域路径的脚本中导入的,则会使用 `imports` 中的模块标识符,映射到 `/node_modules/cool-module/index.js` 中的版本。
+
+请注意,用于选择作用域的路径不会影响地址的解析。映射路径中的值不必与作用域路径匹配,且相对路径仍然解析为包含导入映射的脚本的基础 URL。
+
+与模块标识符映射一样,你可以有多个作用域键,并且这些键可能包含重叠的路径。如果多个作用域匹配引用 URL,则首先检查最具体的作用域路径(最长的作用域键)以查找匹配的标识符。如果没有匹配的标识符,浏览器将回退到下一个最具体的匹配作用域路径,依此类推。如果在任何匹配的作用域中都没有匹配的标识符,浏览器会检查 `imports` 键中的模块标识符映射以查找匹配项。
+
+### 通过映射去除哈希文件名以改进缓存
+
+网站使用的脚本文件通常具有哈希文件名以简化缓存。这种方法的缺点是,如果模块发生变化,任何使用其哈希文件名导入它的模块也需要更新/重新生成。这可能导致更新的级联,浪费网络资源。
+
+导入映射提供了一个方便的解决方案。应用程序和脚本依赖于未哈希版本的模块名称(地址),而不是依赖于特定的哈希文件名。如下所示的导入映射提供了到实际脚本文件的映射。
+
+```json
+{
+ "imports": {
+ "main_script": "/node/srcs/application-fg7744e1b.js",
+ "dependency_script": "/node/srcs/dependency-3qn7e4b1q.js"
+ }
+}
+```
+
+如果 `dependency_script` 发生变化,那么其文件名中的哈希也会发生变化。在这种情况下,我们只需要更新导入映射以反映模块名称的变化。我们不需要更新任何依赖它的 JavaScript 代码的源代码,因为导入语句中的标识符不会改变。
+
+## 加载非 JavaScript 资源
+
+统一模块架构带来的一个令人兴奋的功能是能够将非 JavaScript 资源作为模块加载。例如,你可以将 JSON 作为 JavaScript 对象导入,或将 CSS 作为 {{domxref("CSSStyleSheet")}} 对象导入。
+
+你必须明确声明你正在导入哪种资源。默认情况下,浏览器假定资源是 JavaScript,如果解析的资源是其他类型,将抛出错误。要导入 JSON、CSS 或其他类型的资源,请使用[导入属性](/zh-CN/docs/Web/JavaScript/Reference/Statements/import/with)语法:
+
+```js
+import colors from "./colors.json" with { type: "json" };
+import styles from "./styles.css" with { type: "css" };
+```
+
+浏览器还会对模块类型进行验证,如果 `./data.json` 不是 JSON 文件,将会失败。这确保了你在仅打算导入数据时不会意外执行代码。一旦成功导入,你现在可以像使用普通 JavaScript 对象或 `CSSStyleSheet` 对象一样使用导入的值。
+
+```js
+console.log(colors.map((color) => color.value));
+document.adoptedStyleSheets = [styles];
+```
+
+## 将模块应用到你的 HTML
+
+现在我们只需要将 `main.js` 模块应用到我们的 HTML 页面。这与将常规脚本应用到页面非常相似,但有一些显著的区别。
+
+首先,你需要在 [`
```
-你导入模块功能的脚本基本是作为顶级模块。如果省略它,Firefox 就会给出错误“SyntaxError: import declarations may only appear at top level of a module。
+你也可以将模块的脚本直接嵌入到 HTML 文件中,将 JavaScript 代码放在 `
+```
+
+你只能在模块内使用 `import` 和 `export` 语句,不能在常规脚本中使用。如果你的 `
+
+
+```
-你只能在模块内部使用 `import` 和`export` 语句;不是普通脚本文件。
+通常,你应该在单独的文件中定义所有模块。内联声明在 HTML 中的模块只能导入其他模块,但它们导出的任何内容将无法被其他模块访问(因为它们没有 URL)。
> [!NOTE]
-> 你还可以将模块导入内部脚本,只要包含 `type="module"`,例如 ``。
+> 可以通过在 [``](/zh-CN/docs/Web/HTML/Element/link) 元素中指定 [`rel="modulepreload"`](/zh-CN/docs/Web/HTML/Attributes/rel/modulepreload) 来预加载模块及其依赖项。这可以显著减少使用模块时的加载时间。
-## 其他模块与标准脚本的不同
+## 模块与经典脚本的其他不同
- 你需要注意本地测试——如果你通过本地加载 HTML 文件(比如一个 `file://` 路径的文件),你将会遇到 CORS 错误,因为 JavaScript 模块安全性需要。你需要通过一个服务器来测试。
-- 另请注意,你可能会从模块内部定义的脚本部分获得与标准脚本中不同的行为。这是因为模块自动使用严格模式。
-- 加载一个模块脚本时不需要使用 `defer` 属性 (see [`
+
+