ECMAScript 模块
ECMAScript 模块
ECMAScript 模块(ESM)是在 Web 中使用模块的规范。所有现代浏览器都支持它,并且是在 Web 上编写模块化代码的推荐方式。
Webpack 支持处理 ECMAScript 模块以对其进行优化。
导出
export 关键字允许将 ESM 中的内容暴露给其他模块:
js
export const CONSTANT = 42;
export let variable = 42;
// only reading is exposed
// it's not possible to modify the variable from outside
export function fun() {
console.log("fun");
}
export class C extends Super {
method() {
console.log("method");
}
}
let a, b, other;
export { a, b, other as c };
export default 1 + 2 + 3 + more();
导入
import 关键字允许在 ESM 中获取对其他模块中内容的引用:
js
// import "bindings" to exports from another module
// these bindings are live. The values are not copied,
// instead accessing "variable" will get the current value
// in the imported module
import { CONSTANT, variable } from "./module.js";
// shortcut to import the "default" export
import theDefaultValue from "./module.js";
// import the "namespace object" which contains all exports
import * as module from "./module.js";
module.fun();
当从 ECMAScript 模块导入命名空间对象时,webpack 遵循 ESM 约定,在命名空间对象上设置 Symbol.toStringTag 为 "Module"。
将模块标记为 ESM
默认情况下,webpack 会自动检测一个文件是 ESM 还是其他模块系统。
Node.js 建立了一种通过在 package.json 中使用属性来显式设置文件模块类型的方式。在 package.json 中设置 "type": "module" 会强制该 package.json 以下的所有文件成为 ECMAScript 模块。设置 "type": "commonjs" 则会强制它们成为 CommonJS 模块。
json
{
"type": "module"
}
除此之外,文件可以通过使用 .mjs 或 .cjs 扩展名来设置模块类型。.mjs 会强制文件为 ESM,.cjs 强制为 CommonJS。
在 DataURI 中使用 text/javascript 或 application/javascript MIME 类型也会将模块类型强制为 ESM。
除了模块格式之外,将模块标记为 ESM 还会影响解析逻辑、互操作逻辑以及模块中可用的符号。
ESM 中的 import.meta
Webpack 暴露了多个 import.meta 属性供 ESM 使用:
| 属性 | 描述 |
|---|---|
import.meta.url |
当前模块文件的 URL —— 可用于 new Worker() 或 new URL() |
import.meta.webpack |
webpack 主版本号(例如 5) |
import.meta.webpackHot |
相当于 module.hot —— 用于 ESM 中的 HMR |
import.meta.webpackContext |
require.context 的 ESM 等价物 |
示例 —— 使用 import.meta.url 处理资源:
js
// Resolve a sibling file relative to the current module
const iconUrl = new URL("./icon.png", import.meta.url);
const img = document.createElement("img");
img.src = iconUrl.href;
示例 —— 在 ESM 中进行 HMR:
js
if (import.meta.webpackHot) {
import.meta.webpackHot.accept("./module.js", () => {
// handle update
});
}
顶层 Await
在 ESM 中,你可以在模块的顶层使用 await。Webpack 会自动将该模块视为异步模块。自 5.83.0 起默认启用;experiments.topLevelAwait 选项本身已在 5.102.0 中移除(它开箱即用)。
警告: 当目标是浏览器时,应避免在入口点使用顶层 await。它会延迟整个模块图的求值。对于延迟加载,请优先使用
import()。对于 Node.js、Electron 或 Web Worker 目标,此限制不适用。
js
// user.js (async ESM module)
const response = await fetch("/api/user");
export const user = await response.json();
js
// index.js - importing an async module works as expected
import { user } from "./user.js";
console.log(user.name);
完全指定的导入
ESM 中的导入解析更加严格。当文件被标记为 ESM 时,相对请求必须包含文件扩展名(例如 *.js 或 *.mjs),遵循 Node.js 约定:
js
// will fail - missing extension
import { helper as missingExt } from "./utils";
// correct in ESM
import { helper } from "./utils.js";
提示: 对包的请求(例如
import "lodash")仍然受支持。
要禁用此检查(在迁移大型 CJS 代码库时很有用),可以使用 fullySpecified=false:
js
// webpack.config.js
export default {
module: {
rules: [
{
test: /\.m?js/,
resolve: {
fullySpecified: false,
},
},
],
},
};
CommonJS 互操作
CommonJS 语法在 ESM 中不可用:require、module、exports、__filename、__dirname。
当在 ESM 中从 CommonJS 模块导入时,只有 default 导出可用(即整个 module.exports 对象):
js
// esm-consumer.js (ESM)
import cjs from "./cjs-module.js";
// named imports from CJS don't work
import { foo } from "./cjs-module.js"; // undefined
// cjs-module.js (CommonJS)
module.exports = { foo: 1, bar: 2 };
console.log(cjs.foo); // works - cjs is the whole exports object
当 webpack 将被导入的模块视为 CommonJS 时,这种严格行为适用。如果该模块本身使用 ESM export 语法,webpack 会自动将其检测为 ESM,命名导入将正常工作。这通常会影响那些在设置了 "type": "module" 的项目中混用 .js 文件的项目——webpack 可能将某些文件视为 ESM,而 node_modules 中的第三方包保持为 CommonJS。
提示: 要从 CommonJS 模块获得命名导出,请考虑迁移到 ESM,或使用
@babel/plugin-transform-modules-commonjs。
常见的迁移错误
ReferenceError: require is not defined
当文件被视为 ESM 时,CommonJS 全局变量(require、module、exports、__filename、__dirname)不可用。
修复方法:将 require() 替换为 import 语句。对于条件加载或动态加载,请使用 import()。
Must use import to load ES Module(Node.js)/ SyntaxError: Cannot use import statement in a module(浏览器)
当使用 ESM import/export 语法的文件未被标记为 ESM 时,会发生此错误——要么是 package.json 中缺少 "type": "module",要么是文件使用了 .js 扩展名而不是 .mjs。
修复方法:在 package.json 中添加 "type": "module",或将文件重命名为 .mjs。
Module not found: Error: Can't resolve './utils'(缺少扩展名)
在 ESM 中,相对导入必须包含文件扩展名。Webpack 在此遵循 Node.js 的 ESM 约定。
修复方法:将 import { helper } from './utils' 改为 import { helper } from './utils.js',或在 webpack 配置中设置 fullySpecified: false,以便在迁移期间禁用该检查。
帮助我们改进文档
发现翻译问题或内容错误?请告诉我们。
