2023年6月21日发(作者:)

深入理解基于vue-cli的webpack打包优化实践及探索转眼已经是2019年,短短三四年时间,webpack打包工具成为了前端开发中必备工具,曾经一度的面试题都是问,请问前端页面优化的方式有哪些?大家也是能够信手拈来的说出缓存、压缩文件、CSS雪碧图以及部署CDN等等各种方法,但是今天不一样了,可能你去面试问的就是,请问你是否知道webpack的打包原理,webpack的打包优化方法有哪些?所以该说不说的,笔者闲着没事研究了一下webpack的打包优化,可能大家都有看过类似的优化文章~

但是笔者还是希望能够给大家一些新的启发~1、准备工作:测速与分析bundle既然我们要优化webpack打包,肯定要提前对我们的bundle文件进行分析,分析各模块的大小,以及分析打包时间的耗时主要是在哪里,这里主要需要用到两个webpack插件,speed-measure-webpack-plugin和webpack-bundle-analyzer,前者用于测速,后者用于分析bundle文件。具体配置82936373839464748495051525354const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPluginconst smp = new SpeedMeasurePlugin({ outputFormat:"human",});s = {configureWebpack: ({ plugins: [ new ePlugin({ $: "zepto", Zepto: "zepto", }), new BundleAnalyzerPlugin(), ], optimization: { splitChunks: { cacheGroups: { echarts: { name: "chunk-echarts", test: /[]node_modules[]echarts[]/, chunks: "all", priority: 10, reuseExistingChunk: true, enforce: true, }, demo: { name: "chunk-demo", test: /[]src[]views[]demo[]/, chunks: "all", priority: 20, reuseExistingChunk: true, enforce: true, }, page: { name: "chunk-page", test: /[]src[]/, chunks: "all", priority: 10, reuseExistingChunk: true, enforce: true, }, vendors: { name: "chunk-vendors", test: /[]node_modules[]/, chunks: "all", priority: 5, reuseExistingChunk: true, enforce: true, }, }, }, }, })}由于是基于vue-cli脚手架的,所以其实vue-cli中已经帮你做了一些优化的工作,可以看到,原先项目最初的配置设置了splitchunk,进行代码分割,这在大型项目中是很有必要的,毕竟你不希望你的用户阻塞加载一个5MB大小的JS文件,所以做代码分割和懒加载是很有必要的。说远了,我们来看看这个配置,你需要用smp对配置进行再包裹,因为SpeedMeasurePlugin会对你的其他Plugin对象包裹一层代理,这样的目的是为了能够知道plugin开始和结束的时间~其次,BundleAnalyzerPlugin就跟普通的plugin一样,加载plugins数组的后面即可。接下来我们看一下最初的打包时间以及包内容分析:可以看到项目中较大的三个包,其中两个包是我们的第三方依赖,、lottie、lodash、echarts等。2、开始逐步优化2.1缩小文件查找和处理范围这是webpack优化中的常规操作,基本就是对模块和文件查找的优化,以及减少loader对一些不必要模块的处理,但是vue-cli中的loader并没有暴露给我们操作,所以其内置的loader处理无法由我们进行优化,但是其实vue-cli中的配置项已经对loader的查找路径进行了优化,如果你的项目也是使用了vue-cli,你可以通过以下命令行查看你现有的配置文件是怎样的:1npx vue-cli-service inspect > 具体可以翻阅vuecli官方文档。111213resolve:{ modules: [e(__dirname, 'node_modules')], alias:{ 'three':e(__dirname, './node_modules/three/build/'), 'zepto$':e(__dirname, './node_modules/zepto/dist/'), 'swiper$':e(__dirname, './node_modules/swiper/dist/js/'), 'lottie-web$':e(__dirname, './node_modules/lottie-web/build/player/' 'lodash$':e(__dirname, './node_modules/lodash/'), }},module:{ noParse:/^(vue|vue-router|vuex|vuex-router-sync|three|zepto|swiper|lottie-web|lodash)$/},通过modules指定查找第三方模块的路径。通过alias指定第三方模块直接查找到打包构建好的压缩js文件。通过module指定noparse,对第三方模块不再进行分析依赖。优化效果:2s?可以看到时间就减少了两三秒,在30s波动,感觉没有多大差别。2.2尝试使用happypack由于在进行webpack优化前,翻阅了很多有关webapck优化的文章,所以笔者也想尝试一下用happypack来优化打包时间。在想要用happypack进行的打包之前,大抵有这两种说法:1、webpack4中已经默认是多线程打包了,所以happypack打包效果不明显;2、vue不支持happypack打包,需要设置thread-loader。但是笔者想了一下,还是试试看把,大不了我只对JS和CSS文件设置happypack。但是问题又来了,vue-cli内置封装了loader,这个时候我要怎么拿到它的配置,改写里面的loader配置呢。通过翻阅vue-cli的官方文档我们可以看到以下使用介绍:configureWebpackType: Object | Function如果这个值是一个对象,则会通过 webpack-merge

合并到最终的配置中。如果这个值是一个函数,则会接收被解析的配置作为参数。该函数及可以修改配置并不返回任何东西,也可以返回一个被克隆或合并过的配置版本。为此,笔者特地调试进了vue-cli的源码一探究竟:流程介绍:由于我们执行命令行vue-cli-service build,其实是先去node_modules的.bin文件夹下查找相应的可执行文件,.bin下的vue-cli-service会映射到相应的第三方库内的执行文件。所以我们可以找到这个可执行文件的地址:/node_modules/@vue/cli-service/bin/找到了入口,接下来我们想要进入nodejs的调试,在以往的开发中,我们会通过node --inspect 的方式启动一个后台服务,然后在谷歌浏览器里进入调试界面(F12选择绿色的那个小按钮)但是这里却犯了难,由于我们的打包构建是一次执行的,不同于一个后台服务,是实时监听的,服务一直启动着。查阅了一下,如果是普通的nodejs文件想要调试的话,需要通过这样的方式:1node --inspect-brk=9229 所以,为了强行走进去vue-cli的源码进行调试,可看vue-cli的处理流程,我们需要这样输入以下命令行:1node --inspect-brk=9229 node_modules/@vue/cli-service/bin/ build上面的这个命令行,等价于vue-cli-service build。通过这样的方式,我们终于走进了vue-cli的源码,看了它的执行流程,你可以在对应的位置打下断点,查看此时的作用域内的变量数据。可以看到vue-cli源码里的这一段操作,会执行我们传入的函数,判断函数有没有返回值来决定是否要merge进其内部配置的config。通过这段代码我们可以看出,如果我们configWepack配置为函数,之后通过参数的形式获取到config配置项,本身是一个对象,对象是保留引用的形式,所以如果我们直接对传入的config对象进行修改,就可以实现我们最初的目标!修改vue-cli内置的loader!当然,除了断点进入里面看配置,刚才也说了,我们可以通过命令行输出为一个output文件查看现有的配置。这里可以给大家截图看一下vue-cli内部的配置:可能有点废话了,但是通过断点的方式,我们可以看到vue-cli其实已经对js文件设置了exclude,同时也帮我们设置好了cache-loader,意味着webpack常规的优化方式之一,使用cache-loader缓存它也帮我们做了。回到最初的起点,我们想要处理的是针对JS和CSS的loader,于是模仿大多数的配置,我进行了以下修改:configureWebpack:(config)=>{ ("webpack config start"); let originCssRuleLoader = [6].oneOf[0].use; let newCssRuleLoader = 'happypack/loader?id=css'; [6].oneOf[0].use = newCssRuleLoader [6].oneOf[1].use = newCssRuleLoader [6].oneOf[2].use = newCssRuleLoader [6].oneOf[3].use = newCssRuleLoader ...//other code}尝试对css的loader配置进行修改。之后对plugins进行一下配置:1234567plugins: [ new HappyPack({ id: 'css', threads: 4, loaders: originCssRuleLoader }), ],本以为这样就OK了,但是很遗憾的告诉大家,报错了...可以看到报错的内容,是在处理vue文件的时候,出了错误。如何解决笔者百度了,也谷歌了,大抵是说happypack不支持vue-loader,同时,根据报错也查了一下处理的方案,通过设置parallel参数,也还是无效。笔者甚至怀疑是自己的happypack配置不对,于是我把配置原样移植配置到另一个非vue项目中,一切运行正常。答案:此题无解~原因分析:由于vue文件中会含有CSS,所以vue-loader会提取出其中的css,交给其他loader处理,vue-loader-plugin会通过在vue文件后面加上查询字符串来告诉其他loader,针对这个文件要做处理。意味着什么呢?我们的vue-loader在处理文件的时候,通知其他loader处理,但是此时的loader配置已经被我们改写成了happypack,而vue又与happypack不兼容,最终导致了报错。很遗憾的告诉大家,vue-cli接入happypack--失败。(注:这一部分主要是笔者在webpack优化过程中的探索,虽然最终不能让自己的webpack打包很好的优化,但是在这个探索的过程中,我们也可以学到很多~包括 vue-cli对配置对象的处理?如何调试普通文件nodejs代码?vue-loader中对vue文件的处理流程?vue-loader-plugin帮我们做了什么事?而这些都是要自己慢慢翻阅,慢慢踩坑去了解的~)2.3使用dllplugin和大多数的webpack优化教程一样,笔者也尝试了利用dllplugin进行优化,该插件的本质,是提取出我们常用的第三方模块,单独打成一个文件包,之后插入到我们的html页面中,这样我们以后每次打包,都不需要针对第三方模块进行处理,毕竟第三方模块动辄成千上万行。流程介绍:1、配置针对第三方库打包2、中配置plugin3、html中引入dll打包出来的js文件。(一般采用部署CDN的方式)由于项目中有很多大型的第三方库,类似three、echart等,所以笔者进行了以下配置:()829363738const webpack = require("webpack")const path = require("path")const HtmlWebpackPlugin = require('html-webpack-plugin');s = { entry: { vuebundle: [ 'vue', 'vue-router', 'vuex', ], utils:[ 'lodash', 'swiper', 'lottie-web', 'three', ], echarts:[ 'echarts/lib/echarts', "echarts/lib/chart/bar", "echarts/lib/chart/line", "echarts/lib/component/tooltip", "echarts/lib/component/title", "echarts/lib/component/legend", ]

}, output: { path: e(__dirname, './static/'), filename: '[name].', library: '[name]_library' }, plugins: [ new gin({ path: (__dirname, 'build', '[name]-'), name: '[name]_library' }) ]}针对不同的库的大小进行划分,打了三个包,为啥不打成一个包?一个包那就太大了,你并不希望你的用户加载一个大型JS文件包而阻塞,影响页面性能。接下里是的配置:plugins: [ new ePlugin({ $: "zepto", Zepto: "zepto", }), new DllReferencePlugin({ manifest: require('./build/'), }), new DllReferencePlugin({ manifest: require('./build/'), }), new DllReferencePlugin({ manifest: require('./build/'), }), new BundleAnalyzerPlugin(), ]引入了DllPlugin。接下来配置HTML:(由于笔者没将DLL打包出来的js文件上传到CDN,所以只能本地自己起个node服务器返回静态资源了)1234567