动态导入(dynamic import)

webpack提供了两种实现动态导入的方式:

  • import:使用ECMAScript中的 import() 语法来完成(推荐!!!)(目前还没有原生支持,需要babel转换)
  • require.ensure:使用webpack遗留的 require.ensure (不推荐)

1. require.ensure

2.动态import

image.png
image.png
举例说明
比如我们有一个模块bar.js,该模块我们希望在代码运行过程中来加载它(比如判断一个条件成立时才加载)。因为我们并不确定这个模块中的代码一定会用到,所以最好拆分成一个独立的js文件,这样就可以保证不用到该内容的时候,浏览器就不用加载和处理该文件的js代码了。这个时候我们就可以使用动态导入了。

注意!!!
在webpack中,通过动态导入获取到一个对象;真正导出的内容,是在该对象的 default 属性中的,所以我们需要做一个简单的解构。

  1. import('xxx').then(({ default }) => {})

相关的optimization配置

官网

1.optimizaiton.chunkIds配置
optimizaiton.chunkIds配置用于告知webpack模块的id采用什么算法生成。
有以下值可以配置:

  • false(默认值):告知webpack没有任何内置的算法会被使用;
  • ‘natural’:按照数字的顺序使用id;
  • ‘named’(development模式下的默认值):对调试更友好的可读的id;
  • ‘deterministic’(production模式下的默认值):确定性的,在不同的编译中不变的短数字id。有益于长期缓存。在生产模式中会默认开启;
  • ‘size’:专注于让初始下载包大小更小的数字id;
  • ‘total-size’:专注于让总下载包大小更小的数字id。
    1. module.exports = {
    2. //...
    3. optimization: {
    4. chunkIds: 'named',
    5. },
    6. };

动态导入的文件命名
当optimization.chunkIds值为deterministic。
动态导入的文件命名:因为动态导入通常是一定会打包成独立的文件的,所以并不会在cacheGroups中进行配置。那么,它的命名我们通常会在output.chunkFilename 属性中来命名的。

  1. output: {
  2. chunkFilename: "[name].[hash:6].chunk.js"
  3. },

但是,你会发现默认情况下,我们获取到的[name]是和id的名称保持一致的。如果我们希望修改name的值,可以通过 magic comments(魔法注释)的方式。如下所示。

  1. import(/* webpackChunkName: "foo" */"./foo").then(res => {
  2. console.log(res);
  3. });

2.optimization.runtimeChunk配置 (代码分离!!!)哪几种代码会进行代码分离
作用:配置runtime相关的代码是否抽取到一个单独的chunk中。

runtime相关的代码:指的是在运行环境中,对模块进行解析、加载、模块信息相关的代码。 (单独抽离) 比如我们的component、bar两个通过import函数相关的代码加载,就是通过runtime代码完成的。

好处:将这种代码抽离出来后,有利于浏览器缓存的策略。

  • 比如我们修改了业务代码(main),那么runtime和component、bar的chunk是不需要重新加载的;
  • 比如我们修改了component、bar的代码,那么main中的代码是不需要重新加载的

optimization.runtimeChunk的值

  • true/multiple:针对每个入口打包一个runtime文件;
  • single:打包一个runtime文件;
  • 对象:name属性决定runtimeChunk的名称。
    1. runtimeChunk: {
    2. name: function(entrypoint) {
    3. return `why-${entrypoint.name}`
    4. }
    5. }

动态import的使用场景 - 懒加载
动态import使用最多的一个场景是懒加载(比如路由懒加载)。
举例说明
1)新建一个element.js文件

  1. const element = document.createElement('div');
  2. element.innerHTML = "Hello Element";
  3. export default element;

2)设置在点击按钮时,再加载这个对象

  1. const button = document.createElement("button");
  2. button.innerHTML = "加载元素";
  3. button.addEventListener("click", () => {
  4. // prefetch -> 魔法注释(magic comments)
  5. /* webpackPrefetch: true */
  6. /* webpackPreload: true */
  7. import(
  8. /* webpackChunkName: 'element' */
  9. /* webpackPrefetch: true */
  10. "./element"
  11. ).then(({default: element}) => {
  12. document.body.appendChild(element);
  13. })
  14. });
  15. document.body.appendChild(button);

prefetch(预获取)和 preload(预加载)模块

webpack v4.6.0+ 增加了对预获取预加载的支持。
在声明 import 时,使用下面这些内置指令,来告知浏览器:

  • prefetch(预获取):将来某些导航下可能需要的资源;
  • preload(预加载):当前导航下可能需要资源。

线上网页真正需要立刻加载的是首屏资源,加载完成之后其实带宽就空闲了,并且此时核心逻辑和界面也展示出来了。
那么可不可以此时把上述异步处理的代码下载下来,而不是点击时才下载。这样不就是可以利用好空闲时间嘛!
这里其实webpack的该项优化只需要一个魔法注释就可以了:

  1. document.addEventListener('click',() => {
  2. import(/*webpackPrefetch:true*/'./click.js').then(({default:func}) => {
  3. func();
  4. })
  5. })

此时一旦发现核心JS文件都加载完成以后,会偷偷加载click.js文件!
打包后测试发现在网络空闲时候会加载1.js文件(网络空闲就预先获取添加了魔法注释的异步模块),同时点击的时候也会加载,但是此时是直接利用缓存,所以速度很快~
这里你也可以改成Preload,区别主要在于:Prefetching 是在核心代码加载完成之后带宽空闲的时候再去加载,而 Preloading 是和核心代码文件一起去加载的。
因此,使用 Prefetching 的方式去加载异步文件更合适一些,不过要注意浏览器的兼容性问题!!!

  1. import(
  2. /* webpackChunkName: 'element' */
  3. /* webpackPrefetch: true */
  4. /* webpackPreload: true */
  5. "./element"
  6. ).then(({default: element}) => {
  7. document.body.appendChild(element);
  8. })
  1. module.exports = {
  2. ...
  3. output: {
  4. filename: '[name].js', // 入口文件打包生成的命名
  5. chunkFilename: '[name].chunk.js',//入口文件中引入的一些js文件、库,间接引入的文件
  6. path: path.resolve(__dirname, '../dist')
  7. }
  8. }

:::info prefetch 与 preload 的区别
1)prefetch chunk会在父chunk加载结束后开始加载;而preload chunk会在父chunk加载时,以并行方式开始加载。
2)prefetch chunk会在浏览器闲置时下载;而preload chunk具有中等优先级,并立即下载。
3)prefetch chunk会用于未来的某个时刻;而preload chunk会在父chunk中立即请求。 :::

应用 - Vue中的懒加载