Published on

Webpack 4 升級隨記

Authors
  • avatar
    Name
    米K朗基羅
    Twitter
    @kvzl_

Webpack 4 推出已經有幾個月的時間,標榜了更好的性能、零配置(更多預設值)等等各種提升,只是一直以來的配置也還算堪用,所以看到 Webpack 4 風光上市,第一時間並沒有很想跟進哈哈。

不過最近考慮到本人即將入伍,想儘早把負責的專案做一下清理,就順手把 Webpack 給升級了,殊不知退伍後已經變 Webpack 5 了(別

揪竟是改了什麼

根據官方文章,大致上是這幾點:

  • 性能提升:而且還有進步空間!
  • 零配置:應該說更多預設值了,而且會根據 Mode 有所不同。雖然專案複雜到不能不寫配置,但可以省掉一些選項也是不錯啦。
  • 支援 WebAssembly:這是未來一大趨勢,而且收了錢當然要好好做事

是說網路上已經有很多 hen 棒的文章啦,所以這裡只會紀錄我有用到的部分,不會涵蓋所有 Webpack 4 升級內容,有興趣知道更多的同學可以參考文末的參考文獻 :innocent:

起手式

一切先上再說:

yarn add -D webpack@4 webpack-cli

接著來看看我改了哪些東西:

mode

原本覺得是個有點不起眼的改進,甚至都有點懶得寫了,但仔細想想其實頗重要的啊!

在 Webpack 4 以前,都是用 NODE_ENV 來控制不同環境要啟用哪些選項跟 Plugin,但現在 Webpack 會自動根據 mode 這個選項直接幫你套好各種優化,省了很多事啊~

我主要是透過 CLI 來指定 mode

# before
NODE_ENV=production webpack

# after
webpack --mode production

如果還是有需要在配置裡面取得 mode,可以改用 function 傳回設定,像這樣:

module.exports = (env, argv) => {
  const debug = argv.mode === 'development'
  return {
    // 你的配置
  }
}

ModuleConcatenationPlugin

Scope Hoisting 是自 Webpack 3 開始提供的一項功能,可以有效減少 production 打包後的體積。以前可以透過內建的 ModuleConcatenationPlugin 來達成,現在直接變成選項 optimization.concatenateModules 了,而且預設 production 模式時自動打開,所以連設定都免了 ヾ(*´∀ ˋ*)ノ

// before
{
  plugins: [new webpack.optimize.ModuleConcatenationPlugin()]
}

// after
{
  optimization: {
    // concatenateModules: true,    // production mode 會自己打開
  }
}

UglifyJsPlugin

只要把 optimization.minimize 打開,Webpack 就會幫你用 UglifyJs 處理了,一樣 production 模式自動打開,可以省略的。

如果想針對 UglifyJs 做設定,可以透過 optimization.minimizer

// before
{
  plugins: [
    new webpack.optimize.UglifyJsPlugin({
      /* 各種設定 */
    }),
  ]
}

// after
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')

{
  optimization: {
    // minimize: true,    // production mode 會自己打開
    minimizer: [
      new UglifyJsPlugin({
        /* 各種設定 */
      }),
    ]
  }
}

DefinePlugin

取而代之的做法是透過 optimization.nodeEnv 來指定 process.env.NODE_ENV,不過預設已經是參照 mode 值了,所以可以省略。

// before
{
  plugins: [
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: debug ? '"development"' : '"production"',
      },
    }),
  ]
}

// after
{
  optimization: {
    // nodeEnv: debug ? 'development' : 'production',   // 預設參照 mode,可省略
  }
}

NoEmitOnErrorsPlugin

主要是用來阻止轉譯失敗的程式碼被打包進去,不過現在也變成選項了:

// before
{
  plugins: [
    new webpack.optimize.NoEmitOnErrorsPlugin(),
  ]
}

// after
{
  optimization: {
    noEmitOnErrors: true,
  }
}

CommonsChunkPlugin

雖然好像是為了更好的支援 Code Splitting 才棄用 CommonsChunkPlugin,不過我最後還是採用和原本類似的拆分方式,基本上只是換個寫法而已:

// before
{
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      filename: 'vendor.js',
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'commons',
      filename: 'commons.js',
    }),
  ]
}

// after
{
  optimization: {
    runtimeChunk: 'single',
    splitChunks: {
      name: 'commons',
      chunks: 'all',

      cacheGroups: {
        vendor: {
          test: /node_modules/,
          name: 'vendor',
          chunks: 'all',
          enforce: true
        }
      }
    },
  }
}

這裡要提一下我現在在弄的其實不是 SPA,而是有多個 Entry 的多頁面網站,這也是為什麼我會寫上 optimization.runtimeChunk: 'single'

一開始不知道各個 Entry 會有各自的 Runtime,導致不同 Entry 共用的模組實際上被初始化超過一次,花了不少時間才發現 Webpack 文件有提到這件事 QQ。(以前好像是會被歸納到 Common chunks 裡面)

加上這個選項可以讓 Runtime 的部分獨立成一個 Entry,以避免各個 Entry 有各自的 Runtime。

vue-loader

因為專案本身有用到 Vue.js,搞定 Webpack 後發現 vue-loader 好像有點問題,不過幫 vue-loader 升級就沒事了:

yarn add -D vue-loader@latest

然後 Webpack 的部分需要額外引入 Plugin:

const VueLoaderPlugin = require('vue-loader/lib/plugin')

{
  plugins: [
    new VueLoaderPlugin(), // <-- 記得加這個
  ]
}

其他重大變更可以看看 vue-loader官方文件

一些感想

對我來說這次改進主要在區分 production 和 development,也就是根據環境做優化的這件事變得更容易了。一直以來 Webpack 被大家詬病的就是他的配置地獄,對初學者來說真的有夠不友善(甚至根本就是惡意吧 XD),能夠有點改善真是太棒了~

當然支援 WebAssembly 也是很重要的一部分,只是這件事對我這個小小前端好像沒有太大影響 (|||゚ д ゚),不過這東西一直都在我的學習清單裡面,之後有機會再來詳述。

以上,大 guy 4 John。

參考資料