diff --git a/src/content/configuration/experiments.mdx b/src/content/configuration/experiments.mdx
index 2f893988059d..a76407919858 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 [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.
+
+- 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.
+
+Enables 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 incompatible with 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..9ee5bcdda970 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 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 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: