diff --git a/src/content/guides/ecma-script-modules.mdx b/src/content/guides/ecma-script-modules.mdx index d6a1f5931306..b6bf736111b5 100644 --- a/src/content/guides/ecma-script-modules.mdx +++ b/src/content/guides/ecma-script-modules.mdx @@ -3,6 +3,7 @@ title: ECMAScript Modules sort: 19 contributors: - sokra + - ryzrr related: - title: ECMAScript Modules in Node.js url: https://nodejs.org/api/esm.html @@ -84,12 +85,145 @@ In DataURIs using the `text/javascript` or `application/javascript` mime type wi In addition to the module format, flagging modules as ESM also affect the resolving logic, interop logic and the available symbols in modules. -Imports in ESM are resolved more strictly. Relative requests must include a filename and file extension (e.g. `*.js` or `*.mjs`) unless you have the behaviour disabled with [`fullySpecified=false`](/configuration/module/#resolvefullyspecified). +## import.meta in ESM + +Webpack exposes several `import.meta` properties for use in ESM: + +| Property | Description | +| ---------------------------- | ---------------------------------------------------------------------------------------------- | +| `import.meta.url` | The URL of the current module file - use it for `new Worker()` or `new URL()` | +| `import.meta.webpack` | The webpack major version number (e.g. `5`) | +| `import.meta.webpackHot` | Equivalent of `module.hot` - use for HMR in ESM | +| `import.meta.webpackContext` | [ESM equivalent of `require.context`](/guides/dependency-management/#importmetawebpackcontext) | + +**Example - using `import.meta.url` for assets:** + +```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; +``` + +**Example - HMR in ESM:** + +```js +if (import.meta.webpackHot) { + import.meta.webpackHot.accept("./module.js", () => { + // handle update + }); +} +``` + +## Top-Level Await + +In ESM, you can use `await` at the top level of a module. Webpack treats the module +as an async module automatically. Enabled by default since 5.83.0; the `experiments.topLevelAwait` option itself was removed in 5.102.0 (it just works). + +W> Avoid top-level await in your entry point when targeting the **browser**. It delays the entire module graph evaluation. Prefer `import()` for deferred loading. For Node.js, Electron, or Web Worker targets this restriction does not apply. + +```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); +``` + +## Fully Specified Imports + +Imports in ESM are resolved more strictly. Relative requests must include a file extension (e.g. `*.js` or `*.mjs`) following the Node.js convention when the file is flagged as ESM: + +```js +// will fail - missing extension +import { helper as missingExt } from "./utils"; + +// correct in ESM +import { helper } from "./utils.js"; +``` T> Requests to packages e.g. `import "lodash"` are still supported. -Only the "default" export can be imported from non-ESM. Named exports are not available. +To disable this check (useful when migrating a large CJS codebase), you can use [`fullySpecified=false`](/configuration/module/#resolvefullyspecified): + +```js +// webpack.config.js +export default { + module: { + rules: [ + { + test: /\.m?js/, + resolve: { + fullySpecified: false, + }, + }, + ], + }, +}; +``` + +## CommonJS Interop + +CommonJS syntax is not available in ESM: `require`, `module`, `exports`, `__filename`, `__dirname`. + +When importing from a CommonJS module inside ESM, only the `default` export +is available (the entire `module.exports` object): + +```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 +``` + +This strict behavior applies when webpack treats the **imported** module as CommonJS. +If that module itself uses ESM `export` syntax, webpack will auto-detect it as ESM +and named imports will work normally. This commonly affects projects that mix `.js` files +files in a project that has `"type": "module"` set - webpack may treat some files as +ESM while third-party packages in `node_modules` remain CommonJS. + +T> To get named exports from CommonJS modules, consider migrating to ESM +T> or using [`@babel/plugin-transform-modules-commonjs`](https://babeljs.io/docs/babel-plugin-transform-modules-commonjs). + +## Common Migration Errors + +**`ReferenceError: require is not defined`** + +When a file is treated as ESM, CommonJS globals (`require`, `module`, `exports`, +`__filename`, `__dirname`) are unavailable. + +_Fix_: Replace `require()` with `import` statements. For conditional or dynamic +loading, use `import()`. + +--- + +**`Must use import to load ES Module`** (Node.js) / **`SyntaxError: Cannot use import +statement in a module`** (browser) + +This happens when a file using ESM `import`/`export` syntax is not flagged as ESM - +either `"type": "module"` is missing from `package.json`, or the file uses a `.js` +extension instead of `.mjs`. + +_Fix_: Add `"type": "module"` to your `package.json`, or rename the file to `.mjs`. + +--- + +**`Module not found: Error: Can't resolve './utils'` (missing extension)** -CommonJs Syntax is not available: `require`, `module`, `exports`, `__filename`, `__dirname`. +In ESM, relative imports must include the file extension. Webpack follows the Node.js +ESM convention here. -T> HMR can be used with [`import.meta.webpackHot`](/api/module-variables/#importmetawebpackhot) instead of [`module.hot`](/api/module-variables/#modulehot-webpack-specific). +_Fix_: Change `import { helper } from './utils'` to `import { helper } from './utils.js'`, +or set [`fullySpecified: false`](/configuration/module/#resolvefullyspecified) in your +webpack config to disable the check while migrating.