Skip to content

Commit ad93cca

Browse files
committed
docs(guides): expand ESM guide with import.meta, top-level await, and migration errors
1 parent 85d4e24 commit ad93cca

1 file changed

Lines changed: 136 additions & 4 deletions

File tree

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

Lines changed: 136 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,143 @@ 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+
export const user = await response.json();
129+
```
130+
131+
```js
132+
// index.js - importing an async module works as expected
133+
import { user } from "./user.js";
134+
console.log(user.name);
135+
```
136+
137+
## Fully Specified Imports
138+
139+
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:
140+
141+
```js
142+
// correct in ESM
143+
import { helper } from "./utils.js";
144+
145+
// will fail - missing extension
146+
import { helper } from "./utils";
147+
```
88148
89149
T> Requests to packages e.g. `import "lodash"` are still supported.
90150
91-
Only the "default" export can be imported from non-ESM. Named exports are not available.
151+
To disable this check (useful when migrating a large CJS codebase), you can use [`fullySpecified=false`](/configuration/module/#resolvefullyspecified):
152+
153+
```js
154+
// webpack.config.js
155+
export default {
156+
module: {
157+
rules: [
158+
{
159+
test: /\.m?js/,
160+
resolve: {
161+
fullySpecified: false,
162+
},
163+
},
164+
],
165+
},
166+
};
167+
```
168+
169+
## CommonJS Interop
170+
171+
CommonJS syntax is not available in ESM: `require`, `module`, `exports`, `__filename`, `__dirname`.
172+
173+
When importing from a CommonJS module inside ESM, only the `default` export
174+
is available (the entire `module.exports` object):
175+
176+
```js
177+
// cjs-module.js (CommonJS)
178+
module.exports = { foo: 1, bar: 2 };
179+
180+
// esm-consumer.js (ESM)
181+
import cjs from "./cjs-module.js";
182+
console.log(cjs.foo); // works - cjs is the whole exports object
183+
184+
// named imports from CJS don't work
185+
import { foo } from "./cjs-module.js"; // undefined
186+
```
187+
188+
This strict behavior applies when webpack treats the **imported** module as CommonJS.
189+
If that module itself uses ESM `export` syntax, webpack will auto-detect it as ESM
190+
and named imports will work normally. This commonly affects projects that mix `.js` files
191+
files in a project that has `"type": "module"` set - webpack may treat some files as
192+
ESM while third-party packages in `node_modules` remain CommonJS.
193+
194+
T> To get named exports from CommonJS modules, consider migrating to ESM
195+
T> or using [`@babel/plugin-transform-modules-commonjs`](https://babeljs.io/docs/babel-plugin-transform-modules-commonjs).
196+
197+
## Common Migration Errors
198+
199+
**`ReferenceError: require is not defined`**
200+
201+
When a file is treated as ESM, CommonJS globals (`require`, `module`, `exports`,
202+
`__filename`, `__dirname`) are unavailable.
203+
204+
_Fix_: Replace `require()` with `import` statements. For conditional or dynamic
205+
loading, use `import()`.
206+
207+
---
208+
209+
**`Must use import to load ES Module`** (Node.js) / **`SyntaxError: Cannot use import
210+
statement in a module`** (browser)
211+
212+
This happens when a file using ESM `import`/`export` syntax is not flagged as ESM -
213+
either `"type": "module"` is missing from `package.json`, or the file uses a `.js`
214+
extension instead of `.mjs`.
215+
216+
_Fix_: Add `"type": "module"` to your `package.json`, or rename the file to `.mjs`.
217+
218+
---
219+
220+
**`Module not found: Error: Can't resolve './utils'` (missing extension)**
92221
93-
CommonJs Syntax is not available: `require`, `module`, `exports`, `__filename`, `__dirname`.
222+
In ESM, relative imports must include the file extension. Webpack follows the Node.js
223+
ESM convention here.
94224
95-
T> HMR can be used with [`import.meta.webpackHot`](/api/module-variables/#importmetawebpackhot) instead of [`module.hot`](/api/module-variables/#modulehot-webpack-specific).
225+
_Fix_: Change `import { helper } from './utils'` to `import { helper } from './utils.js'`,
226+
or set [`fullySpecified: false`](/configuration/module/#resolvefullyspecified) in your
227+
webpack config to disable the check while migrating.

0 commit comments

Comments
 (0)