Webpack-chain 从入门到深入

12,731 阅读6分钟

一、前言

webpack 的核心配置的创建和修改基于一个有潜在难于处理的 JavaScript 对象。虽然这对于配置单个项目来说是没有什么问题的,但当你团队中有比较多项目,并且尝试所有项目共享 webpack 配置文件时,你会觉难以入手,因为你需要考虑构建配置的可扩展性,比如某个子项目有自己独有的特征,需要进行一些个性化配置时,便会变得棘手。

webpack-chain 尝试通过提供可链式或顺流式的 API 创建和修改webpack 配置,API 的 Key 部分可以由用户指定的名称引用,这有助于跨项目修改配置方式的标准化。在 vue-cli3 以及一些开源的构建器中陆续采用了 webpack-chain 这种方式,所以本文我们会从入门到熟练上手,帮助大家熟悉 webpack-chain 的编写使用。

二、语法介绍

1、webpack 实例创建

我们可以使用 npm 或者 yarn 的方式安装 webpack-chain 包,如下所示

npm i --save-dev webpack-chain
or
yarn add --dev webpack-chain

当你安装了webpack-chain, 你就可以开始创建一个 webpack 实例,如下所示

// 导入 webpack-chain 模块,该模块导出了一个用于创建一个 webpack 配置 API 的单一构造函数。
const Config = require('webpack-chain');

// 对该单一构造函数创建一个新的配置实例
const config = new Config();

// ... 中间一系列 webpack 的配置,我们在后续的章节再陆续说明,这里暂且省略

// 导出这个修改完成的要被 webpack 使用的配置对象
module.exports = config.toConfig();

2、ChainedMap

webpack-chain 中的核心 API 接口之一是 ChainedMap. 一个 ChainedMap 的操作类似于 JavaScript Map, 为链式和生成配置提供了一些便利, 如果一个属性被标记一个 ChainedMap, 则它将具有如下的 API 和方法: 除非另有说明,否则这些方法将返回 ChainedMap, 允许链式调用这些方法。

// 1、从 Map 移除所有 配置
clear()

// 2、通过键值从 Map 移除单个配置
delete(key)

// 3、获取 Map 中相应键的值
// 注意:返回值是该 key 对应的值
get(key)

// 4、获取 Map 中相应键的值
// 如果键在 Map 中不存在,则 ChainedMap 中该键的值会被配置为 fn 的返回值.
// 注意:返回值是该 key 对应的值,或者 fn 返回的值
getOrCompute(key, fn)

// 5、配置 Map 中 已存在的键的值
set(key, value)

// 6、Map 中是否存在一个配置值的特定键,
// 注意:返回 boolean
has(key)

// 7、返回 Map 中已存储的所有值的数组
// 注意:返回 Array
values()

// 8、返回 Map 中全部配置的一个对象, 其中 键是这个对象属性,值是相应键的值,
entries()

// 9、 提供一个对象,这个对象的属性和值将 映射进 Map
merge(obj, omit)

// 10、对当前配置上下文执行函数 handler
batch(handler)

// 11、条件执行一个函数去继续配置
// condition: Boolean
// whenTruthy: 当条件为真,调用把 ChainedMap 实例作为单一参数传入的函数
// whenFalsy: 当条件为假,调用把 ChainedMap 实例作为单一参数传入的函数
when(condition, whenTruthy, whenFalsy)

3、ChainedSet

webpack-chain 中的核心 API 接口另一个是 ChainedSet,其操作类似于JavaScript Set, 为链式和生成配置提供了一些便利。 如果一个属性被标记一个 ChainedSet,则它将具有如下的 API 和方法: 除非另有说明,否则这些方法将返回 ChainedSet,允许链式调用这些方法。

// 1、添加/追加给 Set 末尾位置一个值
add(value)

// 2、添加给 Set 开始位置一个值
prepend(value)

// 3、移除Set中全部值
clear()

// 4、移除Set中一个指定的值
delete(value)

// 5、检测 Set 中是否存在一个值
// 注意:返回 boolean
has(value)

// 6、返回 Set 中值的数组.
// 注意:返回 Array
values()

// 7、连接给定的数组到 Set 尾部。
merge(arr)

// 8、对当前配置上下文执行函数 handler
batch(handler)

// 8、条件执行一个函数去继续配置
// whenTruthy: 当条件为真,调用把 ChainedSet 实例作为单一参数传入的函数
// whenFalsy: 当条件为假,调用把 ChainedSet 实例作为单一参数传入的函数
when(condition, whenTruthy, whenFalsy)

4、方法简写

除了以上提到的使用 ChainedMap 和 ChainedSet 语法编写实现功能外,webpack-chain 还提供了许多简写方法,我们在这里不在一一列出,读者可以去 webpack-chain github 官方文档 查阅。例如,devServer.hot 就是是一个简写方法,写法如下所示

// devServer 的简写方法如下
devServer.hot(true);

// 上述方法等效于
devServer.set('hot', true);

跟 ChainedMap 和 ChainedSet 语法一样,简写方法在没有特别说明的情况,返回的也是原实例,因此简写方法也是支持链式语法的。

5、合并配置

webpack-chain 支持将对象合并到配置实例,但是要注意,这不是 webpack 配置对象,如果我们需要合并 webpack-chain 对象,需要在合并前对其进行转换。

// 合并
config.merge({ devtool: 'source-map' });
// 获取 "source-map"
config.get('devtool')

6、检查生成的配置

我们可以使用语法 config.toString() 方法将 webpack 对象转换成字符串,转换后的字符串包含命名规则、用法和插件的注释提示,如下所示

config
  .module
    .rule('compile')
      .test(/\.js$/)
      .use('babel')
        .loader('babel-loader');

config.toString();

// 转换后的输出
{
  module: {
    rules: [
      /* config.module.rule('compile') */
      {
        test: /\.js$/,
        use: [
          /* config.module.rule('compile').use('babel') */
          {
            loader: 'babel-loader'
          }
        ]
      }
    ]
  }
}

三、常用实例编写

1、entry 入口配置

// 配置编译入口文件
config.entry('main').add('./src/main.js') 

// 等同于以下 webpack 配置
entry: {
  main: [
    './src/main.js'
  ]
}

2、output 出口配置

// 配置出口文件
config.output
  .path(path.resolve(__dirname, './dist'))
  .filename('[name].[chunkhash].js')
  .chunkFilename('chunks/[name].[chunkhash].js')
  .libraryTarget('umd');

// 等同于以下 webpack 配置
output: {
  path: path.resolve(__dirname, './dist'),
  filename: '[name].[chunkhash].js',
  chunkFilename: 'chunks/[name].[chunkhash].js',
  libraryTarget: 'umd'
},

3、alias 别名配置

// 配置目录别名
config.resolve.alias
  .set('@', path.resolve(__dirname, 'src'))
  .set('assets', path.resolve(__dirname, 'src/assets'))

// 等同于以下 webpack 配置
resolve: {
  alias: {
    '@': path.resolve(__dirname, 'src'),
     assets: path.resolve(__dirname, 'src/assets'))
  }
},

4、loader 配置新增

// 配置一个新的 loader
config.module
.rule('babel')
.test(/\.(js|jsx|mjs|ts|tsx)$/)
.include
  .add(path.resolve(__dirname,  'src'))
  .end()
.use('babel-loader')
  .loader('babel-loader')
  .options({
    'presets':['@babel/preset-env']
  })

// 等同于以下 webpack 配置
module: {
  rules: [
    {
      test: /\.(js|jsx|mjs|ts|tsx)$/,
      include: [
        path.resolve(__dirname,  'src')
      ],
      use: [
        {
          loader: 'babel-loader',
          options: {
              presets: [
                '@babel/preset-env'
              ]
            }
        }
      ]
    }
  ]
}

5、loader 配置修改

跟新增 loader 不同的是,使用了 tap 方法,该方法的回调参数为 options 即该 loader 的配置选项对象,从而我们可以通过更改 options 对象,从而去更改 loader 配置。

config.module
.rule('babel')
.use('babel-loader')
  .tap(options => {
    // 修改它的选项...
    options.include = path.resolve(__dirname,  'test')
    return options
  })

6、loader 配置移除

config.module.rules.clear(); // 添加的 loader 都删掉.

config.module.rule('babel').uses.clear();  删除指定 rule 用 use 添加的

7、plugin 配置新增

// 配置一个新的 plugin
config.plugin('HtmlWebpackPlugin').use(HtmlWebpackPlugin, [
  {
    template: path.resolve(__dirname, './src/index.html'),
    minify: {
      collapseWhitespace: true,
      minifyJS: true,
      minifyCSS: true,
      removeComments: true,
      removeEmptyAttributes: true,
      removeRedundantAttributes: true,
      useShortDoctype: true
    } 
  }
]);

// 等同于以下 webpack 配置
  plugins: [
    new HtmlWebpackPlugin(
      {
        template: path.resolve(__dirname, './src/index.html'),
        minify: {
          collapseWhitespace: true,
          minifyJS: true,
          minifyCSS: true,
          removeComments: true,
          removeEmptyAttributes: true,
          removeRedundantAttributes: true,
          useShortDoctype: true
        }
      }
    )
  ],

8、plugin 配置修改

跟新增 loader/plugin 不同的是,使用了 tap 方法,且保留了之前配置的选项,更改的选项被覆盖。

// 修改插件 HtmlWebpackPlugin
config.plugin('HtmlWebpackPlugin').tap((args) => [
  {
    ...(args[0] || {}),
    template: path.resolve(__dirname, './main.html'),
  }
]);

9、使用 when 条件进行配置

// 1、示例:仅在生产期间添加minify插件
config
  .when(process.env.NODE_ENV === 'production', config => {
    config
      .plugin('minify')
      .use(BabiliWebpackPlugin);
  });

// 2、示例:只有在生产过程中添加缩小插件,否则设置 devtool 到源映射
config
  .when(process.env.NODE_ENV === 'production',
    config => config.plugin('minify').use(BabiliWebpackPlugin),
    config => config.devtool('source-map')
  );

10、插件移除配置

config.plugins.delete('HtmlWebpackPlugin');

四、总结

本文我们会从入门到熟练上手,通过介绍 webpack-chain 的语法到手动编写 webpack 的常见配置和操作,帮助大家熟悉 webpack-chain 的编写使用,希望对你有帮助。

辛苦整理良久,还望手动点赞鼓励~ 博客 github地址为:github.com/fengshi123/… ,汇总了作者的所有博客,欢迎关注及 star ~