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

前言

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。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,002评论 6 509
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,777评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,341评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,085评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,110评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,868评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,528评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,422评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,938评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,067评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,199评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,877评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,540评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,079评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,192评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,514评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,190评论 2 357

推荐阅读更多精彩内容