Lazy Loading 懒加载/按需加载

本指南的继承自代码分离。如果你尚未阅读该指南,请先行阅读。

英文 > This guide is a small follow-up to Code Splitting. If you have not yet read through that guide, please do so now.

懒加载或者按需加载,是一种很好的优化网页或应用的方式。这种方式实际上是先把你的代码在一些逻辑断点处分离开,然后在一些代码块中完成某些操作后,立即引用或即将引用另外一些新的代码块。这样加快了应用的初始加载速度,减轻了它的总体体积,因为某些代码块可能永远不会被加载。

英文 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 示例

我们在代码分离中的例子基础上,进一步做些调整来说明这个概念。那里的代码确实会在脚本运行的时候产生一个分离的代码块 lodash.bundle.js ,在技术概念上“懒加载”它。问题是加载这个包并不需要用户的交互 — 意思是每次加载页面的时候都会请求它。这样做并没有对我们有很多帮助,还会对性能产生负面影响。

我们试试不同的做法。我们增加一个交互,当用户点击按钮的时候用 console 打印一些文字。但是会等到第一次交互的时候再加载那个代码块(print.js)。为此,我们返回到代码分离的例子中,把 lodash 放到主代码块中,重新运行代码分离中的代码 final Dynamic Imports example

英文 Let’s take the example from Code Splitting 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. Let’s try something different. We’ll add an interaction to log some text to the console when the user clicks a button. However, we’ll wait to load that code (print.js) until the interaction occurs for the first time. To do this we’ll go back and rework the final Dynamic Imports example from Code Splitting and leave lodash in the main chunk.

project

  1. webpack-demo
  2. |- package.json
  3. |- webpack.config.js
  4. |- /dist
  5. |- /src
  6. |- index.js
  7. + |- print.js
  8. |- /node_modules

src/print.js

  1. console.log('The print.js module has loaded! See the network tab in dev tools...');
  2. export default () => {
  3. console.log('Button Clicked: Here\'s "some text"!');
  4. };

src/index.js

  1. + import _ from 'lodash';
  2. +
  3. - async function getComponent() {
  4. + function component() {
  5. const element = document.createElement('div');
  6. - const _ = await import(/* webpackChunkName: "lodash" */ 'lodash');
  7. + const button = document.createElement('button');
  8. + const br = document.createElement('br');
  9. + button.innerHTML = 'Click me and look at the console!';
  10. element.innerHTML = _.join(['Hello', 'webpack'], ' ');
  11. + element.appendChild(br);
  12. + element.appendChild(button);
  13. +
  14. + // Note that because a network request is involved, some indication
  15. + // of loading would need to be shown in a production-level site/app.
  16. + button.onclick = e => import(/* webpackChunkName: "print" */ './print').then(module => {
  17. + const print = module.default;
  18. +
  19. + print();
  20. + });
  21. return element;
  22. }
  23. - getComponent().then(component => {
  24. - document.body.appendChild(component);
  25. - });
  26. + document.body.appendChild(component());

注意当调用 ES6 模块的 import() 方法(引入模块)时,必须指向模块的 .default 值,因为它才是 promise 被处理后返回的实际的 module 对象。

现在运行 webpack 来验证一下我们的懒加载功能:

英文 > Note that when using import() on ES6 modules you must reference the .default property as it’s the actual module object that will be returned when the promise is resolved. Now let’s run webpack and check out our new lazy-loading functionality:
  1. ...
  2. Asset Size Chunks Chunk Names
  3. print.bundle.js 417 bytes 0 [emitted] print
  4. index.bundle.js 548 kB 1 [emitted] [big] index
  5. index.html 189 bytes [emitted]
  6. ...

Frameworks 框架

许多框架和类库对于如何用它们自己的方式来实现(懒加载)都有自己的建议。这里有一些例子:

英文 Many frameworks and libraries have their own recommendations on how this should be accomplished within their methodologies. Here are a few examples:

Further Reading 扩展阅读

Lazy Loading ES2015 Modules in the Browser