vue init webpackで作成したプロジェクトをwebpack4に移行する

今更ながらvue init webpackで作成したプロジェクトをwebpack4系に移行したのでメモ。

Vue CLI3系を利用すればwebpack4系のプロジェクトが生成されるけど、webpack.config自体は@vue/cli-serviceに内包されているため、全体像が掴みづらいので勉強の意味も込めて現状のものをアップデートした。
vue cli-serviceのコードを見ると、webpack-chainなどを利用して設定が生成され各種コマンドを実行している様子が伺える。

プロジェクト構成は大雑把に↓な感じ。(特に生成時から変えていない)

app
├── build
│   ├── utils.js
│   ├── webpack.base.conf.js
│   ├── webpack.dev.conf.js
│   ├── webpack.prod.conf.js
│   └── ...
├── config
│   ├── dev.env.js
│   ├── index.js
│   └── prod.env.js
├── dist
├── node_modules
├── src
├── static
├── .eslintrc.js
├── package.json
├── postcss.config.js
└── ...

依存関係アップデート

webpack-cliが必要になったのでこれも追加

// webpack関連
npm isntall --save-dev webpack@latest
npm isntall --save-dev webpack-bundle-analyzer@latest
npm isntall --save-dev webpack-cli@latest
npm isntall --save-dev webpack-merge@latest
npm isntall --save-dev html-webpack-plugin@latest
npm isntall --save-dev mini-css-extract-plugin@latest
npm uninstall extract-text-webpack-plugin

// vue関連
npm isntall vue@latest
npm isntall --save-dev vue-loader@latest
npm isntall --save-dev vue-style-loader@latest
npm isntall --save-dev vue-template-compiler@latest
   "dependencies": {
-    "vue": "^2.5.2"
+    "vue": "^2.5.17"
   },
"devDependencies": {
...
-    "extract-text-webpack-plugin": "^3.0.0",
     "file-loader": "^1.1.4",
     "friendly-errors-webpack-plugin": "^1.7.0",
-    "html-webpack-plugin": "^2.30.1",
+    "html-webpack-plugin": "^3.2.0",
+    "mini-css-extract-plugin": "^0.4.3",
-    "vue-loader": "^13.7.2",
-    "vue-style-loader": "^3.0.1",
-    "vue-template-compiler": "^2.5.2",
+    "vue-loader": "^15.4.2",
+    "vue-style-loader": "^4.1.2",
+    "vue-template-compiler": "^2.5.17",
-    "webpack": "^3.12.0",
-    "webpack-bundle-analyzer": "^2.13.1",
-    "webpack-dev-server": "^2.9.1",
-    "webpack-merge": "^4.1.3"
+    "webpack": "^4.20.2",
+    "webpack-bundle-analyzer": "^3.0.2",
+    "webpack-cli": "^3.1.1",
+    "webpack-dev-server": "^3.1.9",
+    "webpack-merge": "^4.1.4"

modeオプション追加

dev, prodのwebpack.confにmodeを追加し、mode設定により自動的にenableになるPluginを削除する。

  • build/webpack.dev.conf.js
    • mode: 'development' を追加
    • webpack.NamedModulesPluginを削除
    • webpack.NoEmitOnErrorsPluginを削除
  • build/webpack.prod.conf.js
    • mode: 'production' を追加
    • webpack.optimize.ModuleConcatenationPluginを削除
  • NODE_ENVの設定削除(modeで自動設定される)
    • config/dev.env.js
    • config/prod.env.js

https://webpack.js.org/concepts/mode/

mini-css-extract-plugin追加

build時にスタイルを.cssファイルに抽出するのにextract-text-webpack-pluginを利用しているが、

⚠️ Since webpack v4 the extract-text-webpack-plugin should not be used for css. Use mini-css-extract-plugin instead.

なので、mini-css-extract-pluginを代わりに使用する。

  • mini-css-extract-pluginを追加
npm isntall --save-dev mini-css-extract-plugin@latest
  • build/webpack.prod.conf.js
    • ExtractTextPluginをMiniCssExtractPluginに変更
-    new ExtractTextPlugin({
+    new MiniCssExtractPlugin({
       filename: utils.assetsPath('css/[name].[contenthash].css'),
-      // Setting the following option to `false` will not extract CSS from codesplit chunks.
-      // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.
-      // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`, 
-      // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
-      allChunks: true,
-    }),
  • build/utils.js
    • loader生成している、generateLoaders関数内のExtractTextPluginをMiniCssExtractPluginに変更(vue-style-loaderを最初にもってくること)
     // 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'
-      })
+      return ['vue-style-loader', MiniCssExtractPlugin.loader].concat(loaders)
     } else {
       return ['vue-style-loader'].concat(loaders)
     }

github.com

optimization.minimizerの設定

production modeの場合はデフォルトでminifyされるが、UglifyJsPluginのオプションを利用していしてるためoptimization.minimizerに移す。同時にOptimizeCSSPluginも。

  • build/webpack.prod.conf.js
+  optimization: {
+    minimizer: [
+      new UglifyJsPlugin({
+        uglifyOptions: {
+          compress: {
+            warnings: false
+          }
+        },
+        sourceMap: config.build.productionSourceMap,
+        parallel: true
+      }),
+      new OptimizeCSSPlugin({
+        cssProcessorOptions: config.build.productionSourceMap
+          ? { safe: true, map: { inline: false } }
+          : { safe: true }
+      })
+    ],
....
   plugins: [
-    new UglifyJsPlugin({
-      uglifyOptions: {
-        compress: {
-          warnings: false
-        }
-      },
-      sourceMap: config.build.productionSourceMap,
-      parallel: true
-    }),
-    // Compress extracted CSS. We are using this plugin so that possible
-    // duplicated CSS from different components can be deduped.
-    new OptimizeCSSPlugin({
-      cssProcessorOptions: config.build.productionSourceMap
-        ? { safe: true, map: { inline: false } }
-        : { safe: true }
     }),

https://webpack.js.org/configuration/optimization/#optimization-minimizer

optimization. splitChunksの設定

v4からCommonsChunkPluginは削除され、SplitChunksPluginになったので移行する。 どのように分割するかはプロジェクトによって異なるので、各自で設定する。

  • build/webpack.prod.conf.js
+  optimization: {
...
+    splitChunks: {
+      cacheGroups: {
+        vendors: {
+          test: /[\\/]node_modules[\\/]/,
+          name: 'vendors',
+          chunks: 'all'
+        }
+      }
+    }
+  },

...
   plugins: [
-    // 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
-        )
-      }
-    }),
-    // 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
-    }),

意図通りに分割できているかは、webpack-bundle-analyzerで確認する。

npm run build --report

https://webpack.js.org/plugins/split-chunks-plugin/ medium.com

optimization. runtimeChunkの設定

webpackは、モジュール管理のためruntimeとmanifestのコードを生成するのでそれらをmanifest.jsとして出力させる。

  • build/webpack.prod.conf.js
+  optimization: {
...
+    runtimeChunk: {
+      name: 'manifest'
+    },
...
   plugins: [
-    // 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
-    }),

こちらも、意図通りに分割できているかは、webpack-bundle-analyzerで確認する。

npm run build --report

https://webpack.js.org/concepts/manifest/ survivejs.com

VueLoaderPlugin追加

  • build/webpack.base.conf.js
 const config = require('../config')
+const VueLoaderPlugin = require('vue-loader/lib/plugin')
 const vueLoaderConfig = require('./vue-loader.conf')
...
+  plugins: [
+    new VueLoaderPlugin()
+  ],
   module: {

postcss.config.js追加

対象ブラウザの設定は各自行う。

  • postcss.config.js
+module.exports = {
+  plugins: {
+    autoprefixer: {},
+  },
+};

雑ですがこんなイメージ。 あ、babelも上げないと。