代码分割是一个概念,和webpack无关,在没有webpack的时候,代码分割这个概念也存在。通过合理的代码分割会让我们程序运行的性能更高,在没有webpack的时候,我们需要思考手动做代码分割怎么样合适。但是在有webpack后,我们只需在webpack配置中使用几个配置项,webpack就会知道我们的代码怎么样做代码分割合适,就会自动做代码分割,不在需要我们去考虑这个事情。
1.手动代码分割演示
安装loadsh
src/index.js
import _ from "loadsh";
let str = _.join(['hello','world'],'-');
console.log(str)
运行webpack

webpack打包输出的信息,main.js文件是1.38M。main.js文件之所以这么大,是因为将整个loadsh库全部打包进main.js文件中。当我们的业务代码非常长,引用的库文件也比较多时,main文件会非常大,这会导致用户体验非常差。接下来手动拆分一下
文件结构
myProject
 |-dist
 |-node_modules
 |-src
     |-util
        |-math.js
+        |-loadsh.js
     |-assets
        |-css
            |-index.css
        |-less
            |-index.less     
        |-sass
            |-index.scss
        |-images
            |-wali_logo.png
     |-index.html
     |-index.js
 |-package.json
 |-webpack.config.js
 |-postcss.config.js
 |-.babelrc
src/util/loadsh.js
import _ from "loadsh"
window._ = _;
src/index.js
- import _ from "loadsh";
  let str = _.join(['hello','world'],'-');
  console.log(str)
webpack.config.js
module.exports = {
    ...
    entry:{
+       loadsh:'./src/util/loadsh.js'
        main:'./src/index.js'
    },
    ...
}
运行webpack

webpack打包看到main.js文件29.1KB,并没有将loadsh打包进main.js文件,而是单独打包成一个文件。我们测试下打包后能不能运行,打开dist/index.html,打开chrom控制台,看到控制台输出hello-world。其实webpack可以自动帮我们进行代码分割,不需要我们从架构上区分,减轻了开发者的工作。
注意:不知道大家在写entry时,有没有将loadsh和main顺序写反,小菜第一次就他们顺序写反了。导致打完包后,浏览器包了一个_ is not defined。看index.html源码中也将loadsh库加载进来了。
为什么会出现这个问题?
是因为在index.html中先加载main.js然后在加载loadsh但是在执行顺序上main.js文件需要依赖loadsh库文件。虽然浏览器是并行加载js文件的。但是在有依赖关系js代码中,我们并不能保证loadsh一定加载在main文件前。
2.webpack配置代码分割
在配置webpack之前,我们将上面的代码进行回滚
回滚webpack.config.js
module.exports = {
    ...
    entry:{
-       loadsh:'./src/util/loadsh.js'
        main:'./src/index.js'
    },
    ...
}
回滚文件
myProject
 |-dist
 |-node_modules
 |-src
     |-util
        |-math.js
-        |-loadsh.js
     |-assets
        |-css
            |-index.css
        |-less
            |-index.less     
        |-sass
            |-index.scss
        |-images
            |-wali_logo.png
     |-index.html
     |-index.js
 |-package.json
 |-webpack.config.js
 |-postcss.config.js
 |-.babelrc
代码回滚完成后,我们在webpack参数中配置下,让webpack帮助我们做代码分割。
修改src/index.js
import _ from "loadsh";
let str = _.join(['hello','world'],'-');
console.log(str)
webpack.config.js
module.exports = {
    ...
    optimization:{
        usedExports: true,
+        splitChunks:{
+            chunks:'all'
+        }
    },
    ...
}

从小菜截图中可以看到,webpack将公共的类库提取出来,打包成一个文件。
3.异步代码分割
在上面我们已经将loadsh进行了代码分割,不过在src/index.js中的代码是同步的,那webpack能不能将我们的代码做异步分割呢?
src/index.js
function getComponent(){
    return import('loadsh').then(({ default:_ }) =>{
        let element = document.createElement('div');
        element.innerHTML = _.join(['hello','world'],'**');
        return element;
    })
}
getComponent().then(ele=>{
    document.body.appendChild(ele);
})
安装
yarn add @babel/plugin-syntax-dynamic-import
修改.babelrc
{
	"presets": [
		[
			"@babel/preset-env",
			{
				"targets": {
					"chrome": "67"
				},
				"useBuiltIns": "usage"
			}
		]
	],
	"plugins": [
    [
      "@babel/plugin-transform-runtime",
      {
        "absoluteRuntime": false,
        "corejs": 2,
        "helpers": true,
        "regenerator": true,
        "useESModules": false
      }
    ]
+	  "@babel/plugin-syntax-dynamic-import"    
  ]
}
然后在运行webpack
 
发现webpack将异步引入的库文件也打包成了一个文件。
4.代码分割修改文件名
上面webpack将loadsh库文件做代码分割后,文件名称变成了0.js,不在是我们熟悉的loadsh.js如果想要修改文件名称,那就跟小菜继续往下走。
scr/index.js
function getComponent(){
+    return import(/*webpackChunkName:"loadsh"*/ 'loadsh').then(({ default:_ }) =>{
        let element = document.createElement('div');
        element.innerHTML = _.join(['hello','world'],'**');
        return element;
    })
}
getComponent().then(ele=>{
    document.body.appendChild(ele);
})
运行webpack

5.详解SpiltChunksPlugin参数
SpiltChunksPlugin默认参数
module.exports = {
  //...
  optimization: {
    splitChunks: {
      chunks: 'async',
      minSize: 30000,
      maxSize: 0,
      minChunks: 1,
      maxAsyncRequests: 5,
      maxInitialRequests: 3,
      automaticNameDelimiter: '~',
      name: true,
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true
        }
      }
    }
  }
};
      
| 参数 | 默认 | 说明 | 值 | 
|---|
| chunks | async | 设置代码分割类型,和cacheGroups配置配合使用 | async 对异步代码分割
 all 对同步和异步代码分割 | 
| minSize | 30000(30kb) | 当引入的模块大于30kb才会做代码分割 |   | 
| maxSize | 0 | 当引入的模块大于maxSize时,会尝试对引入的模块进行二次拆分,一般不用配置 |   | 
| minChunks | 1 | 当一个模块被至少引入1次,才会做代码分割 |   | 
| maxAsyncRequests | 5 | 当引入模块10个时,只会将前5个模块进行打包 |   | 
| maxInitialRequests | 3 | 入口文件引入的模块如果超过3个,只会将前3个模块做代码分割 |   | 
| automaticNameDelimiter | ~ | 文件连接符 |   | 
| name | true | 拆分块的名称,让cacheGroups里面的名字有效 |   | 
| cacheGroups | {} | 对符合代码拆分的模块进行一个分类 |   | 
cacheGroups参数
| 参数 | 类型 | 说明 | 值 |   | 
|---|
| priority | number | 有限权,当一个模块都符合cacheGroups分组条件,将按照优先权进行分组,priority值越大,优先权越高 | -10 |   | 
| filename | String|Function | 拆分的名称,一般不设置,默认生成vendors~mian。vendors分组名称,~连接符,main引入模块的入口文件 |   |   | 
| reuseExistingChunk | boolean | 如果当前块包含已从主束拆分的模块,则将重用它而不是生成新的块。比如  import a from ‘A’ import b from ‘B’在打包时候,按照打包顺序也会将b打包进a模块,但是在a打包之前,如果已经将b模块进行过打包,那么就不会将b模块在打包到a模块中 |   |   | 
| test | function (module, chunk) | RegExp | string | 控制此缓存组选择的模块。test:/[\\/]node_modules[\\/]/必须要在node_modules模块在才可以 |   |   | 
| enforce | boolean | 将对 splitChunks.minSize splitChunks.minChunks splitChunks.maxAsyncRequests  splitChunks.maxInitialRequests 配置忽略 |   |   | 
webpack.config.js
module.exports = {
  ...
  optimization: {
+    splitChunks: {
+      chunks: 'all',
+      minSize: 30000,
+      maxSize: 0,
+      minChunks: 1,
+      maxAsyncRequests: 5,
+      maxInitialRequests: 3,
+      automaticNameDelimiter: '~',
+      name: true,
+      cacheGroups: {
+        vendors: {
+          test: /[\\/]node_modules[\\/]/,
+          priority: -10
+        },
+        default: {
+          minChunks: 2,
+          priority: -20,
+          reuseExistingChunk: true
+        }
+      }
+    }
  }
};
运行webpack
6.示例1说明
// index.js
import('./a'); // dynamic import
            
// a.js
import 'react';
//...
结果:创建包含react的单独块,在导入调用时,react块和./a的原始块并行加载
原因:
- react块来自node_modules的模块
 - react大于30kb
 - 导入调用时的并行请求数为2
 - 不影响初始页面加载时的请求
 
7.示例2说明
// entry.js
// dynamic imports
import('./a');
import('./b');
      
// a.js
import './helpers'; // helpers is 40kb in size
//...
      
// b.js
import './helpers';
import './more-helpers'; // more-helpers is also 40kb in size
//...
结果:将创建一个./helpers单独的块及其所有依赖项。在导入调用时,此块与原始块并行加载
原因:
./helpers块在两个导入调用之间共享helpers大于30kb- 导入调用的并行请求数为2
 - 不影响初始页面加载时的请求