懒加载

按需加载,或称“懒加载”,是优化您的网站或应用程序的一种绝佳方式。这种做法本质上涉及在逻辑断点处拆分代码,然后在用户执行了需要或将需要新代码块的操作时才加载它。这加快了应用程序的初始加载速度,并减轻了其整体大小,因为某些代码块可能永远不会被加载。

动态导入示例

让我们以《代码分割》中的示例为基础,稍微调整一下,以更深入地演示这个概念。那里的代码确实会生成一个单独的 chunk,lodash.bundle.js,并且在脚本运行后会技术性地“懒加载”它。问题在于加载这个 bundle 不需要任何用户交互——这意味着每次页面加载时,请求都会被触发。这并没有给我们带来太大帮助,反而会对性能产生负面影响。

让我们尝试一些不同的做法。我们将添加一个交互,当用户点击按钮时,向控制台输出一些文本。但是,我们将等到第一次交互发生时才加载该代码(print.js)。为此,我们将回到《代码分割》中最终的*动态导入*示例进行修改,并将 lodash 保留在主 chunk 中。

项目

webpack-demo
|- package.json
|- package-lock.json
|- webpack.config.js
|- /dist
|- /src
  |- index.js
+ |- print.js
|- /node_modules

src/print.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

+ import _ from 'lodash';
+
- async function getComponent() {
+ function component() {
    const element = document.createElement('div');
-   const _ = await import(/* webpackChunkName: "lodash" */ 'lodash');
+   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.
+   button.onclick = e => import(/* webpackChunkName: "print" */ './print').then(module => {
+     const print = module.default;
+
+     print();
+   });

    return element;
  }

- getComponent().then(component => {
-   document.body.appendChild(component);
- });
+ document.body.appendChild(component());

现在让我们运行 webpack 并查看我们新的按需加载功能

...
          Asset       Size  Chunks                    Chunk Names
print.bundle.js  417 bytes       0  [emitted]         print
index.bundle.js     548 kB       1  [emitted]  [big]  index
     index.html  189 bytes          [emitted]
...

延迟导入示例

在某些情况下,将模块的所有使用都转换为异步可能会很麻烦或困难,因为它强制对所有函数进行不必要的异步化,而无法仅延迟同步求值工作。

TC39 提案《模块求值延迟》旨在解决这个问题。

该提案旨在引入一种新的语法导入形式,它将始终只返回一个命名空间异质对象。使用时,模块及其依赖项不会被执行,但在模块图被认为是加载完成之前,它们会完全加载到执行就绪的状态。

只有在访问该模块的属性时,才会执行执行操作(如果需要)。

通过启用 experiments.deferImport 可以使用此功能。

项目

webpack-demo
|- package.json
|- package-lock.json
|- webpack.config.js
|- /dist
|- /src
  |- index.js
+ |- print.js
|- /node_modules

src/print.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

  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());

这类似于 CommonJS 风格的按需加载

src/index.js

  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());

框架

许多框架和库都有关于如何在各自方法中实现这一目标的建议。以下是一些示例:

8 贡献者

iammerrickchrisVillanuevaskipjackbyzykEugeneHlushkoAnayaDesigntapanprakashtsnitin315