知海

ECMAScript 模块

webpackjsorg-main指南-教程

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/javascriptapplication/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 中不可用:requiremoduleexports__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 全局变量(requiremoduleexports__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,以便在迁移期间禁用该检查。

帮助我们改进文档

发现翻译问题或内容错误?请告诉我们。