认识Code Spliting(代码分割)
- 默认情况下,一些第三方库以及一些首页用不到的业务代码很可能都打到相同的bundle中在第一时间加载,那将使首页加载的速度变慢
 - 代码分割的主要目的是将代码分割到不同的bundle中,这样就可以对bundle进行选择性的并行加载或者按需加载
 
webpack中Code Spliting的三种模式
多入口对应多出口动态导入抽离代码 splitChunks重点讲解
多入口对应多出口
先来看单入口
入口处为index.js

            
            
              js
              
              
            
          
              // index.js
    var index = 'index.js'
    console.log(index)
    var main = 'main.js'
    console.log(main)
        
            
            
              js
              
              
            
          
              // webpack
    ...
    entry: {
        index: getPath('src/code-spliting/index.js'),
    },
    output: {
        path: getPath('dist'),
        filename: 'js/[name].[chunkhash:8].bundle.js',
        clean: true
    },
    ...
        毫无疑问,默认会输出一个bundle也就是index.js对应的内容。

 如果想把上述输出内容一分为二,很简单,拆分多入口即可!
多入口

            
            
              js
              
              
            
          
              // main.js
    var main = 'main.js'
    console.log(main)
    
    // index.js
    var index = 'index.js'
    console.log(index)
        
            
            
              js
              
              
            
          
              // webpack
    ...
    entry: {
        index: getPath('src/code-spliting/index.js'),
        main: getPath('src/code-spliting/main.js'),
    },
    ...
        看一下输出产物


动态导入
下面新建other1文件,并在index.js中动态引入
 webpack内部对于动态导入的模块默认采用单独打包的形式
            
            
              js
              
              
            
          
          ...
	entry: {
		index: getPath('src/code-spliting/index.js'),
		main: getPath('src/code-spliting/main.js'),
	},
	output: {
		path: getPath('dist'),
		filename: 'js/[name].[chunkhash:8].bundle.js',
		chunkFilename: 'js/[name].bundle.js',
		clean: true
	},
...
        dist输出产物:

就算在main.js中动态引入也是只会产生一个async-other.bundle.js
splitChunks
webpack官网的默认配置
            
            
              yaml
              
              
            
          
          module.exports = {
  //...
  optimization: {
    splitChunks: {
      chunks: 'async',
      minSize: 20000,
      minRemainingSize: 0,
      minChunks: 1,
      maxAsyncRequests: 30,
      maxInitialRequests: 30,
      enforceSizeThreshold: 50000,
      cacheGroups: {
        defaultVendors: {
          test: /[\/]node_modules[\/]/,
          priority: -10,
          reuseExistingChunk: true,
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true,
        },
      },
    },
  },
};
        官网www.webpackjs.com/plugins/spl...
对后面能用到的几个关键的属性解释一下~
- chunks :
async (默认值)处理异步 导入的代码
initial处理同步 导入的代码(注意:webpack默认支持异步导入代码的抽离不受影响)
all同步+异步代码都会进行处理 - minSize:抽离出来的包最小的大小(抽离出来的包特别特别小,比给定的值还要小就没意义了,还要浪费一次http请求)
 - minChunks:被多入口引用的次数
 - cacheGroups :缓存组,某个模块在满足缓存组的规则之后不会立即抽离,而是等其他也满足该规则的模块打包到一组;(注意:每个缓存组内的属性优先级比外部
splitChunks下的属性优先级高,外部的是统一的,内部的是针对当前缓存组来说的) - priority: 缓存组的权重,相同规则下权重高的命中
 - reuseExistingChunk:某个模块已经被抽离出来,直接服用不会再次打包
 
小试牛刀
- 入口总共三个文件:
entry.js,index.js,main.js - other文件夹下是要被引入的文件:
other1.js,other2.js,other3.js - 每个文件内部仅仅只打印自身的文件名
 

initial模式
抽离公共模块
三个入口文件 分别引入other1.js import './other/other1.js'
下面看打包输出结果 
 三个入口对应输出的三个bundle,而且各自内部都嵌入一份other1的代码,并没有抽离代码。
这是因为默认情况下splitChunks.chunks为async,只对异步导入做抽离,我们的同步代码并没有抽离,下面尝试改一下配置。
            
            
              js
              
              
            
          
              // webpack
    optimization: {
        splitChunks: {
            chunks: 'initial',
            minSize: 0,
            minChunks: 3,  // 1;2;3 都可以,因为other1一共被引入了三次
            cacheGroups: {
                    defaultVendors: false,  // 取消默认缓存组
                    default: false // 取消默认缓存组
            }
        }
    },
        
 输出结果不难看出other1被抽离出来了
抽离三方库
下载一个第三方库lodash在entry和index中引入,补充cacheGroups内部配置项
            
            
              js
              
              
            
          
              optimization: {
        splitChunks: {
            ...
            minChunks: 3, 
            cacheGroups: {
                defaultVendors: false,  // 取消默认缓存组
                default: false, // 取消默认缓存组,
                other: {
                        test: /other/,
                        filename: 'split/other-[id].bundle.js'
                },
                vendors: {
                        test: /node_modules/,
                        filename: 'split/vendors-[id].bundle.js'						
                }
            }
        }
    },
        
 看下结果:other1被正常抽离到split目录下other-832.bundle.js
为啥lodash也是公共代码,但是没有被抽离呢? 是因为缓存组继承外层的minChunks为3,而lodash被引入了两次。我们把外层的minChunks(或者vendors缓存组的minChunks) 改成 2试一试。

果然不出所料!lodash也抽离成vendors-xxx.bundle.js了
再思考一下
这里补充一下上面说的把外层minChunks改成 2,其他没变
            
            
              js
              
              
            
          
              // webapck
    optimization: {
        splitChunks: {
            ...
            minChunks: 2, 
            cacheGroups: {
                defaultVendors: false,  // 取消默认缓存组
                default: false, // 取消默认缓存组,
                other: {
                        test: /other/,
                        filename: 'split/other-[id].bundle.js'
                },
                vendors: {
                        test: /node_modules/,
                        filename: 'split/vendors-[id].bundle.js'						
                }
            }
        }
    },
        目前三个入口文件(entry,index,main)都引入了other1.js,我们在entry.js和index.js两个文件引入lodash的基础上再次同时新增引入other2.js ,显然other2已经满足other缓存组的条件了,思考一下other2会被打入到上面抽出的other1对应的other-832.bundle.js中吗?
看下输出结果 
 很显然除了other-832.bundle.js还有other-193.bundle.js,同一规则下两个相互独立的bundle
我的理解是这样的:对于三个入口文件来说other1是公共的 ,而对于entry和index来说other2是公共的,如果都打在一起的情况下对于entry和index是合理的,因为他们都用到了两个模块。但是对于第三个入口main是不合理的只引入了other1一个模块,所以是两个bundle。(在包的大小以及并发引用次数等一系列属性都理想化时)。
如果在main.js中也引入other2.js,满足三个入口都引入other1和other2,那么会打在一起吗?
 很显然是打在一起的
如果在两个入口再引入other3.js满足other缓存组的条件呢?答案是和上面第一个问题一样的,产生两个bundle,other1和other2是一个,other3是一个。
也就是说理想情况下在满足同一缓存组条件的前提下,不同的模块被相同的文件引入可能会产生同一个包,被不同的文件引入产生不同的包
将引入的other打到一起
如果想要把满足缓存组条件的包都强制打到一起 ,就要设置一个name,如下代码会将满足other缓存组条件的代码全部打成一个other.bundle.js
            
            
              js
              
              
            
          
           // webpack
 cacheGroups: {
    ...
    other: {
        test: /other/,
        minChunks: 2,
        filename: 'split/other-[name].[id].bundle.js',
        name: 'all'
    },
    ...
 }
        
chunks设置initial情况下的异步代码
webpack本身就天然分割异步导入的代码,设置成initial只是对同步代码做出分割,并不会影响异步分割的代码(动态导入)
- 三个入口文件entry.js , index.js , main.js 同步引入 
other1.js;
import './other/other1.js' - entry.js 异步引入 
other2.js;
import (/* webpackChunkName: "async-other2"*/ './other/other2.js') - index.js 同步引入 
other2.js
import './other/other2.js' 
为了方便演示去掉other组内的name属性
            
            
              js
              
              
            
          
              ...
    output: {
        path: getPath('dist'),
        filename: 'js/[name].[chunkhash:8].bundle.js',
        chunkFilename: 'js/[name].bundle.js',
        clean: true
    },
    ...
    other: {
        test: /other/,
        minChunks: 2,
        filename: 'split/other-[id].bundle.js',
        // name: 'all'
    },
        
- 对于other1来说,肯定是要抽出来的(熟悉的配方)
 - 对于异步引入的other2被独立抽离出来了
 - 对于同步引入的other2来说,只在index同步引入了一次,不满足抽离的条件被嵌入到index中了
 
async模式
采用如下依赖关系:
- 三个入口文件entry.js , index.js , main.js 同步引入 
other3.js;
import './other/other3.js' - entry.js 异步引入 
other1.js;
import (/* webpackChunkName: "async-other1"*/ './other/other1.js') - index.js 异步引入 
other2.js
import (/* webpackChunkName: "async-other2"*/ './other/other2.js') 
splitChunks.chunks默认值为async,我们把上述配置中的chunks改为async表示该组(other)只匹配异步代码
            
            
              js
              
              
            
          
              ...
    output: {
        path: getPath('dist'),
        filename: 'js/[name].[chunkhash:8].bundle.js',
        chunkFilename: 'js/[name].bundle.js',
        clean: true
    },
    ...
    optimization: {
        splitChunks: {
            ...
            ...
            other: {
                test: /other/,
                chunks: 'async'
                minChunks: 2,
                filename: 'split/other-[id].bundle.js',
            },
        }
    }
        看下输出结果:

- 同步代码并没有分割出来,这是意料之中的
 - 代码中采用异步引入的模块并没有根据缓存组的规则被抽离出来 ,依然采用默认的异步分割的方式,我们把
minChunks改为 1试一下 
            
            
              js
              
              
            
          
              // webpack
         ...
            other: {
                test: /other/,
                minChunks: 1,
                filename: 'split/other-[id].bundle.js',
            },
        ...
        
补充name属性,完善filename属性:
            
            
              js
              
              
            
          
              // webpack
         ...
            other: {
                test: /other/,
                minChunks: 1,
                filename: 'split/other-[name]-[id].bundle.js',
                name: 'all'
            },
        ...
        
果然,对于异步代码来说满足规则的部分会进行抽离,如果不满足则会天然分割成独立的包
all模式
chunks: all 不论是同步引入还是异步引入的代码都会匹配,把chunks改为'all'试一下
            
            
              js
              
              
            
          
              // webpack
         ...
        other: {
            test: /other/,
            chunks: 'all',
            minChunks: 1,
            filename: 'split/other-[id].bundle.js',
        },
        ...
        
other1 other2 other3 都符合other下的规则
同步引入的other3打成了other-682.bundle.js,另外两个异步引入的other1和other2也都分别打成了other-193-bundle.js和other-832-bundle.js
如果想把这一组打成一个包,那么就要设置name属性
            
            
              js
              
              
            
          
              // webpack
         ...
        other: {
            test: /other/,
            chunks: 'all',
            minChunks: 1,
            filename: 'split/other-[name]-[id].bundle.js',
            name: 'other-all'
        },
        ...
        
注意:all,对于同一模块即被同步引入又用异步引入,缓存组设置name会将包合并复用
总结一下
- webpack本身就是天然对异步导入的代码进行独立分割的
 - 对于
splitChunks.cacheGroups会把满足匹配条件的模块都按照该组的方式进行分割(可能打成多个包,也可能打成一个包),如果想把所有满足该组条件的模块全部打成一个包,要给该组设置一个固定的name(可以是一个字符串) chunks: initial只匹配同步导入代码,满足条件的的同步代码进行分割,不影响天然分割的异步代码块chunks: async只匹配异步导入代码,对满足条件的异步代码块进行分割,不满足的异步代码依然会被天然分割chunks:all匹配(同步+异步)导入代码,如果想把满足该组所有的chunk打成一个包,可以设置一个固定name
先写到这儿,路过的大佬有什么宝贵的建议,可以交流探讨一下~