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官方文档提供的文档说明
- 感谢我的项目组给了我挑战自己的机会
- 感谢我的导师给予我的帮助
- 感谢您百忙之中抽时间阅读我写的博客,谢谢您的肯定,也希望对您能有所帮助
- 如果您有更好的见解请在评论区留言或者私聊我,期待与您的交流