preface

Vuepress has three webPack configurations: base, Dev, and Build. It looks like a normal front-end project, but it uses webpack-chain to generate configurations instead of the traditional write-dead configuration.

CreateBaseConfig. Js createClientConfig createServerConfig

Webpack – chain profile

Chain packager

With the introduction of Webpack-chain, all of our WebPack configurations are generated from a single chain wrapper:

const Config = require('webpack-chain'); const config = new Config(); // Chain build configuration... // Export the WebPack configuration objectexport default config.toConfig();
Copy the code

Before introducing detailed examples, let’s take a look at the two data structures built into Webpack-chain: ChainMap and ChainSet.

ChainedSet

A collection of chained methods.

Obviously, it has key-value pairs like ES6’s Set, but it’s worth mentioning that it operates via chained methods.

In webpack-chain, config.entry(name), config.resolve. Modules, and so on belong to ChainedSet.

If we need to specify enrty for the WebPack configuration, we just need to do this:

config
  .entry('app')
    .add('src/index.js')
Copy the code

It is equivalent to this part of the WebPack configuration object:

entry: {
  app: './src/index.js'
}
Copy the code

Of course, the real power I want to highlight about ChainedSet is the built-in methods that ChainedSet provides: Add (value), delete(value), has(value), and so on.

This allows us to add, delete, or change any part of the entire WebPack configuration.

ChainedMap

Hash table with chained method.

As above, it is similar to ES6 Map in that it also operates via the chained method.

In webpack-chain, config, config.resolve, and so on belong to ChainedMap.

Readers who want to learn more about how to use the API can go to the documentation.

Overview of webpack-chain

We open the source directory:

There are three categories: Chainable, ChainedSet or ChainedMap, and others.

Chain calls

Chainable implements the function of chain call. The code is very simple:

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

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

  end() {
    returnthis.parent; }};Copy the code

This is where the most commonly called end method comes from, which returns the object at the top of the call chain.

For example, we have this code in 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')]})Copy the code

The end() of line 8 returns config again.

Both ChainedSet and ChainedMap inherit from Chainable, and most other classes inherit from ChainedSet or ChainedMap, In addition to the Use and Plugin classes, the Use and Plugin classes are wrapped (as decorators) in Orderable, a higher-order function designed to solve the problem of adjusting the order when using module. Use or Plugin. Interested readers can browse the source ~

Application in Vuepress

We won’t go into the three configurations, as you might do in your normal development projects. One area I need to mention specifically here is writing functions to generate the WebPack configuration:

For example, in createBaseConfig, there is a function like this:

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)
    }
  }
}
Copy the code

It does the following: For a specific style language, CSS modular and non-modular processing is performed in the order loader -> postCSs-loader -> CSS-loader -> vUE style-loader or extract-CSS-loader. It works like this:

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))
Copy the code

Does it reduce the amount of configuration writing? It is also flexible to support user – defined options and later code changes.

conclusion

When should you use Webpack-chain? After all, its introduction increases the cost of the project, and my answer is:

  1. When the webpack configuration of a project needs to be generated according to some logic, it is recommended to introduce webpack-chain to declaratively write the Webpack configuration.
  2. Webpack-chain is not recommended if the Webpack configuration is simple or an object is written to death. If multiple configurations need to be merged, you can introduce webpack-merge.