美文网首页
读 VuePress(二):使用 Webpack-chain 链

读 VuePress(二):使用 Webpack-chain 链

作者: 云峰yf | 来源:发表于2018-09-06 00:25 被阅读0次

    前言

    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。

    相关文章

      网友评论

          本文标题:读 VuePress(二):使用 Webpack-chain 链

          本文链接:https://www.haomeiwen.com/subject/wkvdbftx.html