vue2项目升级webpack5

背景

项目是基于vue2和webpack3的项目,然后这个项目经过逐年的迭代,代码和文件数量都非常的庞大,开发运行和编译运行的速度都非常的慢,有时候你去吃完饭回来还在运行。

开始探索

最开始简单的认为只要设置webpack运用多线程、babel开启缓存等在网上能查到的优化方式就应该能解决这个问题,但是这一套的东西整下来发现效果并不明显,打包时间依然是很慢,还是二十多分钟,也就是说这些方式已经没办法解决这个祖传项目了。那么我们就去尝试升级一下我们的webpack,试一下能不能解决这个问题。

vue2 webpack3升级webpack5全过程

首先确认一点,webpack5需要node10.13.0以上的版本,也不建议升级成最新的版本,我测试过14和16的版本,都是可以支持升级后的运行和打包的。

首先贴一下package文件

perl 复制代码
{
  "version": "1.0.0",
  "description": "A Vue. js project",
  "author": "",
  "private": true,
  "scripts": {
    "dev": "cross-env NODE_ENV=development webpack server --progress --config build/webpack.dev.conf.js",
    "start": "npm run dev",
    "unit": "jest --config test/unit/jest.conf.js --coverage",
    "eZe": "node test/e2e/runner.js",
    "test": "npm run unit && npm run ele",
    "lint": "eslint --ext js, vue sc test/unit test/e2e/specs",
    "build:dev": "cross-env NODE_ENV=development ENV_CONFIG=dev node build/build.js",
    "build:sit": "cross-env NODE_ENV=sit ENV_CONFIG=sit node build/build.js",
    "build:uat": "cross-env NODE_ENV=uat ENV CONFIG=uat node build/build.js",
    "build:prod": "cross-env NODE_ENV=production ENV_CONFIG=prod node build/build. js"
  },
  "dependencies": {
    "@babel/runtime": "^7.27.6",
    "@babel/runtime-corejs2": "^7.27.6",
    "axios": "^0.19.0",
    "babel-loader": "18.0.0",
    "echarts": "^5.4.2",
    "element-ui": "^2.13.2",
    "jquery": "^3.7.0",
    "svg-sprite-loader": "A6.0.11",
    "vue": "^2.5.2",
    "vue-audio-visual": "A2.4.1",
    "vue-drag-resize": "^1.3.2",
    "vue-lazy-component": "A0.1.5",
    "vue-router": "13.3.4",
    "vue-socket.io": "^3.0.9",
    "vuedraggable": "^2.23.0",
    "vuex": "^3.6.2",
    "webpack-jquery-ui": "^2.0.1",
    "webpackbar": "^5.0.0-3"
  },
  "devDependencies": {
    "@babel/core": "^7.0.0",
    "@babel/plugin-proposal-class-properties": "^7.0.0",
    "@babel/plugin-proposal-decorators": "^7.0.0",
    "@babel/plugin-proposal-export-namespace-from": "^7.0.0",
    "@baba/plugin-proposal-function-sent": "^7.0.0",
    "@baba/plugin-proposal-json-strings": "^7.0.0",
    "@babel/plugin-proposal-numeric-separator": "^7.0.0",
    "@babel/plugin-proposal-throw-expressions": "^7.0.0",
    "@babel/plugin-syntax-dynamic-import": "^7.0.0",
    "@babel/plugin-syntax-import-meta": "^7.0.0",
    "@babel/plugin-syntax-jsx": "^7.0.0",
    "@babel/plugin-transform-runtime": "^7.0.0",
    "@babel/preset-env": "^7.0.0",
    "@soda/friendly-errors-webpack-plugin": "^1.8.1",
    "@swc/core": "^1.12.7",
    "autoprefixer": "^7.1.2",
    "babel-eslint": "19.0.0",
    "babel-helper-vue-jsx-merge-props": "^2.0.3",
    "babel-jest": "^21.0.2",
    "babel-plugin-dynamic-import-node": "^1.2.0",
    "babel-plugin-syntax-jsx": "^6.18.0",
    "babel-plugin-transform-es2015-modules-commonjs": "^6.26.0",
    "babel-plugin-transform-runtime": "^6.22.0",
    "babel-plugin-transform-vue-jsx": "^3.5.0",
    "babel-register": "^6.22.0",
    "chalk": "^2.0.1",
    "copy-webpack-plugin": "^4.0.1",
    "cross-env": "^7.0.0",
    "cross-spawn": "15.0.1",
    "css-loader": "^7.1.2",
    "css-minimizer-webpack-plugin": "^7.0.2",
    "eslint": "^4.15.0",
    "eslint-config-standard": "^10.2.1",
    "eslint-friendly-formatter": "^3.0.0",
    "eslint-loader": "^1.7.1",
    "eslint-plugin-import": "^2.7.0",
    "eslint-plugin-node": "^5.2.0",
    "eslint-plugin-promise": "^3.4.0",
    "eslint-plugin-standard": "^3.0.1",
    "eslint-plugin-vue": "^4.0.0",
    "file-loader": "^6.2.0",
    "html-webpack-plugin": "^5.4.0",
    "jest": "^22.0.4",
    "jest-serializer-vue": "^0.3.0",
    "mini-css-extract-plugin": "^2.4.3",
    "nightwatch": "^0.9.12",
    "node-notifier": "^5.1.2",
    "optimize-css-assets-webpack-plugin": "^3.2.0",
    "ora": "^1.2.0",
    "path-browserify": "^1.0.1",
    "portfinder": "^1.0.13",
    "postcss-import": "^11.0.0",
    "postcss-loader": "^8.1.1",
    "postcss-url": "^7.2.1",
    "rimraf": "^2.6.0",
    "sass": "٨1.30.0",
    "sass-loader": "^10.1.0",
    "selenium-server": "13.0.1",
    "semver": "^5.3.0",
    "shelljs": "10.7.6",
    "terser-webpack-plugin": "^5.3.14",
    "url-loader": "^4.1.1",
    "vue-jest": "^1.0.2",
    "vue-loader": "^15.8.0",
    "vue-style-loader": "^4.1.3",
    "vue-template-compiler": "^2.6.14",
    "webpack": "5.37.0",
    "webpack-bundle-analyzer": "^4.5.0",
    "webpack-cli": "^5.1.4",
    "webpack-dev-server": "^4.7.4",
    "webpack-merge": "^5.7.3",
    "webpack-parallel-uglify-plugin": "^1.1.2"
  },
  "engines": {
    "node": ">=6.0.0",
    "npm": ">=3.0.0"
  },
  "browserslist": [
    ">1%",
    "last 2 versions",
    "not ie <= 8"
  ]
}
升级过程中遇到的问题
1. 打包后发现图片、字体图标加在失败,而且图片和字体图标的输出目录在根目录下,没有输出在对应的static下的img和fonts目录下

经过排查和阅读webpack的文档发现,老项目用的url-loader、file-loader等配置会与webpack5本身的asset模块冲突,所有升级后的项目需要使用webpack的asset、asset/resource和asset/inline去处理了。

2. 打包后部署发现字体图标加载失败,并显示404

经过排查发现是MiniCssExtractPlugin.loader抽取css文件时,publicPath设置的不对,导致在查找字体图标时的路径不对,所以在配置文件中需要设置"../../"。

3. 运行npm run dev 页面显示GET /404

经过排查发现升级webpack5以后,publicPath需要区分development和production,分别设置为"/"和"./"。

4. 运行成功后发现无法主动跳转到首页

经过排查和查阅文档发现,webpack5不在主动去处理node的path模块的浏览器兼容问题,页面代码用到了这个path模块,所以导致无法正确加载,解放方式使用path-browserify替换node的path模块。

5. 不支持>>> 这种样的穿透语法

修改页面代码使用::deep或者:deep()替换老的语法。

6. 在开发环境下页面报错会有overlay阻塞调试

在webpack的配置文件中添加client:{overlay:false}

配置文件

Utils.js

javascript 复制代码
'use strict'
const path = require('path')
const config = require('../config')
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const packageConfig = require('../package.json')
exports.assetsPath = function (_path) {
    const assetsSubDirectory = config.build.assetsSubDirectory
    return path.posix.join(assetsSubDirectory, _path)
}
exports.cssLoaders = function (options) {
    options = options || {}
    const cssLoader = {
        loader: 'css-loader',
        options: {
            sourceMap: options.sourceMap
        }
    }
​
    const postcssLoader = {
        loader: 'postcss-loader',
        options: {
            sourceMap: options.sourceMap
        }
    }
​
    // generate loader string to be used with extract text plugin
    function generateLoaders(loader, loaderOptions) {
        const loaders = options.usePostCSS ? [cssLoader.postcssLoader] : [cssLoader]
        if (loader) {
            loaders.push({
                loader: loader + '-loader',
                options: Object.assign({}, loaderOptions, {
                    sourceMap: options.sourceMap
                })
            })
        }
// Extract CSS when that option is specified
// (which is the case during production build)
        if (options.extract) {
            return [
                {
                    loader: MiniCssExtractPlugin.loader,
                    options: {
                        publicPath: '../../',
                    }
​
                }
​
            ].concat(loaders)
        } else {
            return ['vue-style-loader'].concat(loaders)
        }
    }
​
    return {
        css: generateLoaders(),
        postcss: generateLoaders(),
        less: generateLoaders('less'),
        sass: generateLoaders('sass'),
        scss: generateLoaders('scss'),
    }
}
exports.styleLoaders = function (options) {
    const output = []
    const loaders = exports.cssLoaders(options)
    for (const extension in loaders) {
        const loader = loaders[extension]
        output.push({
            test: new RegExp('\.' + extension, 'g'),
            use: loader,
        })
​
    }
    return output
}
​
exports.createNotifierCallback = () => {
    const notifier = require('node-notifier')
    return (severity, errors) => {
        if (severity !== 'error') return
        const error = errors[0]
        const filename = error.file && error.file.split('!').pop()
        notifier.notify({
            title: packageConfig.name,
            message: severity + ':' + error.name,
            subtitle: filename || '',
            icon: path.join(__dirname, 'logo.png'),
        })
    }
}

webpack.base.conf.js

javascript 复制代码
'use strict'
const webpack = require('webpack')
const path = require('path')
const utils = require(' /utils')
const config = require('../config')
const vueLoaderConfig = require('./vue-loader.conf')
const {VueLoaderPlugin} = require("vue-loader")
​
//const HappyPack = require( 'happypack' )
function resolve(dir) {
    return path.join(__dirname, "..", dir)
}
​
const createLintingRule = () => ({
    test: /١. (js| vue) $/,
    loader: 'eslint-loader',
    enforce: 'pre',
    include: [resolve('src'), resolve('test')],
    options: {
        formatter: require('eslint-friendly-formatter'),
        emitWarning: !config.dev.showEslintErrorsIn0verlay
    }
})
module.exports = {
    context: path.resolve(__dirname, '../'),
    entry: {
        app: './src/main.js'
    },
    output: {
        path: config.build.assetsRoot,
        filename: '[name].js',
        publicPath: config.build.assetsPublicPath
    },
    resolve: {
        extensions: ['.js', '.vue', '.json', 'scss'],
        alias: {
            'vue$': 'vue/dist/vue.esm.js',
            '@': resolve('src'),
            'img': resolve('src/assets')
        },
        modules: [
            resolve('src'),
            resolve('node modules')
        ]
    },
    module: {
        rules: [
            {
                test: /.vue$/,
                loader: 'vue-loader',
                options: vueLoaderConfig
            },
            {
                test: /.js$/,
                use:['babel-loader'],
                include: [resolve('src'), resolve('test')],
                exclude: /node_modules/
            },
            {
                test: /.(png|jpg|jpeg|gif|svg)(?.*)?$/,
                type: 'asset/resource',
                exclude: /node_modules/,
                generator:{
                    filename: 'img/[name].[hash].[ext]',
                }
            },
            {
                test: /.(mp4|webm|ogg|mp3|wav|m4a|aac|oga|flac)(?.*)?$/,
                type: 'asset/resource',
                exclude: /node_modules/,
                generator:{
                    filename: 'media/[name].[hash].[ext]',
                }
            },
            {
                test: /.(woff|woff2|eot|ttf|otf)(?.*)?$/,
                type: 'asset/resource',
                generator:{
                    filename: 'fonts/[name].[hash].[ext]',
                }
            }
        ]
    },
    node: {
        global: true
    },
    plugins: [
        new webpack.ProvidePlugin({
            JQuery: "jquery",
            $: "jquery"
        }),
        new VueLoaderPlugin()
    ]
}
​
​
    

webpack.dev.conf.js

php 复制代码
'use strict'
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('@soda/friendly-errors-webpack-plugin')
const portfinder = require('portfinder')
const HOST = process.env.HOST
const PORT = process.env.PORT && Number(process.env.PORT)
const devWebpackConfig = merge(baseWebpackConfig, {
    module: {
        rules: utils.styleLoaders({sourceMap: config.dev.cssSourceMap, usePostCSS: true})
    },
​
// these devServer options should be customized in /config/index.js
    devServer: {
        // clientLogLevel: 'warning'.
        client: {
            overlay: false
        },
        historyApiFallback: {
            rewrites: [
                {from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html')}
            ]
        },
        hot: true,
        compress: true,
        host: HOST || config.dev.host,
        port: PORT || config.dev.port,
        open: config.dev.auto0penBrowser,
        proxy: config.dev.proxyTable,
    },
    plugins: [
        new webpack.DefinePlugin({
            'process.env': require('../config/dev.env')
        }),
        new webpack.HotModuleReplacementPlugin(),
        new HtmlWebpackPlugin({
            filename: 'index.html',
            template: 'index.html',
        }),
        new CopyWebpackPlugin([{
            from: path.resolve(__dirname, 'index.html'),
            to: config.dev.assetsPublicPath,
            ignore: ['.*']
        }])
    ]
})
​
module.exports = new Promise((resolve, reject) => {
    portfinder.basePort = process.env.PORT || config.dev.port
    portfinder.getPort((err, port) => {
        if (err) {
            reject(err)
        } else {
            process.env.PORT = port
            devWebpackConfig.devServer.port = port
            devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
                compilationSuccessInfo: {
                    message: [`xxxxxxxxxx`],
                    onError: err => reject(err)
                },
            }))
            resolve(devWebpackConfig)
        }
    })
})
​

webpack.prod.conf.js

php 复制代码
'use strict'
const path = require('path')
const utils = require('./utils')
const webpack = require('webpack')
const config = require(' ../config')
const {merge} = require('webpack-merge')
const baseWebpackConfig = require(' ./webpack.base.conf')
const CopyWebpackPlugin = require(' copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const WebpackBar = require('webpackbar')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const TerserWebpackPlugin = require('terser-webpack-plugin')
const Version = new Date().getTime()
const env = config.build[process.env.ENV_CONFIG + 'Env']
const webpackConfig = merge(webpackConfig, {
    mode: 'production',
    module: {
        rules: utils.styleLoaders({
            sourceMap: config.build.productionSourceMap,
            extract: true,
            usePostCSS: true,
        })
    },
    output: {
        path: config.build.outputPath,
        filename: 'js/[name].[chunkhash].' + Version + '.js',
        chunkFilename: 'js/[name].[chunkhash].' + Version + '.js',
    },
    optimization: {
        minimize: true,
        minimizer: [
            new TerserWebpackPlugin({
                minify: TerserWebpackPlugin.swcMinify,
                parallel: true,
                extractComments: false,
                terserOptions: {
                    compress: {
                        drop_console: true,
                        drop_debugger: true,
                    },
                    format: {
                        comments: false,
                    }
                }
            }),
            new CssMinimizerPlugin({
                minimizerOptions: {
                    presets: [
                        'default',
                        {
                            discardUnused: false,
                            normalizeUnicode: false,
                            reduceVariables: false,
                        }
                    ]
                }
            })
        ],
        runtimeChunk: {name: 'runtimeChunk'},
        concatenateModules: true,
        splitChunks: {
            chunks: 'all',
            cacheGroups: {
                libs:{
                    name: 'chunk-libs',
                    test: /[\/]node_modules[\/]/,
                    priority: 10,
                    chunks: 'initial',
                },
                vue:{
                    name: 'vue',
                    test: /[\/]node_modules[\/]_?vue(.*)/,
                    priority: 20,
                    chunks: 'initial',
                },
                elementUI:{
                    name: 'element-ui',
                    priority: 0,
                    test: /[\/]node_modules[\/]_?element-ui(.*)/
                },
                echarts:{
                    name: 'echarts',
                    priority: 20,
                    test: /[\/]node_modules[\/]_?echarts(.*)/
                },
                jquery:{
                    name: 'jquery',
                    priority: 20,
                    test: /[\/]node_modules[\/]_?jquery(.*)/
                },
                vuedraggable:{
                    name: 'vuedraggable',
                    priority: 20,
                    test: /[\/]node_modules[\/]_?vuedraggable(.*)/
                },
                fortawesome:{
                    name: 'fortawesome',
                    priority: 20,
                    test: /[\/]node_modules[\/]_?fortawesome(.*)/
                }
            }
        },
        moduleIds: 'deterministic'
    },
    plugins: [
        new webpack.DefinePlugin({
            'process.env': env
        }),
        new MiniCssExtractPlugin({
            filename: 'css/[name].css',
            chunkFilename: 'css/[name].css',
        }),
        new HtmlWebpackPlugin({
            filename: 'index.html',
            template: 'index.html',
            inject: 'true',
            minify: {
                removeComments: true,
                collapseWhitespace: true,
                removeAttributeQuotes: true,
            },
            chunksSortMode: 'auto'
        }),
        new CopyWebpackPlugin([{
            from: path.resolve(__dirname, './static'),
            to: config.build.outputPath,
            ignore: ['.*'],
        }]),
        new WebpackBar(),
    ]
})
​
if (config.build.productionGzip){
    const CompressionWebpackPlugin = require('compression-webpack-plugin')
    webpackConfig.plugins.push(new CompressionWebpackPlugin({
        asset: '[path][name].gz]',
        algorithm: 'gzip',
        test: /.js$/,
        threshold: 10240,
        minRatio: 0.8,
    }))
}
module.exports = webpackConfig
总结

升级完webpack5以后,对应的项目开发环境运行速度和编译速度都有质的提升,我们的项目测试下来,编译打包从之前的30多分钟到现在的6分钟,开发运行从之前的20多分钟到现在的2到3分钟,可以说升级webpack5后确实让我们快了很多。

相关推荐
Ali酱3 分钟前
远程这两年,我才真正感受到——工作,原来可以不必吞噬生活。
前端·面试·远程工作
金金金__8 分钟前
优化前端性能必读:浏览器渲染流程原理全揭秘
前端·浏览器
Data_Adventure12 分钟前
Vue 3 手机外观组件库
前端·github copilot
泯泷17 分钟前
Tiptap 深度教程(二):构建你的第一个编辑器
前端·架构·typescript
屁__啦24 分钟前
前端错误-null结构
前端
lichenyang45324 分钟前
从0开始的中后台管理系统-5(userList动态展示以及上传图片和弹出创建用户表单)
前端
未来之窗软件服务28 分钟前
解析 div 禁止换行与滚动条组合-CSS运用
前端·css
程序员JerrySUN32 分钟前
当前主流GPU全景讲解:架构、功能与应用方向
数据库·人工智能·驱动开发·redis·缓存·架构
不远处的小阿秋1 小时前
2025年,前端还需要虚拟DOM吗
前端
DcTbnk1 小时前
tailwindcss、postcss、autoprefixer,这三个分别是干嘛的
前端