4年前给一个美术馆做的微信公众号门票售卖H5、后台管理系统以及核销app,最近客户要求升级一些功能,由于是老项目了,当我读着以前写的烂代码,觉得这次很有必要重构一下框架,升级一下插件了。 心里是很有干劲的,但是这次升级竟硬硬的肝了我3个日夜,用了各种搜索引擎以及chatgtp等工具,教训依然惨痛!每一次取得进展之后总觉得胜利在望之时,却又总是被折磨的死去活来,每个晚上本打算11点结束战斗,却最后都是毫无例外被逼着熬到凌晨2点,而且最后都是带着问题被迫去睡觉,这对于一个37岁的老程序员来说极其难受! 先总结: 1. nodejs插件生态系统极其复杂,各种插件各种版本交叉错乱互相引用,某个插件的升降级都可能导致整个系统无法正常运行,因此一个老项目能不升级版本就尽量不升级。 2. 当你打算升级一个能够正常运行的项目时,每次升级插件解决一个旧问题还没来得及高兴却出现了新问题,有一种离成功越来越近最后却有个问题怎么都解决不了时,你要想起有句话这么说的,方向不对等于白费!当你崩溃的时候,试试回到原点重新选一条路。 这次有个富文本编辑框的插件需要重构替换,经过选型后,找到一款ckeditor5的免费插件,但是这个插件必须用nodejs18的版本才能支持,因此第一步就是先升级nodejs11升级到nodejs18版本,安装nodejs18版本的步骤不是重点,有需要的自己安装一下,我的开发机器是mac,查看一下node版本已经是v18了。 中间走过的弯路太多,不详细交代了,只说下最重要的几个节点。每个节点都贴上最初的配置文件与升级后的配置文件。 一、升级gulp3到gulp4 package.json gulpfile.js 二、升级webpack3到webpack5 package.json 三、升级打包文件脚本 升级前,开发配置与生产配置共同继承了base配置,共有3个配置文件: 升级后,开发配置继承basedev配置,生产配置继承base配置,共有4个配置文件: 这样独立开继承关系,是因为我在第二天升级过程中遇到了很烦人的问题,当时好不容易解决了npm run build生产打包的问题,打包后的代码可以正常部署运行以后,npm run dev却又出现了问题,当我解决npm run dev的时候,有会导致npm run build出问题,最后变成一个互斥现象,运行时编译和生产打包编译同一种配置只有一个可以生效,然后为了定位问题,就拆解成4个配置文件,运行时编译和生产编译用各自的配置文件,互不干扰。 然后用4个配置文件后,终于到了最终问题出现那一刻! 运行时编译和生产编译都能正确编译,并且生产编译后打包的文件可以在nginx正常运行,而运行时编译虽然能正确编译,打开页面却一直都是白屏,控制台会报错找不到vue的错误,只有把webpack5再降级回webpack3以后,相应配置文件也回退版本后才能正常运行(记住,这里其实是三天升级的中间时刻状态的环境)!但是这样一来,webpack降级又影响了生产编译!进了死胡同,无法同时满足生产编译打包与运行时编译! 昨天夜里最痛苦的就是这一刻,实在解决不了这个问题就痛苦的睡觉去了! 今天继续肝的时候,发现了一个突破点,当时问了下自己,是不是因为这个出错的插件升级的太高了,于是就想着降级到原始版本时那个插件的版本再试试 高版本的css-loader必须用new VueLoaderPlugin()插件,但是在运行时编译的时候却会报错,vue-loader options has an unknown property 'minimize',这时候只能降级css-loader到1.0.0的版本,css-loader原有的minimize选项在1.0.0版本后已经移除,将这个插件降级到1.0.0版本后,本次升级任务出现的问题就全部解决了。 升级后: webpack.base.conf.js webpack.basedev.conf.js webpack.dev.conf.js webpack.prod.conf.js 升级成功后的所有配置文件都已经贴上了,有需要的可以直接参考。wang@wangdeMBP wx-mall-system-admin-web % nvm use 18.18.0
Now using node v18.18.0 (npm v9.8.1)
wang@wangdeMBP wx-mall-system-admin-web % node -v
v18.18.0
wang@wangdeMBP wx-mall-system-admin-web %
升级前
"gulp": "3.9.1",
"gulp-concat": "2.6.1",
"gulp-load-plugins": "1.5.0",
"gulp-replace": "0.6.1",
"gulp-shell": "0.6.5",
升级后
"gulp": "^4.0.0",
"gulp-concat": "^2.6.1",
"gulp-load-plugins": "^2.0.8",
"gulp-replace": "^1.1.4",
"gulp-shell": "^0.8.0",
升级前(v3.9.1)
var gulp = require('gulp');
var $ = require('gulp-load-plugins')();
var path = require('path');
var del = require('del');
var distPath = path.resolve('./dist');
var version = ''; // 版本号
var versionPath = ''; // 版本号路径
var env = ''; // 运行环境
// 创建版本号(年月日时分)
(function () {
var d = new Date();
var yy = d.getFullYear().toString().slice(2);
var MM = d.getMonth() + 1 >= 10 ? (d.getMonth() + 1) : '0' + (d.getMonth() + 1);
var DD = d.getDate() >= 10 ? d.getDate() : '0' + d.getDate();
var h = d.getHours() >= 10 ? d.getHours() : '0' + d.getHours();
var mm = d.getMinutes() >= 10 ? d.getMinutes() : '0' + d.getMinutes();
version = yy + MM + DD + h + mm;
versionPath = distPath + '/' + version;
})();
// 编译
gulp.task('build', $.shell.task([ 'node build/build.js' ]));
// 创建版本号目录
gulp.task('create:versionCatalog', ['build'], function () {
return gulp.src(`${distPath}/static/**/*`)
.pipe(gulp.dest(`${versionPath}/static/`))
});
// 替换${versionPath}/static/js/manifest.js window.SITE_CONFIG.cdnUrl占位变量
gulp.task('replace:cdnUrl', ['create:versionCatalog'], function () {
return gulp.src(`${versionPath}/static/js/manifest.js`)
.pipe($.replace(new RegExp(`"${require('./config').build.assetsPublicPath}"`, 'g'), 'window.SITE_CONFIG.cdnUrl + "/"'))
.pipe(gulp.dest(`${versionPath}/static/js/`))
});
// 替换${versionPath}/static/config/index-${env}.js window.SITE_CONFIG['version']配置变量
gulp.task('replace:version', ['create:versionCatalog'], function () {
return gulp.src(`${versionPath}/static/config/index-${env}.js`)
.pipe($.replace(/window.SITE_CONFIG\['version'\] = '.*'/g, `window.SITE_CONFIG['version'] = '${version}'`))
.pipe(gulp.dest(`${versionPath}/static/config/`))
});
// 合并${versionPath}/static/config/[index-${env}, init].js 至 ${distPath}/config/index.js
gulp.task('concat:config', ['replace:version'], function () {
return gulp.src([`${versionPath}/static/config/index-${env}.js`, `${versionPath}/static/config/init.js`])
.pipe($.concat('index.js'))
.pipe(gulp.dest(`${distPath}/config/`))
});
// 清空
gulp.task('clean', function () {
return del([versionPath])
});
gulp.task('default', ['clean'], function () {
// 获取环境配置
env = process.env.npm_config_qa ? 'qa' : process.env.npm_config_uat ? 'uat' : 'prod'
// 开始打包编译
gulp.start(['build', 'create:versionCatalog', 'replace:cdnUrl', 'replace:version', 'concat:config'], function () {
// 清除, 编译 / 处理项目中产生的文件
del([`${distPath}/static`, `${versionPath}/static/config`])
})
});
升级后(v4.0.0)
const gulp = require('gulp');
const $ = require('gulp-load-plugins')();
const path = require('path');
const del = require('del');
const { series } = require('gulp');
const distPath = path.resolve('./dist');
let version = ''; // 版本号
let versionPath = ''; // 版本号路径
let env = ''; // 运行环境
// 创建版本号(年月日时分)
(function () {
const d = new Date();
const yy = d.getFullYear().toString().slice(2);
const MM = (d.getMonth() + 1).toString().padStart(2, '0');
const DD = d.getDate().toString().padStart(2, '0');
const h = d.getHours().toString().padStart(2, '0');
const mm = d.getMinutes().toString().padStart(2, '0');
version = yy + MM + DD + h + mm;
versionPath = path.join(distPath, version);
})();
// 编译
function build() {
return gulp.src('src/*.js')
.pipe($.shell([ 'node build/build.js' ]));
}
// 创建版本号目录
function createVersionCatalog() {
return gulp.src(`${distPath}/static/**/*`)
.pipe(gulp.dest(`${versionPath}/static/`));
}
// 替换${versionPath}/static/js/manifest.js window.SITE_CONFIG.cdnUrl占位变量
function replaceCdnUrl() {
return gulp.src(`${versionPath}/static/js/manifest.js`, { allowEmpty: true })
.pipe($.replace(new RegExp(`"${require('./config').build.assetsPublicPath}"`, 'g'), 'window.SITE_CONFIG.cdnUrl + "/"'))
.pipe(gulp.dest(`${versionPath}/static/js/`));
}
// 替换${versionPath}/static/config/index-${env}.js window.SITE_CONFIG['version']配置变量
function replaceVersion() {
console.log('replaceVersion version', version)
console.log('`${versionPath}/static/config/index-${env}.js`', `${versionPath}/static/config/index-${env}.js`)
return gulp.src(`${versionPath}/static/config/index-${env}.js`)
.pipe($.replace(/\{VERSION\}/g, version))
.pipe(gulp.dest(`${versionPath}/static/config/`));
}
// 合并${versionPath}/static/config/[index-${env}, init].js 至 ${distPath}/config/index.js
function concatConfig() {
return gulp.src([`${versionPath}/static/config/index-${env}.js`, `${versionPath}/static/config/init.js`])
.pipe($.concat('index.js'))
.pipe(gulp.dest(`${distPath}/config/`));
}
// 清空
function clean() {
return del([versionPath]);
}
// 默认任务
function defaultTask(done) {
// 获取环境配置
env = process.env.npm_config_qa ? 'qa' : process.env.npm_config_uat ? 'uat' : 'prod';
series(
clean,
build,
createVersionCatalog,
replaceCdnUrl,
replaceVersion,
concatConfig,
function (cb) {
// 清除, 编译 / 处理项目中产生的文件
del([`${distPath}/static`, `${versionPath}/static/config`]);
cb();
}
)(done);
}
exports.build = build;
exports.createVersionCatalog = createVersionCatalog;
exports.replaceCdnUrl = replaceCdnUrl;
exports.replaceVersion = replaceVersion;
exports.concatConfig = concatConfig;
exports.clean = clean;
exports.default = defaultTask;
升级前
"webpack": "3.6.0",
"webpack-bundle-analyzer": "2.9.0",
"webpack-dev-server": "2.9.1",
"webpack-merge": "4.1.0"
升级后
"webpack": "^5.88.2",
"webpack-bundle-analyzer": "2.9.0",
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^3.0.1",
"webpack-merge": "^5.10.0"
升级前
"scripts": {
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
"start": "npm run dev",
"unit": "jest --config test/unit/jest.conf.js --coverage",
"e2e": "node test/e2e/runner.js",
"test": "npm run unit && npm run e2e",
"lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs",
"build": "gulp"
},
升级后
"scripts": {
"dev": "npx webpack serve --config build/webpack.dev.conf.js --color --progress",
"start": "npm run dev",
"unit": "jest --config test/unit/jest.conf.js --coverage",
"e2e": "node test/e2e/runner.js",
"test": "npm run unit && npm run e2e",
"lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs",
"build": "gulp"
},
'use strict'
const path = require('path')
const utils = require('./utils')
const config = require('../config')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const vueLoaderConfig = require('./vue-loader.conf')
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.showEslintErrorsInOverlay
}
})
module.exports = {
context: path.resolve(__dirname, '../'),
entry: {
app: ['babel-polyfill', './src/main.js']
},
output: {
path: config.build.assetsRoot, // 打包后生成的文件夹
filename: '[name].[contenthash:8].js', // js文件名称
clean: true, // 每次构建都清除dist包
publicPath: process.env.NODE_ENV === 'production'
? config.build.assetsPublicPath
: config.dev.assetsPublicPath
},
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'vue#39;: 'vue/dist/vue.esm.js',
'@': resolve('src'),
},
fallback: {
// prevent webpack from injecting useless setImmediate polyfill because Vue
// source contains it (although only uses it if it's native).
setImmediate: false,
// prevent webpack from injecting mocks to Node native modules
// that does not make sense for the client
dgram: 'empty',
fs: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty'
}
},
module: {
rules: [
// vue-loader 要放在匹配规则的第一个,否则会报错
{
test: /\.vue$/,
loader: 'vue-loader',
},
{
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader, // creates style nodes from JS strings
},
{
loader: "css-loader",
}
],
},
{
test: /\.(sass|scss)$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
},
{
test: /.js$/,
use: {
loader: 'babel-loader'
},
include: [resolve('src'), resolve('test'), resolve('node_modules/vue-cal')]
},
{
test: /.tsx?$/,
exclude: resolve('node_modules'),
use: [
{
loader: 'babel-loader'
},
{
loader: "ts-loader",
options: { appendTsxSuffixTo: [/.vue$/], transpileOnly: true }
}
]
},
{
test:/\.(png|jpg|jpeg|gif|svg)$/,
type: 'asset/resource', //在 webpack5 中,内置了资源模块(asset module),代替了 file-loader 和 url-loader
parser: {
//转base64的条件
dataUrlCondition: {
maxSize: 5 * 1024, // 10kb
}
},
generator:{
filename:'images/[name].[contenthash:6][ext]'
}
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, // 视频资源
loader: 'url-loader',
options: {
limit: 10000,
name: 'media/[name].[hash:7].[ext]',
},
},
{
test: /\.(eot|svg|otf|ttf|woff2?)$/,
type: 'asset/resource',
generator: {
filename: 'font/[name]-[hash:3][ext]'
}
},
],
},
// 引入外部库, 无需webpack打包处理
externals: {
mockjs: 'Mock',
echarts: 'echarts',
ueditor: 'UE'
}
}
'use strict'
const path = require('path')
const webpack = require('webpack')
const utils = require('./utils')
const config = require('../config')
const vueLoaderConfig = require('./vue-loader.conf')
const {VueLoaderPlugin} = require('vue-loader')
const HtmlWebpackPlugin = require('html-webpack-plugin')
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.showEslintErrorsInOverlay
}
})
module.exports = {
context: path.resolve(__dirname, '../'),
entry: {
app: ['babel-polyfill', './src/main.js']
},
output: {
path: config.build.assetsRoot, // 打包后生成的文件夹
filename: '[name].[contenthash:8].js', // js文件名称
clean: true, // 每次构建都清除dist包
publicPath: process.env.NODE_ENV === 'production'
? config.build.assetsPublicPath
: config.dev.assetsPublicPath
},
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'vue#39;: 'vue/dist/vue.esm.js',
'@': resolve('src'),
},
fallback: {
// prevent webpack from injecting useless setImmediate polyfill because Vue
// source contains it (although only uses it if it's native).
setImmediate: false,
// prevent webpack from injecting mocks to Node native modules
// that does not make sense for the client
dgram: 'empty',
fs: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty'
}
},
module: {
rules: [
...(config.dev.useEslint ? [] : []),
{
test: /\.vue$/,
loader: 'vue-loader',
options: vueLoaderConfig
},
{
test: /.js$/,
use: {
loader: 'babel-loader'
},
include: [resolve('src'), resolve('test'), resolve('node_modules/vue-cal')]
},
{
test: /.tsx?$/,
exclude: resolve('node_modules'),
use: [{
loader: 'babel-loader'
},
{
loader: "ts-loader",
options: {
appendTsxSuffixTo: [/.vue$/],
transpileOnly: true
}
}
]
},
{
test: /.(png|jpe?g|gif|svg)(\?.*)?$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 10 * 1024 // 10kb
}
},
generator: {
filename: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
use: {
loader: 'url-loader'
}
}
]
},
// 引入外部库, 无需webpack打包处理
externals: {
mockjs: 'Mock',
echarts: 'echarts',
ueditor: 'UE'
}
}
'use strict'
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const { merge }= require('webpack-merge')
// const baseWebpackConfig = require('./webpack.base.conf')
const baseDevWebpackConfig = require('./webpack.basedev.conf')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
const portfinder = require('portfinder')
const path = require('path')
const HOST = process.env.HOST
const PORT = process.env.PORT && Number(process.env.PORT)
function resolve (dir) {
return path.join(__dirname, '..', dir)
}
const devWebpackConfig = merge(baseDevWebpackConfig, {
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: {
clientLogLevel: 'warning',
historyApiFallback: true,
hot: true,
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,
}
},
module: {
rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
},
plugins: [
new webpack.DefinePlugin({
'process.env': require('../config/dev.env')
}),
new webpack.HotModuleReplacementPlugin(),
// https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true
})
],
})
module.exports = new Promise((resolve, reject) => {
portfinder.basePort = process.env.PORT || config.dev.port
portfinder.getPort((err, port) => {
if (err) {
reject(err)
} else {
// publish the new Port, necessary for e2e tests
process.env.PORT = port
// add port to devServer config
devWebpackConfig.devServer.port = port
// Add FriendlyErrorsPlugin
devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
compilationSuccessInfo: {
messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
},
onErrors: config.dev.notifyOnErrors
? utils.createNotifierCallback()
: undefined
}))
resolve(devWebpackConfig)
}
})
})
'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 MiniCssExtractPlugin = require('mini-css-extract-plugin')
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
// const { VueLoaderPlugin } = require('vue-loader');
const ProgressBarPlugin = require('progress-bar-webpack-plugin')
const env = process.env.NODE_ENV === 'testing'
? require('../config/test.env')
: require('../config/prod.env')
function resolve (dir) {
return path.join(__dirname, '..', dir)
}
const webpackConfig = merge(baseWebpackConfig, {
mode: 'production', // 设置为生产模式
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'vue#39;: 'vue/dist/vue.esm.js',
'@': resolve('src'),
},
fallback: {
// prevent webpack from injecting useless setImmediate polyfill because Vue
// source contains it (although only uses it if it's native).
setImmediate: false,
// prevent webpack from injecting mocks to Node native modules
// that does not make sense for the client
dgram: 'empty',
fs: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty'
}
},
// 使用 HashedModuleIdsPlugin 选项
optimization: {
moduleIds: 'deterministic',
innerGraph: false,
minimize: false,
sideEffects: false,
runtimeChunk: true,
splitChunks: {
chunks: "all",
minSize: 30 * 1024, // 30KB chunk文件的最小值
//maxSize: 2 * 1024 * 1024, //1M
minChunks: 2,//最小引入次数
maxAsyncRequests: 10, // 按需加载的最大并行请求数目
maxInitialRequests: 5, // 在入口点的并行请求的最大数目
name: "default",
cacheGroups: { //根据设置的test匹配特定的依赖将该代码分割出去
default: false,
vendor: {
name: "vendor",
test: /node_modules/,
chunks: "all",
priority: 10,//优先级
},
mintui: {
name: "mint-ui",
test: /mint-ui/,
chunks: "all",
priority: 20,
},
elementui: {
name: "element-ui",
test: /element-ui/,
chunks: "all",
priority: 20,
}
}
},
minimizer: [
//Terser 压缩工具来最小化和混淆你的 JavaScript 代码,以减小文件大小并提高加载性能
new TerserPlugin({
terserOptions: {
compress: {
warnings: false,
},
sourceMap: config.build.productionSourceMap, // 将 sourceMap 配置在 terserOptions 中
},
parallel: true,
}),
new CssMinimizerPlugin({
minimizerOptions: {
preset: [
'default',
{
discardComments: { removeAll: true },
},
],
},
}),
],
},
module: {
// rules: utils.styleLoaders({
// sourceMap: config.build.productionSourceMap,
// // extract: true,
// usePostCSS: true
// })
},
devtool: config.build.productionSourceMap ? config.build.devtool : false,
output: {
path: config.build.assetsRoot,
filename: 'js/[name].[chunkhash].js', // 此选项决定了每个输出 bundle 的名称
chunkFilename: 'js/[id].[chunkhash].js', // 此选项决定了非入口(non-entry) chunk 文件的名称
},
plugins: [
// http://vuejs.github.io/vue-loader/en/workflow/production.html
new webpack.DefinePlugin({
'process.env': env
}),
new ProgressBarPlugin({
complete: '█',
}),
// extract css into its own file
new MiniCssExtractPlugin({
filename: "css/[name].[contenthash:6].css"
}),
new HtmlWebpackPlugin({
title: "",
filename: process.env.NODE_ENV === 'testing' ? 'index.html' : config.build.index,
template: 'index.html',
inject: true,
cache: true, // 当文件没有发生任何改变时, 直接使用之前的缓存
minify: {
collapseWhitespace: true,
keepClosingSlash: true,
removeComments: true,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
minifyCSS: true,
minifyJS: {
mangle: {
toplevel: true
}
}
}
}),
// enable scope hoisting
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
new CopyWebpackPlugin({
patterns: [
{
from: path.resolve(__dirname, '../static'),
to: config.build.assetsSubDirectory,
globOptions: {
ignore: ['.*'], // 忽略以点开头的文件(如隐藏文件)
},
},
],
}),
]
})
if (config.build.productionGzip) {
const CompressionWebpackPlugin = require('compression-webpack-plugin')
webpackConfig.plugins.push(
new CompressionWebpackPlugin({
asset: '[path].gz[query]',
algorithm: 'gzip',
test: new RegExp(
'\\.(' +
config.build.productionGzipExtensions.join('|') +
')#39;
),
threshold: 10240,
minRatio: 0.8
})
)
}
if (config.build.bundleAnalyzerReport) {
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
webpackConfig.plugins.push(new BundleAnalyzerPlugin({ analyzerMode: 'static' }))
}
module.exports = webpackConfig