讀 VuePress(二):使用 Webpack-chain 鏈式生成 webpack 配置

雲峰yf發表於2018-09-05

前言

vuepress 有三套 webpack 配置:基礎配置、dev 配置、build 配置,看似和普通的一個前端專案也沒什麼差別,但它使用 webpack-chain 生成配置而不是傳統的寫死配置。

相關原始碼見 createBaseConfig.jscreateClientConfigcreateServerConfig

webpack-chain 簡介

鏈式包裝器

引入 webpack-chain 後,我們所有的 webpack 配置通過一個鏈式包裝器便可生成了:

const Config = require('webpack-chain');
const config = new Config();
// 鏈式生成配置
...
// 匯出 webpack 配置物件
export default config.toConfig();
複製程式碼

在引入詳細的示例之前,先讓我們介紹一下 webpack-chain 中內建的兩種資料結構:ChainMap、ChainSet。

ChainedSet

帶鏈式方法的集合。

很顯然,它和 ES6 的 Set 類似,都擁有鍵值對,但值得一提的是:它通過鏈式方法來操作。

在 webpack-chain 中,屬於 ChainedSet 的有 config.entry(name)config.resolve.modules 等。

假如我們需要指定 webpack 配置的 enrty,我們只需要這樣做:

config
  .entry('app')
    .add('src/index.js')
複製程式碼

它等價於 webpack 配置物件的這部分:

entry: {
  app: './src/index.js'
}
複製程式碼

當然,我想強調的 ChainedSet 真正強大的地方,在於 ChainedSet 提供的內建方法:add(value)、delete(value)、has(value) 等。

這可以幫助我們增刪改查整個 webpack 配置中的任意一個部分。

ChainedMap

帶鏈式方法的雜湊表。

同上,它和 ES6 的 Map 類似,也通過鏈式方法來操作。

在 webpack-chain 中,屬於 ChainedMap 的有 configconfig.resolve 等。

想了解更多 API 用法的讀者可以前往文件

webpack-chain 原理簡介

我們開啟原始碼目錄:

webpack-chain 原始碼目錄

一共有三種類:Chainable、ChainedSet 或 ChainedMap、其它。

鏈式呼叫

Chainable 實現了鏈式呼叫的功能,它的程式碼很簡潔:

module.exports = class {
  constructor(parent) {
    this.parent = parent;
  }

  batch(handler) {
    handler(this);
    return this;
  }

  end() {
    return this.parent;
  }
};
複製程式碼

最常呼叫的 end 方法便是來源於這了,它會返回撥用鏈中最前端的那個物件。

比如說,我們在 vuepress 中有這樣一段程式碼:

config
    .use('cache-loader')
    .loader('cache-loader')
    .options({
      cacheDirectory,
      cacheIdentifier
    })
    .end()
    .use('babel-loader')
      .loader('babel-loader')
      .options({
        // do not pick local project babel config
        babelrc: false,
        presets: [
          require.resolve('@vue/babel-preset-app')
        ]
      })
複製程式碼

第八行結尾 end() 處返回的便又是 config 了。

ChainedSet 和 ChainedMap 都繼承於 Chainable,其他類大多都繼承於 ChainedSet 或 ChainedMap,除了 Use 和 Plugin 類使用 Orderable 這個高階函式包裝了一下(相當於裝飾器),目的在於解決在使用 module.use 或 plugin 時調整順序的問題。有興趣的讀者可以自行翻閱原始碼~

在 Vuepress 中的應用

分成三個配置我們就不贅述了,畢竟大家平常開發的專案中也可能這樣做。在這裡我需要特別提一下的地方便是編寫函式生成 webpack 配置

舉個例子,在 createBaseConfig 裡,有一個這樣的函式:

function createCSSRule (lang, test, loader, options) {
  const baseRule = config.module.rule(lang).test(test)
  const modulesRule = baseRule.oneOf('modules').resourceQuery(/module/)
  const normalRule = baseRule.oneOf('normal')

  applyLoaders(modulesRule, true)
  applyLoaders(normalRule, false)

  function applyLoaders (rule, modules) {
    if (!isServer) {
      if (isProd) {
        rule.use('extract-css-loader').loader(CSSExtractPlugin.loader)
      } else {
        rule.use('vue-style-loader').loader('vue-style-loader')
      }
    }

    rule.use('css-loader')
      .loader(isServer ? 'css-loader/locals' : 'css-loader')
      .options({
        modules,
        localIdentName: `[local]_[hash:base64:8]`,
        importLoaders: 1,
        sourceMap: !isProd
      })

    rule.use('postcss-loader').loader('postcss-loader').options(Object.assign({
      plugins: [require('autoprefixer')],
      sourceMap: !isProd
    }, siteConfig.postcss))

    if (loader) {
      rule.use(loader).loader(loader).options(options)
    }
  }
}
複製程式碼

它做了這樣一件事:對特定的一種樣式語言進行 css 模組化和非模組化的處理,順序是 loader -> postcss-loader -> css-loader -> vue-style-loader 或 extract-css-loader。 使用方式是這樣的:

createCSSRule('css', /\.css$/)
createCSSRule('postcss', /\.p(ost)?css$/)
createCSSRule('scss', /\.scss$/, 'sass-loader', siteConfig.scss)
createCSSRule('sass', /\.sass$/, 'sass-loader', Object.assign({ indentedSyntax: true }, siteConfig.sass))
createCSSRule('less', /\.less$/, 'less-loader', siteConfig.less)
createCSSRule('stylus', /\.styl(us)?$/, 'stylus-loader', Object.assign({
  preferPathResolver: 'webpack'
}, siteConfig.stylus))
複製程式碼

是不是一下減少了配置的編寫量?而且還很靈活的支援使用者自定義 options 和後期的程式碼變更。

結語

什麼時候應該使用 webpack-chain 呢?畢竟它的引入增加了專案的成本,我的答案是:

  1. 當專案的 webpack 配置需要根據某些邏輯生成的時候,推薦引入 webpack-chain 對 webpack 配置進行宣告式的編寫。
  2. 如果 webpack 配置很簡單或者直接寫死一個物件就行,不推薦引入 webpack-chain,如果有多個配置需要合併的需求,可以引入 webpack-merge。

相關文章