玖叶教程网

前端编程开发入门

vue2的老框架项目升级nodejs(11到18),webpack(3到5)硬肝3个日夜

4年前给一个美术馆做的微信公众号门票售卖H5、后台管理系统以及核销app,最近客户要求升级一些功能,由于是老项目了,当我读着以前写的烂代码,觉得这次很有必要重构一下框架,升级一下插件了。

心里是很有干劲的,但是这次升级竟硬硬的肝了我3个日夜,用了各种搜索引擎以及chatgtp等工具,教训依然惨痛!每一次取得进展之后总觉得胜利在望之时,却又总是被折磨的死去活来,每个晚上本打算11点结束战斗,却最后都是毫无例外被逼着熬到凌晨2点,而且最后都是带着问题被迫去睡觉,这对于一个37岁的老程序员来说极其难受!

先总结:

1. nodejs插件生态系统极其复杂,各种插件各种版本交叉错乱互相引用,某个插件的升降级都可能导致整个系统无法正常运行,因此一个老项目能不升级版本就尽量不升级。

2. 当你打算升级一个能够正常运行的项目时,每次升级插件解决一个旧问题还没来得及高兴却出现了新问题,有一种离成功越来越近最后却有个问题怎么都解决不了时,你要想起有句话这么说的,方向不对等于白费!当你崩溃的时候,试试回到原点重新选一条路。

这次有个富文本编辑框的插件需要重构替换,经过选型后,找到一款ckeditor5的免费插件,但是这个插件必须用nodejs18的版本才能支持,因此第一步就是先升级nodejs11升级到nodejs18版本,安装nodejs18版本的步骤不是重点,有需要的自己安装一下,我的开发机器是mac,查看一下node版本已经是v18了。

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 % 

中间走过的弯路太多,不详细交代了,只说下最重要的几个节点。每个节点都贴上最初的配置文件与升级后的配置文件。

一、升级gulp3到gulp4

package.json

升级前
    "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",

gulpfile.js

升级前(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;

二、升级webpack3到webpack5

package.json

升级前
    "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"
  },

三、升级打包文件脚本

升级前,开发配置与生产配置共同继承了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

'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'
  }
}

webpack.basedev.conf.js

'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'
  }
}

webpack.dev.conf.js

'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)
    }
  })
})

webpack.prod.conf.js

'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

升级成功后的所有配置文件都已经贴上了,有需要的可以直接参考。

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言