- 升级背景
公司项目是18年创建的,当时应该是使用webpack3.6,随着项目越来越大以及依赖越来越多,启动与打包时间都变得比较长,并且部分依赖如node-sass与sass-loader只能支持npm-v6以下安装依赖才可以正常启动,而最近上的项目随着使用vue-cli3以及vue3+vite+ts等需要更高版本的node与npm环境,经常切换nvm管理也比较麻烦,所以就有了本次升级过程,在提高开发与打包效率的同时也可以将一些已经被弃用的loader和plugin替换掉
- 成果展示
升级前:
开发启动时间
打包生产时间
升级后:
开发启动时间:
怎么还增加时间了呢?那不是白白升级啦?其实本来的webpack3配置上已经做了一定的优化,升级webpack5主要是为了便利使用缓存以及弃用一些老旧loader顺便体验下新特性,当开启了cash之后:
打包生产时间:
其实打包开始也是和原本差不多的,但是在使用了SWC构建以及image-webpack-loader图片压缩与externals将部分三方库如vue及全家桶vuex、router、以及element-ui、echarts等抽离出来之后,打包速度从原来的2分半降到1分钟,还是很显著的提升的!
- 升级过程
首先主要参考了以下文章
先把webpack和相应的依赖升级,
package.json中的dependencies:
"autoprefixer": "^7.1.2",
"babel-core": "^6.22.1",//更换
"babel-helper-vue-jsx-merge-props": "^2.0.3",//更换
"babel-loader": "^7.1.1",//更换
"babel-plugin-component": "^1.1.1",//更换
"babel-plugin-syntax-jsx": "^6.18.0",//更换
"babel-plugin-transform-runtime": "^6.22.0",//更换
"babel-plugin-transform-vue-jsx": "^3.5.0",//更换
"babel-preset-env": "^1.3.2",//更换
"babel-preset-stage-2": "^6.22.0",//更换
"chalk": "^2.0.1",
"compression-webpack-plugin": "^1.1.2",//升级
"copy-webpack-plugin": "^4.0.1",//升级
"css-loader": "^0.28.0",//webpack5内置
"extract-text-webpack-plugin": "^3.0.0",//更换
"file-loader": "^1.1.4",//webpack5内置
"friendly-errors-webpack-plugin": "^1.6.1",
"html-webpack-plugin": "^2.30.1",//升级
"node-notifier": "^5.1.2",
"node-sass": "^4.14.1",//升级
"optimize-css-assets-webpack-plugin": "^3.2.0",//更换
"portfinder": "^1.0.13",
"postcss-import": "^11.0.0",
"postcss-loader": "^2.0.8",
"postcss-url": "^7.2.1",
"prettier": "^1.12.1",//升级
"sass-loader": "^7.3.1",//升级
"uglifyjs-webpack-plugin": "^1.1.1",//更换
"url-loader": "^0.5.8",//webpack5内置
"vue-loader": "^13.3.0",//升级
"vue-style-loader": "^3.0.1",//webpack5内置
"vue-template-compiler": "^2.5.2",//与vue版本保持一致
"webpack": "^3.6.0",//升级
"webpack-dev-server": "^2.9.1",//升级
"webpack-merge": "^4.1.0"//升级
现有依赖:
"@babel/core": "^7.12.10",
"@babel/eslint-parser": "^7.12.16",
"@babel/plugin-transform-arrow-functions": "^7.12.1",
"@babel/plugin-transform-modules-commonjs": "^7.19.6",
"@babel/plugin-transform-runtime": "^7.12.1",
"@babel/preset-env": "^7.12.1",
"@babel/runtime-corejs3": "^7.12.1",
"autoprefixer": "^7.1.2",
"babel-loader": "^8.2.5",
"chalk": "^4.1.2",
"compression-webpack-plugin": "^10.0.0",
"copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.7.1",
"css-minimizer-webpack-plugin": "^4.2.2",
"html-webpack-plugin": "^5.5.0",
"mini-css-extract-plugin": "^2.6.1",
"node-notifier": "^10.0.1",
"ora": "^3.4.0",
"portfinder": "^1.0.32",
"postcss-import": "^11.0.0",
"postcss-loader": "^2.0.8",
"postcss-url": "^7.2.1",
"rimraf": "^2.6.0",
"sass": "^1.32.7",
"sass-loader": "^12.0.0",
"style-loader": "^3.3.1",
"vue-loader": "^15.10.0",
"vue-template-compiler": "^2.5.2",
"webpack": "^5.74.0",
"webpack-bundle-analyzer": "^4.7.0",
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.11.1",
"webpack-merge": "^5.8.0"
主要变更文件涉及下列
基本配置:
webpack.base.conf.js中,主要是loader与plugins的变更
//变更前
const path = require('path');
const utils = require('./utils');
const config = require('../config')
var webpack = require('webpack');
function resolve (dir) {
return path.join(__dirname, '..', dir)}
module.exports = { ...不重要配置忽略
module: {
rules: [
{
oneOf:[
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
test:/\.css$/,
use:['style-loader','css-loader'],
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
},
{
test: /\.s[ca]ss$/,
use:['style-loader','css-loader','sass-loader']
}
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,//视音频等其他同样使用url-loader
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
]
}
]
},
plugins: [
new webpack.ProvidePlugin({//全局引入jQuery
$$: "jquery", jQuery: "jquery",
"windows.jQuery": "jquery"
}),
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
// 剥离除 “en” 以外的所有语言环境,减少包大小
]
}
//变更后新增
const { VueLoaderPlugin } = require('vue-loader') //vue-loader 15以上需要额外引入
module:{
rules: [
...忽略不变 {
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
type: 'asset',//webpack5自带资源解析
parser: {
dataUrlCondition: {
maxSize: 8 * 1024,
},
},
generator: {
filename: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
]}
plugins:{
new VueLoaderPlugin(),//vue-loader15以上用法变更
new webpack.ProvidePlugin({
$$: "jquery",
jQuery: "jquery",
"windows.jQuery": "jquery"
}),
//忽略moment.js语言包写法变更
new webpack.IgnorePlugin({
resourceRegExp:/^\.\/locale$/,
contextRegExp:/moment$/,
}),
}
开发环境配置,主要变更devServe中属性写法与 CopyWebpackPlugin 的写法变更,具体还是看webpack官方文档更清楚
webpack.dev.conf.js:
//变更前
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')
const path = require('path')
const baseWebpackConfig = require('./webpack.base.conf')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
const portfinder = require('portfinder')
const devWebpackConfig = merge(baseWebpackConfig, {
mode:'development',
devServer: {
clientLogLevel: 'warning',
hot: true,
contentBase: false, // since we use CopyWebpackPlugin.
compress: true,
host: HOST || config.dev.host,
port: PORT || config.dev.port,
open: config.dev.autoOpenBrowser,
overlay: config.dev.errorOverlay
? { warnings: false, errors: true } : false,
publicPath: config.dev.assetsPublicPath,
proxy: config.dev.proxyTable,
quiet: true, // necessary for FriendlyErrorsPlugin
watchOptions: { poll: config.dev.poll, } },
plugins: [
new webpack.DefinePlugin({
'process.env': require('../config/dev.env')
}),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
// https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true
}), // copy custom static assets
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../static'),
to: config.dev.assetsSubDirectory,
ignore: ['.*']
}
])
]})
//变更后
... 不变省略
const { merge } = require('webpack-merge')//新版webpack-merge需要解构
const devWebpackConfig = merge(baseWebpackConfig, {
mode:'development', // cheap-module-eval-source-map is faster for development
devtool: config.dev.devtool,
// these devServer options should be customized in /config/index.js
devServer: {
... 不变省略
client:{
logging:'error',
overlay: config.dev.errorOverlay
? { warnings: false, errors: true }
: false,
},
static:{
publicPath: config.dev.assetsPublicPath,
},
},
plugins: [
... 不变省略
new CopyWebpackPlugin(
{
patterns:[
{
from: path.resolve(__dirname, '../static'),
to: config.dev.assetsSubDirectory,
globOptions:{
dot:true,
gitignore:true,
ignore:['.*'],
}
}
]
})
]
})
生产打包配置,主要是压缩外部css与js代码,以及一些分包的配置,升级后弃用了UglifyJsPlugin、OptimizeCSSPlugin、optimize.CommonsChunkPlugin等,改为使用
MiniCssExtractPlugin 、TerserPlugin进行处理。
webpack.prod.conf.js:
//变更前
const baseWebpackConfig = require('./webpack.base.conf')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const webpackConfig = merge(baseWebpackConfig,
{ plugins: [// http://vuejs.github.io/vue-loader/en/workflow/production.html
new webpack.DefinePlugin({ 'process.env': env }),
new UglifyJsPlugin({
uglifyOptions: {
compress: {
warnings: false
}
},
sourceMap: config.build.productionSourceMap,
parallel: true
}),// extract css into its own file
new ExtractTextPlugin({
filename: utils.assetsPath('css/[name].[contenthash].css'),
allChunks: true,
}),
new HtmlWebpackPlugin({
filename: config.build.index,
template: 'index.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
// https://github.com/kangax/html-minifier#options-quick-reference },
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
chunksSortMode: 'dependency' }),
// keep module.id stable when vendor modules does not change
new webpack.HashedModuleIdsPlugin(),// enable scope hoisting
new webpack.optimize.ModuleConcatenationPlugin(),
// split vendor js into its own file
... 三方库的chunk压缩,太长了删减
new webpack.optimize.CommonsChunkPlugin({
name: 'app',
async: 'vendor-async',
children: true,
minChunks: 3 }),
]})
if (config.build.productionGzip) {//开启Gzip,需要服务端配合设置才可以解析
const CompressionWebpackPlugin = require('compression-webpack-plugin')
webpackConfig.plugins.push(
new CompressionWebpackPlugin({
asset: '[path].gz[query]',
algorithm: 'gzip',
test: new RegExp( '\\.(' +
config.build.productionGzipExtensions.join('|') + ')$'
),
threshold: 10240,
minRatio: 0.8 }) )}//变更后
...省略不变
const { merge } = require('webpack-merge')//新版需要解构
const MiniCssExtractPlugin = require("mini-css-extract-plugin");//代替OptimizeCSSPluginconst
OptimizeCSSPlugin = require('css-minimizer-webpack-plugin')//写入optimization配置
const webpackConfig = merge(baseWebpackConfig, {
mode:'production',
target:['web' , 'es5'],//设置打包es5处理兼容
plugins: [ // http://vuejs.github.io/vue-loader/en/workflow/production.html
new MiniCssExtractPlugin({
filename: utils.assetsPath('css/[name].[contenthash].css'),
chunkFilename:utils.assetsPath('css/[name].[contenthash].css'), }),
new HtmlWebpackPlugin({ ... // 打包报错,未解决问题
chunksSortMode: 'auto', }),
new CopyWebpackPlugin(
{ patterns:[
{
from: path.resolve(__dirname, '../static'),
to: config.dev.assetsSubDirectory,
globOptions:{
dot:true,
gitignore:true,
ignore:['.*'],
}
}
]
}
)
],
optimization:{
minimize: true,
minimizer: [
new TerserPlugin(),
new OptimizeCSSPlugin(), ],
runtimeChunk: { name: 'runtime'
},
concatenateModules: true,
splitChunks: { ...自行查配置 }, },
})
if (config.build.productionGzip) {
... asset属性改为了filename
webpackConfig.plugins.push(
new CompressionWebpackPlugin({
filename: '[path][base].gz[query]',
})
)}
- 踩坑记录
主要有以下几点:
1.vue2 vue-loader 只支持15.10.0 16以上需要vue3,开始安装了17.0一直报错,忘截图了,还有就是15以上需要
const { VueLoaderPlugin } = require('vue-loader')
//并且在plugings使用
plugins:[new VueLoaderPlugin()]
2.import 方式的svg/icon等方式的方式变更
启动报错不能转换数据类型,最后确定是资源引入方式写法的问题,改好后OK
之前有些地方资源这样使用 import * as teamImg from '@/assets/img/home_1.png'
需要去掉 " * as" -import teamImg from '@/assets/img/home_1.png'
3./deep/的替换,vue-loader升级需要使用::v-deep或者 :deep(selector) ;类似的样式问题也有一些,但是都比较好解决
4.new webpack.IgnorePlugin(/^\.\/locale/)写法变更
new webpack.IgnorePlugin({
resourceRegExp:/^\.\/locale$/,
contextRegExp:/moment$/,
}),
5.image-webpack-loader有个依赖gifsicle的地址被墙了T_T;最后是通过cnpm进行了安装后跑通
npm install -g cnpm --registry=https://registry.npm.taobao.org
cnpm install -D image-webpack-loader
- 总结
1.本次升级主要是顺手把eslint与.prettierrc给配置上了(之前项目接手时并没有限制风格,团队开发时成员开发的组件命名习惯,缩进,目录结构等都比较随意,并且很少codeReview,改别人代码会比较费时,所以为了后续开发维护,先把风格统一,后续组件开发也尽量以统一风格进行)
2.就是启动速度和打包速度都有了不错的提升,节省了不少摸鱼时间啦~
3.尝试了SWC模式以及缓存等新特性,以及externals部分三方包,但是因为之前用ukpng出过问题,这次选择从字节CDN把js文件下载下来通过插件CDN的方式,虽然总体积没变,但是访问速度是提升的
CDN地址 cdn.bytedance.com/
4.说实话动老项目还是比较麻烦的,因为老司机常说老项目能跑就不要乱动,确实刚改的时候各种报错,特别是定位问题都要一步步解决,不过解决后对于工程化的相关概念和知识点又多了一些认识和巩固,这是团队必要的基建,也是必须要解决的痛点问题。