首页 > Vue >

chainWebpack之optimization.splitChunks的cacheGroups缓存组代码分块实践案

时间: 作者:admin 浏览:

研究了好几天webpack打包,我们项目是vue的高版本,已经没有了webpack.config.js文件了,是直接在vue.config.js里的chainWebpack方法直接配置,这样做法的好处是用户既可以保留webpack的默认配置,又可以通过chainWebpack设置更具针对性的参数,链式的写法正是避免了用户直接面对webpack多而复杂的配置项,想要更专业的配置再去学习webpack更深层的知识,大大降低了webpack使用难度和大多数小项目群体学习成本,一般项目只需要使用默认的配置即可满足要求,项目比较大的时候就需要深入的学习了,下面我们来说说webpack里的分块方法optimization.splitChunks以及缓存组cacheGroups的配置方法;

当我们npm run build进行打包的时候,事实上是根据cacheGroups的默认配置进来分包并包的:

splitChunks: {
    chunks: "all",
    minSize: 30000,//默认值,超过30K才独立分包
    cacheGroups: {
        vendors: {
            test: /[\/]node_modules[\/]/,  // 匹配node_modules目录下的文件
            priority: -10   // 打包优先级权重值
        },
        default: {
            minChunks: 2,
            priority: -20,   // 打包优先级权重值
            reuseExistingChunk: true//遇到重复包直接引用,不重新打包
        }
    }
}

默认配置会将 node_mudules下的模块打包进一个叫 vendors的bundle中,所有引用超过两次(minChunks:2)的模块分配到 default的bundle中,这就是我们打包完常见的xxx.bundle.js类;

但是如果node_mudules都打进一个包,通常vendor.bundle.js会很大,因此不得不抽离一部分依赖成单独块,先看一下常见的简单配置:
vue.config.js:

module.exports = {
    chainWebpack( config ) {
        //process.env.NODE_ENV的值要在.env.development和.env.production文件分别配置,不想判断的直接删除when方法这一行
        config.when( process.env.NODE_ENV==="production",  ( config )=>{
            config.optimization.splitChunks({
                chunks:"all",//initial同步,async异步,all同步或者异步
                automaticNameDelimiter:"-",//打包文件名默认连接符号
                cacheGroups: {
                    elementUI: {
                        name: "chunk-elementUI",
                        priority: 30,
                        test: /[\/]node_modules[\/]_?element-ui(.*)/,
                    },
                    nodesInitial: {
                        name:"chunk-nodesInitial",
                        test: /[\/]node_modules[\/]/,
                        priority: 10,
                        minChunks: 1,
                        chunks: "initial",//仅打包同步引用的依赖
                        reuseExistingChunk: true
                    },
                    nodesAsync: {
                        name:"chunk-nodesAsync",
                        test: /[\/]node_modules[\/]/,
                        priority: 9,
                        minChunks: 1,
                        chunks: "async",//仅打包异步引用的依赖
                        reuseExistingChunk: true
                    },
                    components: {
                        name: "chunk-components",
                        test: resolve('src/components'),
                        minChunks: 2,
                        priority: 5,
                        reuseExistingChunk: true
                    }
                }
            })
            config.optimization.runtimeChunk("single")
        })
    }
}

可以见到上面的配置已经将node_modules里的elementUI与公共组件components抽离成独立块,同步和异步引用的依赖分开打包,运行打包之后感觉还不错,这里要注意的是minSize的值,缓存组里独立打包的块如果小于30k,是不会打包的,所以可以针对文件的大小设置打包最小值minSize,使其成为独立块打包出来,下图是小编项目打包后webpack-bundle-analyzer插件分析出来的结果:

因为本项目文件确实比较多比较大,所以可以看到好几个超过1M的大文件,原来默认的时候app.a900c452.js是2M多的,按上面的设置了以后,小于1M的只剩3个,已经是不小的进度,但是明显还可以再抽离一部分,于是在再对webpack-bundle-analyzer插件图分析,看哪些还可以分割出来的,同步异步那两个文件再做分割:

vue.config.js:

module.exports = {
    chainWebpack( config ) {
        //process.env.NODE_ENV的值要在.env.development和.env.production文件分别配置,不想判断的直接删除when方法这一行
        config.when( process.env.NODE_ENV==="production",  ( config )=>{
            config.optimization.splitChunks({
                chunks:"all",//initial同步,async异步,all同步或者异步
                automaticNameDelimiter:"-",
                cacheGroups: {
                    elementUI: {
                        name: "chunk-elementUI",
                        priority: 40,
                        test: /[\/]node_modules[\/]_?element-ui(.*)/,
                        minSize:0
                    },
                    formMaking: {
                        name:"chunk-formMaking",
                        test: resolve('lib/vue-form-making/dist'),
                        priority: 39,
                        minSize:0,
                        enforce:true
                    },
                    antDesignVue: {
                        name:"chunk-antDesignVue",
                        test: /[\/]node_modules[\/]_?ant-design-vue(.*)/,
                        priority: 39,
                        minSize:0,
                        enforce:true
                    },
                    antDesign: {
                        name:"chunk-antDesign",
                        test: /[\/]node_modules[\/]_?@ant-design(.*)/,
                        priority: 39,
                        minSize:0,
                        enforce:true
                    },
                    moment: {
                        name: "chunk-moment",
                        priority: 39,
                        test: /[\/]node_modules[\/]_?moment(.*)/,
                        minSize:0,
                        enforce:true
                    },
                    jeecg: {
                        name:"chunk-jeecg",
                        test: /[\/]node_modules[\/]_?@jeecg(.*)/,
                        priority: 39,
                        minSize:0,
                        enforce:true
                    },
                    quill: {
                        name:"chunk-quill",
                        test: /[\/]node_modules[\/]_?quill(.*)/,
                        priority: 39,
                        minSize:0,
                        chunks: "async",//仅打包异步引用的依赖
                    },
                    nodesAsync: {
                        name:"chunk-nodesAsync",
                        test: /[\/]node_modules[\/]/,
                        priority: 10,
                        minChunks: 2,
                        chunks: "async",//仅打包异步引用的依赖
                        reuseExistingChunk:true
                    },
                    nodesInitial: {
                        name:"chunk-nodesInitial",
                        test: /[\/]node_modules[\/]/,
                        priority: 9,
                        minChunks: 1,
                        chunks: "initial",//仅打包同步引用的依赖
                        reuseExistingChunk:true
                    },
                    components: {
                        name: "chunk-components",
                        test: resolve('src/components'),
                        minChunks: 2,
                        priority: 5,
                        reuseExistingChunk: true
                    }
                }
            })
            config.optimization.runtimeChunk("single")
            config.plugin('webpack-bundle-analyzer').use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin)
        })
    }
}

打包后的结果图如下:

可以明显看到这次的分块里nodesInitial已经不见了,因为已经被分成其他的小块了,但是为什么这次的包总大小会比上次的要大300K左右呢,其实是因为抽离出来之后有部分依赖在其他模块还有引用的,无法完全分割的话只好重复打包了,这个可以根据打包情况调整代码的引用方式;

分包过程中可能发现大文件会转移,比如一开始可能是chunk-nodesInitial.js很大,增加缓存组来分割它以后,发现nodesInitial文件不见了,这时候反而app.xxxxx.js变得很大了,这个问题我还没想明白,只看哪个文件大,对它分包就行了;

缓存组分包过程中如果发现分不出来,可以加上enforce: true参数,有点强行拽出来的意思,小编在分块过程中也发现这个情况,比如增加缓存组antDesignVue后,打包是可以独立分离出来的,但是如果再加一个缓存组moment或jeecg,会发现,可能jeecg分出来了,但是ant-design-vue又被拽回去原来的包了,要不就是jeecg分不出来,这时候只要加了enforce: true参数,就能分出来,试过不是权重优先级priority的问题,还要多研究研究,过程中基本是看结果图,看哪个文件大,就分一个缓存组;

本次案例分块的主要逻辑是,先分同步和异步两大块,再从这两大块里找最大的继续分割,同步分出来的就都使用chunks:”initial”,异步分出来的就都是chunks:”async”,一直往下分……

那分到什么程度为止呢,小编的结论是,如果继续分下去,总包变大了,那基本就可以停止了,因为总包变大意味着有依赖重复打包了,就不要尝试抽离这个包了,换一个包继续尝试,如果大文件里每一个包都分不出来了,那就不继续分了,到这个程度基本上分包已到了相对合理的地步,但如果分到总包变大还有大文件,可以尝试换个角度分块,一开始分的两个大包大小最好接近二等分,说明两个包之间的代码耦合度比较低,越低越利于分割,多试试;

本次的分块成果:由原来的192个文件,减少到182个文件,最大文件值由6M缩小到所有文件不超过2M,因为有些单个文件本来就是1M多的,比如formMaking,已经没法再分了,所以暂时优化至此,后续深入了解再做调整;

还有个要提醒的问题,就是使用enforce: true参数后,不知会不会引发使用该组件的页面空白,只是小编猜测,大家可以自己验证下;

本项目小编验证过暂时是没有问题的,分的包可以说非常完美,最后还使用gzip压缩,将原来140M的包,压缩到70多M,可以说这次的分块压缩非常满意;gzip压缩的文章在这里:

vue打包之compression-webpack-plugin实现gzip压缩


本文参考资料:
https://blog.51cto.com/u_13961087/4911889

前端新手交流群
欢迎加入web前端新手交流qq群:
734802480(已满)、 794324979

更多文章

栏目文章


Copyright © 2014-2022 seozhijia.net 版权所有-粤ICP备13087626号-4