预取/预加载模块

webpack 4.6.0+增加了对预取和预加载的支持。

在声明导入时使用这些内联指令允许webpack输出“Resource Hint”,它告诉浏览器:

  • prefetch:将来某些导航可能需要资源

  • preload:当前导航期间可能需要资源

简单的预取示例可以是一个HomePage组件,该组件呈现一个LoginButton组件,然后在LoginModal点击后按需加载组件。

LoginButton.js

  1. //...
  2. import(/* webpackPrefetch: true */ 'LoginModal');

这将导致<link rel="prefetch" href="login-modal-chunk.js">被附加在页面的头部,这将指示浏览器在空闲时间预取login-modal-chunk.js文件。

一旦父块加载,webpack将添加预取提示。

与prefetch相比,Preload指令有许多不同之处:

  • 与 prefetch 指令相比,preload 指令有许多不同之处:
  • preload chunk 会在父 chunk 加载时,以并行方式开始加载。prefetch chunk 会在父 chunk 加载结束后开始加载。
  • preload chunk 具有中等优先级,并立即下载。prefetch chunk 在浏览器闲置时下载。
  • preload chunk 会在父 chunk 中立即请求,用于当下时刻。prefetch chunk 会用于未来的某个时刻。
  • 浏览器支持程度不同。

下面这个简单的 preload 示例中,有一个 Component,依赖于一个较大的 library,所以应该将其分离到一个独立的 chunk 中。

我们假想这里的图表组件 ChartComponent 组件需要依赖一个体积巨大的 ChartingLibrary 库。它会在渲染时显示一个 LoadingIndicator(加载进度条) 组件,然后立即按需导入 ChartingLibrary

ChartComponent.js

  1. //...
  2. import(/* webpackPreload: true */ 'ChartingLibrary');

在页面中使用 ChartComponent 时,在请求 ChartComponent.js 的同时,还会通过 <link rel="preload"> 请求 charting-library-chunk。假定 page-chunk 体积很小,很快就被加载好,页面此时就会显示 LoadingIndicator(加载进度条) ,等到 charting-library-chunk 请求完成,LoadingIndicator 组件才消失。启动仅需要很少的加载时间,因为只进行单次往返,而不是两次往返。尤其是在高延迟环境下。

错误地使用webpackPreload实际上会损害性能,因此使用它时要小心。

延迟加载

懒惰或“按需”加载是优化网站或应用程序的好方法。这种做法主要涉及在逻辑断点处拆分代码,然后在用户完成需要或将需要新代码块的操作后加载它。这可以加快应用程序的初始负载并减轻其总体重量,因为某些块甚至可能永远不会被加载。

让我们以Code Splitting为例,稍微调整一下,以进一步展示这个概念。这些代码确实会导致生成一个单独的块,lodash.bundle.js并在脚本运行时从技术上“延迟加载”它。问题是加载捆绑包不需要用户交互 - 这意味着每次加载页面时,都会触发请求。这对我们没有多大帮助,并且会对性能产生负面影响。

让我们尝试不同的东西。当用户单击按钮时,我们将添加一个交互以将一些文本记录到控制台。但是,我们将等待加载该代码(print.js),直到第一次发生交互。为此,我们将返回并重新编写Code Splitting中最终Dynamic Imports示例,并留在主块中。lodash

  1. webpack-demo
  2. |- package.json
  3. |- webpack.common.js
  4. |- webpack.dev.js
  5. |- webpack.prod.js
  6. |- server.js
  7. |- DEV-server.js
  8. |- /dist
  9. |- /src
  10. |- data.xml
  11. |- icon.png+
  12. |- style.css
  13. |- my-font.woff
  14. |- my-font.woff2
  15. |- print.js
  16. |- index.js
  17. |- math.js
  18. |- /node_modules
  1. // print.js
  2. console.log('The print.js module has loaded! See the network tab in dev tools...');
  3. export default () => {
  4. console.log('Button Clicked: Here\'s "some text"!');
  5. };
  1. // index.js
  2. import _ from 'lodash';
  3. function component() {
  4. var element = document.createElement('div');
  5. var button = document.createElement('button');
  6. var br = document.createElement('br');
  7. button.innerHTML = 'Click me and look at the console!';
  8. element.innerHTML = _.join(['Hello', 'webpack'], ' ');
  9. element.appendChild(br);
  10. element.appendChild(button);
  11. // Note that because a network request is involved, some indication
  12. // of loading would need to be shown in a production-level site/app.
  13. button.onclick = e => import(/* webpackChunkName: "print" */ './print').then(module => {
  14. var print = module.default;
  15. print();
  16. });
  17. return element;
  18. }
  19. document.body.appendChild(component());

请注意,import()在ES6模块上使用时,必须引用该.default属性,因为它module是解析promise时将返回的实际对象。

构架

许多框架和图书馆都有自己的建议,说明如何在其方法中实现这一目标。这里有一些例子: