w
e
b
p
a
c
k
优化背景
webpack优化背景
webpack优化背景
- 前段时间我很幸运地接了一个古老的项目,webpack的版本还停留在4.0的版本,本来没想优化的,但是由于每次启动需要6分钟,保存一下页面热启动也需要2分钟,直接把我整崩溃了,这种心情 一言难尽
- 我开发了一周,每天大概浪费2个小时在等待页面启动和编译上,我真的崩溃了
- 于是我决定使用一天的时间优化webpack配置
- 开始了我的优化之旅
优化成果
- 第一次启动 10s
- 热启动 5s 之内
- 第一次打包 20s之内
- 再次打包 10s之内
优化前提
- 仔细阅读webpack的开发文档
- 注意 很多api是只有webpack5的版本才支持的
优化的最大痛点
- webpack 的版本过低(4.0),很多优秀的api和属性甚至是打包编译思想都没有用到
突如其来的一道灵光,换壳,将vue-cli3.6的版本直接升级到vue-cli5.0
- 业务代码不变,改变项目的整体壳子和vue-clid等配置
可行性分析
业务代码影响分析
- 都是vue项目并且vue的版本都是用的是vue2.6 的版本,所以业务代码是没有什么风险点的,唯一要改的是配置
配置代码分析
- 由于vue-cli3.6升级到vue-cli5.0之后,webpack 的优秀属性就都可以使用了
升级vue-cli步骤
使用vue-cli5脚手架搭建一个空壳项目
替换全局代码
替换业务代码
- 将src文件夹全部替换
- 将public 文件夹替换
package.json 修改
- 将vue-cli3.6老项目中的全局配置项和vue-cli5新壳子中的配置项目对比,把vue-cli5中出现过的依赖项直接删除,使用vue-cli5默认的
- 将vue-cli3.6老项目中出现的项目依赖项移捞出来放到新壳子中去
- 删除代码中没有引用的依赖项
- 由于老项目中的依赖项可能是其他项目搬过来的,所以必定会出现没有用到的依赖项
- 在代码中src文件夹下全局搜索,判断插件是否有使用到,没有用到的直接删除
.babelrc 文件修改
- 由于vue-cli3.6和vue-cli5.0的babel处理方式不太一样,所以需要修改
- 老的代码就不放了
vue-cli5的.babelrc代码
| { |
| "presets": [ |
| "@vue/cli-plugin-babel/preset" |
| ], |
| "plugins": [ |
| "equire", |
| [ |
| "import", |
| { |
| "libraryName": "view-design", |
| "libraryDirectory": "src/components" |
| } |
| ] |
| ] |
| } |
复制
修改vue.config.js
配置 vue.config.js
定义环境变量
| const isDev = process.env.NODE_ENV == 'development' |
复制
transpileDependencies 关闭
| transpileDependencies: isDev ? false : true, |
| |
复制
- 默认情况下 babel-loader 会忽略所有 node_modules 中的文件。你可以启用本选项,以避免构建后的代码中出现未转译的第三方依赖。
开启 terser-webpack-plugin 代码压缩
开启参数
TerserPlugin 配置
- parallel 最大并行进程
- cache 是否开启缓存
- sourceMap 是否开启 sourceMap
- terserOptions
-
-
- compress.drop_console 是否删除console 生产环境删除,开发环境保留
-
- compress.drop_debugger 是否删除debugger 生产环境删除,开发环境保留
-
- output.comments 是否删除comments 生产环境删除,,开发环境保留
| let minimizeConfig = { |
| minimize: true, |
| minimizer: [new TerserPlugin({ |
| parallel: 4, |
| cache: true, |
| sourceMap: false, |
| terserOptions: { |
| compress: { |
| drop_console: isDev ? false : true, |
| drop_debugger: isDev ? false : true, |
| }, |
| output: { |
| comments: false, |
| }, |
| }, |
| } |
| )], |
| concatenateModules: false, |
| } |
复制
开启摇树优化
| config.optimization = { |
| |
| |
| usedExports: isDev ? false : true, |
| sideEffects: false, |
| |
| splitChunks: { |
| chunks: 'all', |
| minSize: 20000, |
| minRemainingSize: 0, |
| minChunks: 1, |
| maxAsyncRequests: 30, |
| maxInitialRequests: 30, |
| enforceSizeThreshold: 50000, |
| cacheGroups: { |
| |
| common: { |
| chunks: 'initial', |
| minSize: 0, |
| minChunks: 2, |
| }, |
| |
| vendor: { |
| priority: 1, |
| test: /node_modules/, |
| chunks: 'initial', |
| minSize: 0, |
| minChunks: 2, |
| }, |
| default: { |
| minChunks: 2, |
| priority: -20, |
| reuseExistingChunk: true |
| } |
| } |
| } |
| } |
复制
watchOptions 忽略node_modules
| config.watchOptions = { |
| ignored: /node_modules/, |
| aggregateTimeout: 600, |
| poll: 1000 |
| } |
复制
devtool配置
| config.devtool = isDev ? 'source-map' : false |
复制
开发环境
- 启用sourcemap
- devtool 设置值为 source-map
生产环境
- 关闭sourcemap
- 注意设置为false,否则无法彻底关闭sourcemap
开启cache缓存
- 缓存是前端优化的常规手段,webpack 中同样可以
- 缓存分内存和磁盘文件缓存,此处是使用文件磁盘缓存
-
-
- allowCollectingMemory 收集在反序列化期间分配的未使用的内存,仅当 cache.type 设置为 ‘filesystem’ 时生效。这需要将数据复制到更小的缓冲区中,并有性能成本。
- cacheDirectory 文件缓存的目录
-
- 指定为档期目录下的 .temp_cache 文件夹下
-
- 它下面分development 和 production 文件夹
| config.cache = { |
| type: 'filesystem', |
| allowCollectingMemory: true, |
| cacheDirectory: path.resolve(__dirname, '.temp_cache'), |
| } |
| |
复制
- 注意 建议定期删除 temp_cache 文件夹,以免占用过多磁盘空间
小伙伴担忧
是否会卡顿
- 缓存太多是否会导致,内存爆掉甚至于卡顿
- 我在此明确地告诉你,不会,原因很简单
-
-
output 配置
| config.output = { |
| clean: true, |
| compareBeforeEmit: false, |
| filename: '[name].[contenthash].bundle.js', |
| chunkFilename: 'js/[name].[contenthash].bundle.js', |
| path: path.join(__dirname, 'testProject'), |
| publicPath: isDev ? '/' : '/testProject/', |
| } |
复制
备注 chainWebpack 和 configureWebpack 区别
官方介绍
- chainWebpack 是一个函数,会接收一个基于 webpack-chain 的 ChainableConfig 实例。允许对内部的 webpack 配置进行更细粒度的修改。
- configureWebpack 如果这个值是一个对象,则会通过 webpack-merge 合并到最终的配置中
通俗区别
- chainWebpack用于修改,会和默认配置项合并,追加某个属性值
- configureWebpack用于合并,向原有配置项追加配置,直接添加整个配置项
- 所以,只修改某个属性使用 chainWebpack,添加配置项使用configureWebpack
webpack辅助工具
webpack-bundle-analyzer
speed-measure-webpack-plugin
| config.plugins.push(new BundleAnalyzerPlugin()) |
| config.plugins.push(new WebpackBar({ name: 'PC', color: '#07c160' })) |
复制
| const {defineConfig} = require('@vue/cli-service') |
| const TerserPlugin = require('terser-webpack-plugin'); |
| const path = require('path') |
| const webpack = require('webpack'); |
| const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin |
| const resolve = dir => path.join(__dirname, dir) |
| const packageName = require('./package.json').name |
| const isDev = process.env.NODE_ENV == 'development' |
| const SpeedMeasurePlugin = require("speed-measure-webpack-plugin"); |
| const WebpackBar = require('webpackbar'); |
| |
| module.exports = defineConfig({ |
| |
| publicPath: isDev ? '/' : '/testProject/', |
| outputDir: 'testProject', |
| lintOnSave: false, |
| transpileDependencies: isDev ? false : true, |
| chainWebpack: config => { |
| config.plugin('speed-measure-webpack-plugin').use(SpeedMeasurePlugin).end(); |
| if (!isDev) { |
| config.plugins.delete('prefetch'); |
| |
| config.plugins.delete('preload'); |
| } |
| config.plugin('speed-measure-webpack-plugin').use(SpeedMeasurePlugin).end(); |
| }, |
| configureWebpack: config => { |
| if (!isDev) { |
| config.entry = "./src/main.js" |
| config.output = { |
| clean: true, |
| compareBeforeEmit: false, |
| filename: '[name].[contenthash].bundle.js', |
| chunkFilename: 'js/[name].[contenthash].bundle.js', |
| path: path.join(__dirname, 'testProject'), |
| publicPath: isDev ? '/' : '/testProject/', |
| |
| library: `${packageName}-[name]`, |
| libraryTarget: 'umd', |
| chunkLoadingGlobal: `webpackJsonp_${packageName}`, |
| } |
| config.plugins.push(new BundleAnalyzerPlugin()) |
| config.plugins.push(new WebpackBar({ name: 'PC', color: '#07c160' })) |
| } |
| config.resolve.alias = |
| |
| { |
| '@': resolve('src'), |
| '_c': resolve('src/components') |
| } |
| let minimizeConfig = { |
| minimize: true, |
| minimizer: [new TerserPlugin({ |
| parallel: 4, |
| cache: true, |
| sourceMap: false, |
| terserOptions: { |
| compress: { |
| drop_console: isDev ? false : true, |
| drop_debugger: isDev ? false : true, |
| }, |
| output: { |
| comments: false, |
| }, |
| }, |
| } |
| )], |
| concatenateModules: false, |
| } |
| config.optimization = { |
| |
| |
| usedExports: isDev ? false : true, |
| sideEffects: false, |
| |
| splitChunks: { |
| chunks: 'all', |
| minSize: 20000, |
| minRemainingSize: 0, |
| minChunks: 1, |
| maxAsyncRequests: 30, |
| maxInitialRequests: 30, |
| enforceSizeThreshold: 50000, |
| cacheGroups: { |
| |
| common: { |
| chunks: 'initial', |
| minSize: 0, |
| minChunks: 2, |
| }, |
| |
| vendor: { |
| priority: 1, |
| test: /node_modules/, |
| chunks: 'initial', |
| minSize: 0, |
| minChunks: 2, |
| }, |
| default: { |
| minChunks: 2, |
| priority: -20, |
| reuseExistingChunk: true |
| } |
| } |
| } |
| } |
| |
| if (!isDev) { |
| config.optimization = Object.assign(config.optimization, minimizeConfig) |
| console.log(config.module) |
| } |
| |
| config.watchOptions = { |
| ignored: /node_modules/, |
| aggregateTimeout: 600, |
| poll: 1000 |
| } |
| config.devtool = isDev ? 'source-map' : false |
| config.cache = { |
| type: 'filesystem', |
| allowCollectingMemory: true, |
| cacheDirectory: path.resolve(__dirname, '.temp_cache'), |
| } |
| } |
| }) |
| |
复制
优化成果
初次 npm run dev 运行速度
- 所有的缓存文件都清除,运行时间大概在36秒

基于缓存 npm run dev 运行速度
- 只需要 4.7秒

初次 npm run build 打包速度

基于缓存 npm run build 打包速度

项目体量
src文件数量和大小
- 770个文件,10MB

项目依赖大小
- 781MB

打包后文件 大小
- 仅有 9.8MB

个人总结
- webpack 本质上也是js,我们不会配置,可能只是不太熟悉,不要有恐惧心理
- 先看仔细阅读文档,重点看他的优化方案,整合下即可结合到项目中
致谢
- 感谢webpack官方文档提供的文档说明
- 感谢我的项目组给了我挑战自己的机会
- 感谢我的导师给予我的帮助
- 感谢您百忙之中抽时间阅读我写的博客,谢谢您的肯定,也希望对您能有所帮助
- 如果您有更好的见解请在评论区留言或者私聊我,期待与您的交流