Skip to content

Commit 4859143

Browse files
authored
docs(guides): expand ESM guide with import.meta, top-level await, and… (#8207)
1 parent b9ad103 commit 4859143

1 file changed

Lines changed: 138 additions & 4 deletions

File tree

src/content/guides/ecma-script-modules.mdx

Lines changed: 138 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ title: ECMAScript Modules
33
sort: 19
44
contributors:
55
- sokra
6+
- ryzrr
67
related:
78
- title: ECMAScript Modules in Node.js
89
url: https://nodejs.org/api/esm.html
@@ -84,12 +85,145 @@ In DataURIs using the `text/javascript` or `application/javascript` mime type wi
8485

8586
In addition to the module format, flagging modules as ESM also affect the resolving logic, interop logic and the available symbols in modules.
8687

87-
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).
88+
## import.meta in ESM
89+
90+
Webpack exposes several `import.meta` properties for use in ESM:
91+
92+
| Property | Description |
93+
| ---------------------------- | ---------------------------------------------------------------------------------------------- |
94+
| `import.meta.url` | The URL of the current module file - use it for `new Worker()` or `new URL()` |
95+
| `import.meta.webpack` | The webpack major version number (e.g. `5`) |
96+
| `import.meta.webpackHot` | Equivalent of `module.hot` - use for HMR in ESM |
97+
| `import.meta.webpackContext` | [ESM equivalent of `require.context`](/guides/dependency-management/#importmetawebpackcontext) |
98+
99+
**Example - using `import.meta.url` for assets:**
100+
101+
```js
102+
// Resolve a sibling file relative to the current module
103+
const iconUrl = new URL("./icon.png", import.meta.url);
104+
const img = document.createElement("img");
105+
img.src = iconUrl.href;
106+
```
107+
108+
**Example - HMR in ESM:**
109+
110+
```js
111+
if (import.meta.webpackHot) {
112+
import.meta.webpackHot.accept("./module.js", () => {
113+
// handle update
114+
});
115+
}
116+
```
117+
118+
## Top-Level Await
119+
120+
In ESM, you can use `await` at the top level of a module. Webpack treats the module
121+
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).
122+
123+
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.
124+
125+
```js
126+
// user.js (async ESM module)
127+
const response = await fetch("/api/user");
128+
129+
export const user = await response.json();
130+
```
131+
132+
```js
133+
// index.js - importing an async module works as expected
134+
import { user } from "./user.js";
135+
136+
console.log(user.name);
137+
```
138+
139+
## Fully Specified Imports
140+
141+
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:
142+
143+
```js
144+
// will fail - missing extension
145+
import { helper as missingExt } from "./utils";
146+
147+
// correct in ESM
148+
import { helper } from "./utils.js";
149+
```
88150
89151
T> Requests to packages e.g. `import "lodash"` are still supported.
90152
91-
Only the "default" export can be imported from non-ESM. Named exports are not available.
153+
To disable this check (useful when migrating a large CJS codebase), you can use [`fullySpecified=false`](/configuration/module/#resolvefullyspecified):
154+
155+
```js
156+
// webpack.config.js
157+
export default {
158+
module: {
159+
rules: [
160+
{
161+
test: /\.m?js/,
162+
resolve: {
163+
fullySpecified: false,
164+
},
165+
},
166+
],
167+
},
168+
};
169+
```
170+
171+
## CommonJS Interop
172+
173+
CommonJS syntax is not available in ESM: `require`, `module`, `exports`, `__filename`, `__dirname`.
174+
175+
When importing from a CommonJS module inside ESM, only the `default` export
176+
is available (the entire `module.exports` object):
177+
178+
```js
179+
// esm-consumer.js (ESM)
180+
import cjs from "./cjs-module.js";
181+
// named imports from CJS don't work
182+
import { foo } from "./cjs-module.js"; // undefined
183+
184+
// cjs-module.js (CommonJS)
185+
module.exports = { foo: 1, bar: 2 };
186+
187+
console.log(cjs.foo); // works - cjs is the whole exports object
188+
```
189+
190+
This strict behavior applies when webpack treats the **imported** module as CommonJS.
191+
If that module itself uses ESM `export` syntax, webpack will auto-detect it as ESM
192+
and named imports will work normally. This commonly affects projects that mix `.js` files
193+
files in a project that has `"type": "module"` set - webpack may treat some files as
194+
ESM while third-party packages in `node_modules` remain CommonJS.
195+
196+
T> To get named exports from CommonJS modules, consider migrating to ESM
197+
T> or using [`@babel/plugin-transform-modules-commonjs`](https://babeljs.io/docs/babel-plugin-transform-modules-commonjs).
198+
199+
## Common Migration Errors
200+
201+
**`ReferenceError: require is not defined`**
202+
203+
When a file is treated as ESM, CommonJS globals (`require`, `module`, `exports`,
204+
`__filename`, `__dirname`) are unavailable.
205+
206+
_Fix_: Replace `require()` with `import` statements. For conditional or dynamic
207+
loading, use `import()`.
208+
209+
---
210+
211+
**`Must use import to load ES Module`** (Node.js) / **`SyntaxError: Cannot use import
212+
statement in a module`** (browser)
213+
214+
This happens when a file using ESM `import`/`export` syntax is not flagged as ESM -
215+
either `"type": "module"` is missing from `package.json`, or the file uses a `.js`
216+
extension instead of `.mjs`.
217+
218+
_Fix_: Add `"type": "module"` to your `package.json`, or rename the file to `.mjs`.
219+
220+
---
221+
222+
**`Module not found: Error: Can't resolve './utils'` (missing extension)**
92223
93-
CommonJs Syntax is not available: `require`, `module`, `exports`, `__filename`, `__dirname`.
224+
In ESM, relative imports must include the file extension. Webpack follows the Node.js
225+
ESM convention here.
94226
95-
T> HMR can be used with [`import.meta.webpackHot`](/api/module-variables/#importmetawebpackhot) instead of [`module.hot`](/api/module-variables/#modulehot-webpack-specific).
227+
_Fix_: Change `import { helper } from './utils'` to `import { helper } from './utils.js'`,
228+
or set [`fullySpecified: false`](/configuration/module/#resolvefullyspecified) in your
229+
webpack config to disable the check while migrating.

0 commit comments

Comments
 (0)