vue-cli项目升级webpack4踩坑

5,472 阅读4分钟

webpack4 也发布3个月了,一直想体验一下。最近项目不忙,也感觉项目编译和打包的速度略慢,索性就把一个由 vue-cli 生成的项目从 webpack3 升级到 webpack4,期间遇到的问题也记录一下。

安装依赖

npm i webpack@latest webpack-cli --save-dev

出现报错信息:

报错信息

根据报错信息,逐个升级它们:

npm install extract-text-webpack-plugin@latest html-webpack-plugin@latest inject-loader@latest webpack-dev-middleware@latest webpack-dev-server@latest

顺便把其它 loadersplugins 都升级到最新版本

npm install webpack-bundle-analyzer@latest vue-template-compiler@latest webpack-merge@latest friendly-errors-webpack-plugin@latest copy-webpack-plugin@latest optimize-css-assets-webpack-plugin@latest

npm install css-loader@latest file-loader@latest url-loader@latest less-loader@latest postcss-loader@latest vue-loader@latest vue-style-loader@latest

升级的版本信息如下:

  • webpack@4.8.3

  • webpack-cli@2.1.4

  • html-webpack-plugin@3.2.0

  • extract-text-webpack-plugin@4.0.0-beta.0

  • webpack-dev-server@3.1.4

  • webpack-dev-middleware@3.1.3

  • friendly-errors-webpack-plugin@1.7.0

  • webpack-bundle-analyzer@2.13.1

  • webpack-merge@4.1.2

  • optimize-css-assets-webpack-plugin@4.0.1

  • copy-webpack-plugin@4.5.1

  • vue-template-compiler@2.5.16

  • postcss-loader@2.1.5

  • inject-loader@4.0.1

  • less-loader@4.1.0

  • css-loader@0.28.11

  • vue-style-loader@4.1.0

  • file-loader@1.1.11

  • vue-loader@15.2.0

  • url-loader@1.0.1

运行 npm run dev,又出现 eslint 的报错信息

eslint报错信息

npm i eslint@latest eslint-config-standard@latest eslint-friendly-formatter@latest eslint-loader@latest eslint-plugin-import@latest eslint-plugin-node@latest eslint-plugin-promise@latest eslint-plugin-standard@latest eslint-plugin-vue@latest

  • eslint-plugin-standard@3.1.0
  • eslint-plugin-vue@4.5.0
  • eslint-plugin-promise@3.7.0
  • eslint-plugin-import@2.12.0
  • eslint-config-standard@11.0.0
  • eslint-loader@2.0.0
  • eslint-plugin-node@6.0.1
  • eslint@4.19.1
  • eslint-friendly-formatter@4.0.1

配置

相比于 webpack 3,webpack 4 的配置部分改变,具体如下:

在 dev 环境中,添加 mode: 'development',去掉 webpack.NamedModulesPlugin 及 webpack.NoEmitOnErrorsPlugin 插件,因为 webpack4 开发模式已经内置。

// webpack.dev.conf.js
module.exports = {
  // ...
  mode: 'development',
  // ...
  plugins: {
      // new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
      // new webpack.NoEmitOnErrorsPlugin(),
  }
}

在 prod 环境中添加 mode 配置,用 optimization 代替以前的 webpack.optimize.CommonsChunkPlugin 、 uglifyjs-webpack-plugin 、 webpack.optimize.ModuleConcatenationPlugin 相关配置及引用

// webpack.production.prod.js
// const UglifyJsPlugin = require('uglifyjs-webpack-plugin')

const webpackConfig = merge(baseWebpackConfig, {
    // ...
    mode: 'production',
    // webpack4 内置
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          chunks: 'initial',
          name: 'vendors',
        },
        'async-vendors': {
          test: /[\\/]node_modules[\\/]/,
          minChunks: 2,
          chunks: 'async',
          name: 'async-vendors'
        }
      }
    },
    runtimeChunk: { name: 'runtime' }
  },
  // ...
  pluins: {
    // new UglifyJsPlugin({
    //   uglifyOptions: {
    //     beautify: false,
    //     comments: false,
    //     compress: {
    //       warnings: false,
    //       drop_console: true
    //     }
    //   },
    //   sourceMap: config.build.productionSourceMap,
    //   parallel: true
    // }),

    // ...

    // enable scope hoisting
    // new webpack.optimize.ModuleConcatenationPlugin(),
    // split vendor js into its own file
    // new webpack.optimize.CommonsChunkPlugin({
    //   name: 'vendor',
    //   minChunks (module) {
    //     // any required modules inside node_modules are extracted to vendor
    //     return (
    //       module.resource &&
    //       /\.js$/.test(module.resource) &&
    //       module.resource.indexOf(
    //         path.join(__dirname, '../node_modules')
    //       ) === 0
    //     )
    //   }
    // }),
    // extract webpack runtime and module manifest to its own file in order to
    // prevent vendor hash from being updated whenever app bundle is updated
    // new webpack.optimize.CommonsChunkPlugin({
    //   name: 'manifest',
    //   minChunks: Infinity
    // }),
    // // This instance extracts shared chunks from code splitted chunks and bundles them
    // // in a separate chunk, similar to the vendor chunk
    // // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
    // new webpack.optimize.CommonsChunkPlugin({
    //   name: 'app',
    //   async: 'vendor-async',
    //   children: true,
    //   minChunks: 3
    // }),
  }
}

运行 npm run dev,又出现 vue-loader 的报错信息:

vue-loader was used without the corresponding plugin. Make sure to include VueLoaderPlugin in your webpack config.

为了解决上面的 vue-loader 的报错,在 webpack.base.conf.js 中添加配置

// webpack.base.conf.js
const { VueLoaderPlugin } = require('vue-loader')

//...
plugins: [
  new VueLoaderPlugin()
]

运行 npm run dev,报错:

cyclic dependency报错信息

虽然有报错信息,但是没有显示错误的具体位置。在网上查找了很久,发现有人说他在template filerouting file 出现了循环引用。 根据这个,我突然发现在 main.js 和 向后台请求的api文件中都引用了路由文件,api.js 中引用是为了写 axios拦截器,出现 401、404 或 500 时,跳转相应页面。 注释掉 api.js 中对 router 的引用,确实可以成功运行了,但是 401、404、500 跳页的问题就没法解决了。 但是后来我又找到了另一种方法,在 HtmlWebpackPlugin 插件中添加或者修改 chunksSortMode: none。(不过不确定这方式会不会对性能优化等等有影响)

new HtmlWebpackPlugin({
  filename: 'index.html',
  template: 'index.html',
  inject: true,
  chunksSortMode: 'none'
}),

运行 npm run dev,没问题了。

接下来试试生产环境,运行 npm run build。1分钟过去了……5分钟过去了……中午吃饭1个小时都过去了……居然没反应,也没有报错。又尝试了好几次,依旧如此。在网上也没有找到相关问题。

于是我只好在 build.js 中逐步打断点,最后发现是 extract-text-webpack-plugin 插件的问题。 查找资料了解到 extract-text-webpack-plugin 其实是适配 webpack3 的,有个 extract-text-webpack-plugin@4.0.0-beta.0 版本可以适配 webpack4,但是我用的就是这个版本。

有人推荐用 mini-css-extract-plugin 来替代它,我就根据 文档尝试配置一下。

需要在 webpack.prod.conf.jsutils.js 两个文件中配置。

// webpack.prod.conf.js
// const ExtractTextPlugin = require('extract-text-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

// ...
// extract css into its own file
// new ExtractTextPlugin({
// ...
// })
// 升级 webpack4, 由 ExtractTextPlugin 改用 MiniCssExtractPlugin
new MiniCssExtractPlugin({
  filename: utils.assetsPath('css/[name].[contenthash].css'),
  allChunks: true,
}),
// utils.js
// const ExtractTextPlugin = require('extract-text-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

// ...

// 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 ExtractTextPlugin.extract({
  //     use: loaders,
  //     fallback: 'vue-style-loader'
  //   })
  // } else {
  //   return ['vue-style-loader'].concat(loaders)
  // }
  // 升级 webpack4, 由 ExtractTextPlugin 改用 MiniCssExtractPlugin
  return [
    options.extract ? MiniCssExtractPlugin.loader : 'vue-style-loader',
  ].concat(loaders)
}

运行 npm run build,终于成功了。

附上 webpack3 和 webpack4 的打包对比图,可以看到,体积少了一点(后期我会在边学习 webpack 边进行优化)但是打包的时间减少了一半,也不枉费我这么辛苦的升级。

webpack3

webpack4

参考

  1. vue-cli#webpack3.0 升级到webpack4.6
  2. Webpack4 新特性 及 Vue-cli项目升级
  3. https://github.com/jantimon/html-webpack-plugin/issues/870
  4. https://webpack.js.org/plugins/mini-css-extract-plugin/
  5. https://vue-loader.vuejs.org/guide/extract-css.html#webpack-4