webpack4 optimization配置splitChunks和namedChunks

3,812 阅读5分钟

添加分析工具

想要实时查看打包变化,可以通过一个打包分析利器。webpack-bundle-analyzer
安装

npm i webpack-bundle-analyzer --save-dev

package.json里面添加命令

"analyz": "NODE_ENV=production npm_config_report=true npm run build"

webpack配置

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
plugins: [new BundleAnalyzerPlugin()],

执行

npm npm run analyz

工具配置好之后

首先看下webpack的默认打包配置

module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'async',
      minSize: 30000,
      minChunks: 1,
      maxAsyncRequests: 5,
      maxInitialRequests: 3,
      automaticNameDelimiter: '~',
      name: true,
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true
        }
      }
    }
  }
};

解析下参数

  1. chunk: 含义是拆分模块的范围,它有三个值async、initial和all。

async表示只从异步加载得模块(动态加载import())里面进行拆分 initial表示只从入口模块进行拆分 all表示以上两者都包括

  1. minSize:最小拆分组件大小
  2. minChunks:最小引用次数
  3. maxAsyncRequests:限制异步模块内部的并行最大请求数的
  4. maxInitialRequests:允许入口并行加载的最大请求数
  5. automaticNameDelimiter:文件名的连接符
  6. name: split 的 chunks name
  7. cacheGroups:缓存组

默认为vendors和default。可以设置权重值priority。

vendor一般放置node_modules里面的文件, default放置公共组件

增加模块标识

我们需要加添加几个文件进行测试
但是现在个问题
假如我先建立一个btest文件,再建立一个atest文件,进行打包后,发现btest的文件的hash也会发生变化。
这是因为,webpack在建立依赖的时候的文件是按照ascall码进行排序的,然后新插入的文件就会发生变化。
解决办法,
给模块的名字加上标识。这样在打包的时候就会按照标识进行。 配置如下

optimization: {
            namedChunks:true, //增加模块标识
            splitChunks: {
                // 配置
            }
        },
plugins: [
     new webpack.HashedModuleIdsPlugin(), //模块增加标识,开发环境建议用 NamedModulePlugin
],

再进行打包,发现问题解决,再回到我们的打包方案分析。

打包分析

现在打包有几种方案
1. node—modules和公共组件(比如loading)引用超过两次以上都会打包到base文件。

webpack配置如下

optimization: {
            splitChunks: {
                cacheGroups: {
                    base:{
                        chunks: 'initial', //initial表示提取入口文件的公共部分
                        minChunks: 2, //表示提取公共部分最少的文件数
                        minSize: 0, //表示提取公共部分最小的大小
                        name: 'base' //提取出来的文件命名
                    }
                }
            }
        },

打包分析结果如下

现在我们在atest里面引入loading组件

<template>
  <div>
    atest
  </div>
</template>
<script>
import Loading from '../components/loading/loading' //引入Loading组件
export default {

}
</script>

再次打包

和我们想的一样,只有atest发生变化 然后在btest里面再引入loading
再次打包

发现 atest btest base 都发生了变化。
因为loading被引入超过了两次,loading先从atest抽离,atset发生变化。base加入loading组件发生变化。 但是这就是隐患之一,但是我们可以把minChunks设置为1,这样的话,所有被引用的组件都会被base.js里面去的。
但是如果base文件太大的话,缓存的代价太大了,所以我们换一个策略

2. 分离式打包,node_modules会单独打包,每个引入的组件会打包到一个文件中。 webpack配置如下

optimization: {
            namedChunks:true,
            splitChunks: {
                chunks: 'all',
                minSize: 0,
                minChunks: 1,
                maxAsyncRequests: 10,
                maxInitialRequests: 10,
                // automaticNameDelimiter: '~',
                name: true,
                cacheGroups: {
                  vendors: {
                    minChunks: 1,
                    test: /[\\/]node_modules[\\/]/,
                    priority: -10 // 权重
                  },
                  default: {
                    minChunks: 1,
                    priority: -20, // 权重
                    reuseExistingChunk: true
                  }
                }
              }
        },

设置两个缓存组:其中vendors是放node_modules打包后的文件
老样子,添加atest文件和btest文件
打包分析

vendor放置的就是node_moldules里面的vue相关文件
现在我们在atest里面引入loading组件 ,然后在btest文件页引入loading组件 打包分析

其中的atest~btest就是分离出来的loading组件,但是这个配置是存在一个问题的?可以看到我在default里面将minChunks的值改成1,代表是当组件被引用超过一次就会打包出来,我们看下,只有atest引入loading引入的时候的情况 只有atest引入

  <div>
    atest
  </div>
</template>
<script>
import myCom from '../components/testCom/testCom'
export default {
    
}
</script>

打包分析

可以看到引入的testCom组件并没有被单独打包出来。但是我们设置了minChunks:1,按理说testCom组件应该被分出来,但是结果并没有,现在猜想是可能只满足了被引用一次,但是没有满足其他条件,或者是因为key设置为了default的原因。这个等之后再做研究,
我们现在先替换掉default,换回我们的base

 vendors: {
        minChunks: 1,
        test: /[\\/]node_modules[\\/]/,
        priority: -10, // 权重
        minSize: 0,
    },
    base: {
        priority: -20, // 权重
        chunks: 'initial', //initial表示提取入口文件的公共部分
        minChunks: 1, //表示提取公共部分最少的文件数
        minSize: 0, //表示提取公共部分最小的大小
        name: '
    }

来看下打包结果

node_modules引用打包到了vendors,公共组件打包到base里面,一切都如此美好,这个也能满足开发需求。
下面我们来研究一个新的东西
3. 全部打散式打包,node_modules会打成包,每个组件自己单独打包。
现在我们已经实现了分离打包,公共组件,比如loading都打进了base文件,但是如果我们想把loading单独打包出来,形成loading.js,或者我们再引入一个toast组件,也要打包成toast.js文件,这个怎么进行操作?也有办法!

先上代码

  vendors: {
        name:'vendors',
        minChunks: 1,
        test: /[\\/]node_modules[\\/]/,
        priority: -10, // 权重
        minSize: 0,
    },
    default:{
        test:/[\\/]components[\\/]|[\\/]common[\\/]/,
        priority:-20, // 权重
        name(module){
            // console.log('模块分析打印')
            // console.log(module.identifier())
            const moduleFileName = module
            .identifier()
            .split('/')
            .pop()
            .replace('.js','')
            return `${moduleFileName}`
        }
    },

其中的components是我存放公共组件的地方,common是存放公共js方法的地方。我只需要筛选他们下面的引入
打包结果如下

可以看出atest里面引入的testCom被打包出来成了一个公共组件
现在再引入一个公共方法

<template>
  <div>
    atest
  </div>
</template>
<script>
import myCom from '../components/testCom/testCom'
import '../common/widget/common'
export default {

}
</script>

在看打包结果

可以看出testCom单独打包,而且commmon也单独打包了,ok!解决 !

这样比较上面方式的好处是,上面那种方法,当你只改变一个组件的内容时候,会引起整个base的变化。这个弊端会随着base文件的越来越大而越来越️明显

代码

最后放一下三种方案的代码
1. node—modules和公共组件(比如loading)引用超过两次以上都会打包到base文件。

 optimization: {
            namedChunks: true,
            splitChunks: {
                chunks: 'all',
                minSize: 0,
                minChunks: 1,
                maxAsyncRequests: 100,
                maxInitialRequests: 100,
                automaticNameDelimiter: '~',
                name: true,
                cacheGroups: { 
                    base: {
                        minChunks: 1, //表示提取公共部分最少的文件数
                        minSize: 0, //表示提取公共部分最小的大小
                        name: 'base' //提取出来的文件命名
                    }
                }
    }

2. 分离式打包,node_modules会单独打包,每个引入的组件也会单独打包。

 optimization: {
            namedChunks: true,
            splitChunks: {
                chunks: 'all',
                minSize: 0,
                minChunks: 1,
                maxAsyncRequests: 100,
                maxInitialRequests: 100,
                automaticNameDelimiter: '~',
                name: true,
                cacheGroups: { 
                     vendors: {
                        minChunks: 1,
                        test: /[\\/]node_modules[\\/]/,
                        priority: -10, // 权重
                        minSize: 0,
                    },
                    base: {
                        priority: -20, // 权重
                        chunks: 'initial', //initial表示提取入口文件的公共部分
                        minChunks: 1, //表示提取公共部分最少的文件数
                        minSize: 0, //表示提取公共部分最小的大小
                        name: 'base' //提取出来的文件命名
                    }
                }
    }

3. 全部打散式打包,node_modules会打成包,每个组件自己单独打包。

 optimization: {
            namedChunks: true,
            splitChunks: {
                chunks: 'all',
                minSize: 0,
                minChunks: 1,
                maxAsyncRequests: 100,
                maxInitialRequests: 100,
                automaticNameDelimiter: '~',
                name: true,
                cacheGroups: { 
                       vendors: {
                        name:'vendors',
                        minChunks: 1,
                        test: /[\\/]node_modules[\\/]/,
                        priority: -10, // 权重
                        minSize: 0,
                    },
                    default:{
                        test:/[\\/]components[\\/]|[\\/]common[\\/]/,
                        priority:-20, // 权重
                        name(module){
                            // console.log('模块分析打印')
                            // console.log(module.identifier())
                            const moduleFileName = module
                            .identifier()
                            .split('/')
                            .pop()
                            .replace('.js','')
                            return `${moduleFileName}`
                        }
                    }
                }
    }