From 7859db89400c435dc225a8d2d5676454f7a3d93c Mon Sep 17 00:00:00 2001
From: Jack Works <5390719+Jack-Works@users.noreply.github.com>
Date: Thu, 3 Jul 2025 12:03:00 +0800
Subject: [PATCH 1/3] docs: add deferImport
---
src/content/configuration/experiments.mdx | 56 +++++++++-
src/content/guides/lazy-loading.mdx | 120 +++++++++++++++++++++-
2 files changed, 172 insertions(+), 4 deletions(-)
diff --git a/src/content/configuration/experiments.mdx b/src/content/configuration/experiments.mdx
index 2f893988059d..ffa29aecd1fe 100644
--- a/src/content/configuration/experiments.mdx
+++ b/src/content/configuration/experiments.mdx
@@ -25,6 +25,7 @@ Available options:
- [`buildHttp`](#experimentsbuildhttp)
- [`cacheUnaffected`](#experimentscacheunaffected)
- [`css`](#experimentscss)
+- [`deferImport`](#experimentsdeferimport)
- [`futureDefaults`](#experimentsfuturedefaults)
- `layers`: Enable module and chunk layers.
- [`lazyCompilation`](#experimentslazycompilation)
@@ -190,9 +191,9 @@ Enable native CSS support. Note that it's an experimental feature still under de
Experimental features:
- CSS Modules support: webpack will generate a unique name for each CSS class. Use the `.module.css` extension for CSS Modules.
-- Style-specific fields resolution in `package.json` files:
- webpack will look for `style` field in `package.json` files and use that if it
- exists for imports inside CSS files.
+- Style-specific fields resolution in `package.json`
+ files: webpack will look for `style` field in `package.json` files and use
+ that if it exists for imports inside CSS files.
For example, if you add `@import 'bootstrap';` to your CSS file, webpack will look for `bootstrap` in `node_modules` and use the `style` field in `package.json` from there. If `style` field is not found, webpack will use the `main` field instead to preserve backward compatibility.
@@ -209,6 +210,55 @@ Enable additional in-memory caching of modules which are unchanged and reference
Defaults to the value of [`futureDefaults`](#experimentsfuturedefaults).
+### experiments.deferImport
+
+Enable support of the tc39 proposal [Deferring Module Evaluation](https://github.com/tc39/proposal-defer-import-eval).
+This allows deferring the evaluation of a module until its first use.
+This is useful when using dynamic `import()` is problematic.
+
+- Type: `boolean`
+
+This feature requires the runtime environment to have [`Proxy`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) (ES6) support.
+
+Enable the following syntaxes:
+
+```js
+import defer * as module from 'module-name';
+// or
+import * as module2 from /* webpackDefer: true */ 'module-name2';
+
+export function f() {
+ // module-name is evaluated synchronously, then call doSomething() on it.
+ module.doSomething();
+}
+```
+
+#### Limitations of magic comments (`/* webpackDefer: true */`)
+
+It's suggested to put the magic comment after the `from` keyword, other positions may work, but have not been tested.
+
+Putting the magic comment after the `import` keyword is known to be broken, it will break the filesystem cache.
+
+```js
+import /* webpackDefer: true */ * as ns from '...'; // known broken
+import * as ns from /* webpackDefer: true */ '...'; // recommended
+```
+
+You should make sure your loaders do not remove the magic comment.
+
+TypeScript, Babel, SWC, and Flow.js can be configured to preserve the magic comment.
+
+Esbuild is _not_ compatible with this feature (see [evanw/esbuild#1439](https://github.com/evanw/esbuild/issues/1439) and [evanw/esbuild#309](https://github.com/evanw/esbuild/issues/309)), but it may support this in the future.
+
+#### Not implemented
+
+The asynchronous form of this proposal is not implemented yet.
+
+```js
+import.defer('module-name'); // not implemented
+import(/* webpackDefer: true */ 'module-name'); // not implemented
+```
+
### experiments.futureDefaults
Use defaults of the next major webpack and show warnings in any problematic places.
diff --git a/src/content/guides/lazy-loading.mdx b/src/content/guides/lazy-loading.mdx
index 80c1efeeb2cf..40f33eb53c8c 100644
--- a/src/content/guides/lazy-loading.mdx
+++ b/src/content/guides/lazy-loading.mdx
@@ -21,7 +21,7 @@ T> This guide is a small follow-up to [Code Splitting](/guides/code-splitting).
Lazy, or "on demand", loading is a great way to optimize your site or application. This practice essentially involves splitting your code at logical breakpoints, and then loading it once the user has done something that requires, or will require, a new block of code. This speeds up the initial load of the application and lightens its overall weight as some blocks may never even be loaded.
-## Example
+## Dynamic Import Example
Let's take the example from [Code Splitting](/guides/code-splitting/#dynamic-imports) and tweak it a bit to demonstrate this concept even more. The code there does cause a separate chunk, `lodash.bundle.js`, to be generated and technically "lazy-loads" it as soon as the script is run. The trouble is that no user interaction is required to load the bundle – meaning that every time the page is loaded, the request will fire. This doesn't help us too much and will impact performance negatively.
@@ -100,6 +100,124 @@ index.bundle.js 548 kB 1 [emitted] [big] index
...
```
+## Defer Import Example
+
+W> This section does not lazy "load" a module, but lazy "evaluate" a module. This means that the module is still downloaded and parsed, but its evaluation is lazy.
+
+In some cases, it might be annoying or hard to convert all uses of a module to asynchronous, since it enforces the unnecessary asyncification of all functions, without providing the ability to only defer the synchronous evaluation work.
+
+The tc39 proposal [Deferring Module Evaluation](https://github.com/tc39/proposal-defer-import-eval) is to solve this problem.
+
+> The proposal is to have a new syntactical import form which will only ever return a namespace exotic object. When used, the module and its dependencies would not be executed, but would be fully loaded to the point of being execution-ready before the module graph is considered loaded.
+>
+> _Only when accessing a property of this module, would the execution operations be performed (if needed)._
+
+This feature is available by enabling [experiments.deferImport](/configuration/experiments/#experimentsdeferimport).
+
+W> This feature is still in the experimental stage and may change in future versions of webpack.
+
+**project**
+
+```diff
+webpack-demo
+|- package.json
+|- package-lock.json
+|- webpack.config.js
+|- /dist
+|- /src
+ |- index.js
++ |- print.js
+|- /node_modules
+```
+
+**src/print.js**
+
+```js
+console.log(
+ 'The print.js module has loaded! See the network tab in dev tools...'
+);
+
+export default () => {
+ console.log('Button Clicked: Here\'s "some text"!');
+};
+```
+
+**src/index.js**
+
+```diff
+ import _ from 'lodash';
++ import defer * as print from './print';
+
+ function component() {
+ const element = document.createElement('div');
+ const button = document.createElement('button');
+ const br = document.createElement('br');
+
+ button.innerHTML = 'Click me and look at the console!';
+ element.innerHTML = _.join(['Hello', 'webpack'], ' ');
+ element.appendChild(br);
+ element.appendChild(button);
+
+- // Note that because a network request is involved, some indication
+- // of loading would need to be shown in a production-level site/app.
++ // In this example, the print module is downloaded but not evaluated,
++ // so there is no network request involved after the button is clicked.
+- button.onclick = e => import(/* webpackChunkName: "print" */ './print').then(module => {
++ button.onclick = e => {
+ const print = module.default;
++ // ^ The module is evaluated here.
+
+ print();
+- });
++ };
+
+ return element;
+ }
+
+ getComponent().then(component => {
+ document.body.appendChild(component);
+ });
+ document.body.appendChild(component());
+```
+
+This is similar to the CommonJS style of lazy loading:
+
+**src/index.js**
+
+```diff
+ import _ from 'lodash';
+- import defer * as print from './print';
+
+ function component() {
+ const element = document.createElement('div');
+ const button = document.createElement('button');
+ const br = document.createElement('br');
+
+ button.innerHTML = 'Click me and look at the console!';
+ element.innerHTML = _.join(['Hello', 'webpack'], ' ');
+ element.appendChild(br);
+ element.appendChild(button);
+
+ // In this example, the print module is downloaded but not evaluated,
+ // so there is no network request involved after the button is clicked.
+ button.onclick = e => {
++ const print = require('./print');
++ // ^ The module is evaluated here.
+ const print = module.default;
+- // ^ The module is evaluated here.
+
+ print();
+ };
+
+ return element;
+ }
+
+ getComponent().then(component => {
+ document.body.appendChild(component);
+ });
+ document.body.appendChild(component());
+```
+
## Frameworks
Many frameworks and libraries have their own recommendations on how this should be accomplished within their methodologies. Here are a few examples:
From 81cd76f9a0d2780eea3c57deb66c709963f37930 Mon Sep 17 00:00:00 2001
From: Jack Works <5390719+Jack-Works@users.noreply.github.com>
Date: Mon, 7 Jul 2025 20:33:43 +0800
Subject: [PATCH 2/3] Apply suggestions from code review
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: Nicolò Ribaudo
---
src/content/configuration/experiments.mdx | 8 ++++----
src/content/guides/lazy-loading.mdx | 4 ++--
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/src/content/configuration/experiments.mdx b/src/content/configuration/experiments.mdx
index ffa29aecd1fe..1088a641dc18 100644
--- a/src/content/configuration/experiments.mdx
+++ b/src/content/configuration/experiments.mdx
@@ -214,13 +214,13 @@ Defaults to the value of [`futureDefaults`](#experimentsfuturedefaults).
Enable support of the tc39 proposal [Deferring Module Evaluation](https://github.com/tc39/proposal-defer-import-eval).
This allows deferring the evaluation of a module until its first use.
-This is useful when using dynamic `import()` is problematic.
+This is useful to synchronously defer code execution when it's not possible to use `import()` due to its asynchronous nature.
- Type: `boolean`
This feature requires the runtime environment to have [`Proxy`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) (ES6) support.
-Enable the following syntaxes:
+Enables the following syntaxes:
```js
import defer * as module from 'module-name';
@@ -235,9 +235,9 @@ export function f() {
#### Limitations of magic comments (`/* webpackDefer: true */`)
-It's suggested to put the magic comment after the `from` keyword, other positions may work, but have not been tested.
+It's suggested to put the magic comment after the `from` keyword. Other positions may work, but have not been tested.
-Putting the magic comment after the `import` keyword is known to be broken, it will break the filesystem cache.
+Putting the magic comment after the `import` keyword is incompatible with the filesystem cache.
```js
import /* webpackDefer: true */ * as ns from '...'; // known broken
diff --git a/src/content/guides/lazy-loading.mdx b/src/content/guides/lazy-loading.mdx
index 40f33eb53c8c..9ee5bcdda970 100644
--- a/src/content/guides/lazy-loading.mdx
+++ b/src/content/guides/lazy-loading.mdx
@@ -102,11 +102,11 @@ index.bundle.js 548 kB 1 [emitted] [big] index
## Defer Import Example
-W> This section does not lazy "load" a module, but lazy "evaluate" a module. This means that the module is still downloaded and parsed, but its evaluation is lazy.
+W> This feature does not lazily "load" a module, but it lazily "evaluates" a module. This means that the module is still downloaded and parsed, but its evaluation is lazy.
In some cases, it might be annoying or hard to convert all uses of a module to asynchronous, since it enforces the unnecessary asyncification of all functions, without providing the ability to only defer the synchronous evaluation work.
-The tc39 proposal [Deferring Module Evaluation](https://github.com/tc39/proposal-defer-import-eval) is to solve this problem.
+The TC39 proposal [Deferring Module Evaluation](https://github.com/tc39/proposal-defer-import-eval) is to solve this problem.
> The proposal is to have a new syntactical import form which will only ever return a namespace exotic object. When used, the module and its dependencies would not be executed, but would be fully loaded to the point of being execution-ready before the module graph is considered loaded.
>
From be5023375b3fd1ba199b36a591da515595a7d493 Mon Sep 17 00:00:00 2001
From: Jack Works <5390719+Jack-Works@users.noreply.github.com>
Date: Mon, 7 Jul 2025 20:34:08 +0800
Subject: [PATCH 3/3] apply review
---
src/content/configuration/experiments.mdx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/content/configuration/experiments.mdx b/src/content/configuration/experiments.mdx
index 1088a641dc18..a76407919858 100644
--- a/src/content/configuration/experiments.mdx
+++ b/src/content/configuration/experiments.mdx
@@ -212,7 +212,7 @@ Defaults to the value of [`futureDefaults`](#experimentsfuturedefaults).
### experiments.deferImport
-Enable support of the tc39 proposal [Deferring Module Evaluation](https://github.com/tc39/proposal-defer-import-eval).
+Enable support of the tc39 proposal [the `import defer` proposal](https://github.com/tc39/proposal-defer-import-eval).
This allows deferring the evaluation of a module until its first use.
This is useful to synchronously defer code execution when it's not possible to use `import()` due to its asynchronous nature.