老项目webpack版本从3升到5踩坑记录

956 阅读5分钟
  • 升级背景

       公司项目是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分钟,还是很显著的提升的!

  • 升级过程

首先主要参考了以下文章

www.cnblogs.com/webhmy/p/14…

juejin.cn/post/708314…

juejin.cn/post/690484…

juejin.cn/post/708455…

先把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/,/moment/, /moment/)写法变更

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.说实话动老项目还是比较麻烦的,因为老司机常说老项目能跑就不要乱动,确实刚改的时候各种报错,特别是定位问题都要一步步解决,不过解决后对于工程化的相关概念和知识点又多了一些认识和巩固,这是团队必要的基建,也是必须要解决的痛点问题。