前端工程化- webpack | 项目打包webpack优化
背景
原项目使用vue2+vuecli脚手架搭建的。(note: vue cli现已处于维护模式,官方推荐使用create-vue来创建基于vite的新项目了)
vuecli,CLI 服务 (@vue/cli-service) 是一个开发环境依赖。它是一个 npm 包,局部安装在每个 @vue/cli 创建的项目中。CLI 服务是构建于 webpack 和 webpack-dev-server 之上的。
configureWebpack
此时,webpack配置最简单的方式就是在 vue.config.js 中的 configureWebpack 选项提供一个对象:
1 | // vue.config.js |
该对象将会被 webpack-merge 合并入最终的 webpack 配置。
如果你需要基于环境有条件地配置行为,或者想要直接修改配置,那就换成一个函数 (该函数会在环境变量被设置之后懒执行)。该方法的第一个参数会收到已经解析好的配置。在函数内,你可以直接修改配置,或者返回一个将会被合并的对象:
1 | // vue.config.js |
查看vue-cli项目的webpack配置:(用于审查的被序列化的格式)
webpack-chain
vue-cli中webpack 内部的配置是通过 webpack-chain(opens new window)维护的。这个库提供了一个 webpack 原始配置的上层抽象,使其可以定义具名的 loader 规则和具名插件,并有机会在后期进入这些规则并对它们的选项进行修改。
它允许我们更细粒度的控制其内部配置。这里有一些例子:
编译一个依赖模块、替换已有的基础的 Loader、修改插件选项等
例如:默认情况下 Babel 配置会跳过:
1 | // vue.config.js |
原项目vue.config.js配置
使用了compression-webpack-plugin进行gzip压缩资源;
设置使用svg-sprite-loader,在vue中使用svg;
使用ScriptExtHtmlWebpackPlugin(相当于增强版本的html-webpack-plugin,提供在生成的 html 文件中的 script 标签上增加某些属性,比如 async defer 、crossorigin等等),对runtime chunk 进行内联,不会在请求文件,减少http请求。
使用了splitChunks,进行分包( 最终的目的就是减少请求资源的大小和请求次数。因这两者是互相矛盾的,故要以项目实际的情况去使用 SplitChunks 插件,需切记中庸之道。 )。
Webpack 中一个提取或分离代码的插件,主要作用是提取公共代码,防止代码被重复打包,拆分过大的 js 文件,合并零散的 js 文件。例如将node_modules第三方依赖打包成一个chunk,由于elmentUI也比较大,也可以另外打包出来一个chunk,最后在把src/componts重复引入超过3次的组件也打包出来一个chunk。
抽象
一个项目(新项目),怎么考虑配置webpack?
目标:热启动、打包速度、打包体积
分析工具有哪些
分析当前项目构建问题
对策方案
优化前后对比(打包速度和体积大小)
改成vite的使用?
打包速度 | 配置难易 | 打包大小
vite使用esbuild+esm,现代浏览器esm优势,热更新启动快。vite的配置语法也比webpack简单一些,但webpack插件等社区生态比较丰富。而webpack目前没有支持esm,使用polyfill加载模块,打包大小会比vite大。
其他原因
已经稳定运行的webpack项目要换构建工具是一个潜在成本很大的事情。
webpack5
最近随着 WebAssembly 这个概念的兴起, 其实Webpack5在性能上也有一些质的飞跃
在项目维护上,能否在生产环境使用webpack,在测试环境使用vite?
案例情况
- 发现同一库存在多个版本;
- 第三方包太大;(例如moment)
- 没有按需加载;(例如lodash,echart)
- 在某些场景下才会使用到的包;(例如某些插件并不是每次打开页面的时候都会开启,这样可以把流程修改成在 url 上添加一个开启该插件的参数,并且在代码中改成动态加载)
思考
- 是否引入了第三方库的多个版本,例如 antd,react 等库
- 在引入第三方库的时候要先进行思考(考验的就是选型的能力)
- 是否要引入第三方库,能否自己手写实现一个?
- 多个第三方库进行对比,哪个更适合当下的场景?
- 第三方库包体积有多大?是否支持按需引入?
- 总结&寻求更多的包体积优化方案,在这里讲的只是发现问题的方式之一。
- 建立前端性能指标,例如包体积应该要小于多少才是合理的?(当然还有其他指标)。
- 寻求自动化解决方案,例如在构建完之后获取检查包体积是否符合指标,不符合则进行邮件 OR 钉钉通知对应的负责人进行优化。
项目分析和webpack优化
分析工具
webpack-bundle-analyzer
安装webpack-bundle-analyzer -D (vue-cli 里面会有)
在构建命令后面拼接 –report( package.json: script :”build:test”: “vue-cli-service build –report”,)
构建执行了之后,会生成report.html,即打包分析报告:
(打包后,有静态文件,其中,images文件夹的大小也不低,这里能否优化?)
report.html:
可以发现打包后的体积大概在4M,其中,echarts占1M(GZipped 320K):优化方向:按需使用;或者使用CDN方式引用(风险就是CDN失效会有问题,公共免费的CDN不稳定,除非公司自己有自己的CDN)。
打包体积优化:echarts按需使用
方案:https://echarts.apache.org/handbook/zh/basics/import/
注意:不要引入echarts的主题,不然按需引入的方式,会额外存在echarts的引入;
优化后:echart占了469KB(Gzipped 150KB)
打包体积优化:使用compression-webpack-plugin
线上的项目,一般都会结合构建工具 webpack 插件或服务端配置 nginx,来实现 http 传输的 gzip 压缩,目的就是把服务端响应文件的体积尽量减小,优化返回速度。
前端将文件打包成 .gz 文件,然后通过 nginx 的配置,让浏览器直接解析 .gz 文件,可以大大提升文件加载的速度,浏览器可以直接解析 .gz 文件并解压。
压缩文件格式介绍
.gz:浏览器可以直接解析并解压。
.br:BR 文件是使用 Brotli(一种开源数据压缩算法)压缩的文件,它包含网页资产,例如 CSS 、XML、SVG、JS 文件,以 Brotli 压缩数据格式压缩。 Web 浏览器(例如 Chrome 和 Firefox)使用 BR 文件来提高页面加载速度,Brotli 数据压缩格式旨在替代 Zopfli 压缩算法,该格式允许的压缩率比Zopfli 高大约 20%。
.br 压缩需要基于 nodejs >=v10.16.0 版本才能生成,一般本地没问题,需要注意线上服务器会可能发生找不到 zlib 的情况并进行安装。
使用插件:
1 | new CompressionPlugin({ |
效果:3M->1M
打包体积优化使用splitChunks进行分包
最终的目的就是减少请求资源的大小和请求次数。因这两者是互相矛盾的,故要以项目实际的情况去使用 SplitChunks 插件,需切记中庸之道。
图片压缩
基于 Node 库的 imagemin
配置 image-webpack-loader
- 有很多定制选项
- 可以引入更多第三方优化插件,例如pngquant
- 可以处理多种图片格式
pngquant: 是一款PNG压缩器,通过将图像转换为具有alpha通道(通常比24/32位PNG
文件小60-80%)的更高效的8位PNG格式,可显著减小文件大小。
pngcrush:其主要目的是通过尝试不同的压缩级别和PNG过滤方法来降低PNG IDAT数据
流的大小。
optipng:其设计灵感来自于pngcrush。optipng可将图像文件重新压缩为更小尺寸,而不
会丢失任何信息。
tinypng:也是将24位png文件转化为更小有索引的8位图片,同时所有非必要的metadata
也会被剥离掉
speed-measure-webpack-plugin
speed-measure-webpack-plugin 是一个 Webpack 插件,可以帮助你测量和分析构建项目时各个插件和 Loader 的执行时间。这可以帮助你优化构建速度,因为你可以找出慢速插件并优化它们。
安装npm install speed-measure-webpack-plugin –save-dev
vue-config.js 中引入
1 | const SpeedMeasurePlugin = require('speed-measure-webpack-plugin') |
执行构建:
多线程打包
方案一:happyPack (作者已经不在维护,其推荐使用thread-loader)
由于运行在 Node.js 之上的 webpack 是单线程模型的,我们需要 webpack 能同一时间处理多个任务,发挥多核 CPU 电脑的威力.HappyPack 就能实现多线程打包,它把任务分解给多个子进程去并发的执行,子进程处理完后再把结果发送给主进程,来提升打包速度。
方案二:thread-loader(vue-cli带有)
使用时,需将此 loader 放置在其他 loader 之前。放置在此 loader 之后的 loader 会在一个独立的 worker 池中运行。
其他
缩小文件搜索范围
- 优化loader配置:include 、 exclude
- 优化module.noParse配置: 忽略对部分没采用模块化的文件的递归解析处理
- 优化resolve.modules配置: 去哪些目录下寻找第三方模块
- 优化resolve.alias配置
- 优化resolve.mainFields配置
- 优化resolve.extensions配置:配置在尝试匹配过程中用到的后缀列表
减少打包文件
- 提取公共代码
- 动态链接DllPlugin (dll 选项将被删除。 Webpack 4 应该提供足够好的性能,并且在 Vue CLI 内维护 DLL 模式的成本不再合理;issue:https://github.com/vuejs/vue-cli/issues/1205 )
- externals
- Tree Shaking
缓存(二次打包时间)
- babel 缓存 cacheDirectory: true
- cache-loader
- contenthash
多进程
- happypack
- thread-loader
** 并行压缩:**
- terser-webpack-plugin (vue-cli默认带有)
- terser-webpack-plugin 使用 Terser 来进行 JavaScript 代码的压缩和混淆。Terser 是一个 JavaScript 压缩器和美化器,它可以将 JavaScript 代码压缩成更小的体积,从而加快网页的加载速度,提高用户体验。
修改:
1 | module.exports = { |
优化前后对比
项目其实只添加了echarts按需加载,其他vue-cli已经做了优化和项目已经配置了分包策略、gzip压缩。
优化前后对比:
打包体积:4M->3.6M
打包速度:80s->50s
做了缓存,二次打包速度加快。
项目改用成vite(rollup配置)和对比
为了保证老项目的正常运行,在生产环境还是使用webpack,而在开发环境使用vite。
创建并配置vite.config.js:
1 | import { defineConfig } from 'vite' |
注意安装vite、@vitejs/plugin-vue和其他插件的版本,不然可能会报错:
1 | { |
几个关键的点:
使用vite-plugin-html做 index.html ejs模板的创建和数据的注入;
使用dotenv做vue cli变量的转换和在Vite下的兼容:
路径path使用path-browserify,文件名搜索extensions和做路径的alias别名定义:
vue、jsx等语法识别插件:
SVGIcons组件的改造和使用
main.js导入:
webpack wequire.context 和vite import.meta.globEager
vite动态路由,路由的使用
两种方式:(原理都是搜索路径,拿到文件路径值)
1 vite.config.js插件
2 使用vite import.meta.glob
cjs兼容问题
vite无法识别使用module.exports