webpack-chain GitHub 中文文档


rules

新增rule

// e.g.

config.module
  .rule(name)
    .use(name)
      .loader(loader)
      .options(options)
module.exports = {
    chainWebpack: (config) => {
        // 通过 style-resources-loader 来添加less全局变量
        const types = ['vue-modules', 'vue', 'normal-modules', 'normal'];
        types.forEach(type => {
            let rule = config.module.rule('less').oneOf(type)
            rule.use('style-resource')
                .loader('style-resources-loader')
                .options({
                    patterns: [path.resolve(__dirname, './lessVariates.less')]
                });
        });

        const oneOfsMap = config.module.rule("less").oneOfs.store;
        oneOfsMap.forEach(item => {
          item
          .use("style-resources-loader")
           .loader("style-resources-loader")
           .options({
            	patterns: [ './public/themes/default/commom/commonFunc.less', './public/themes/global.less' ]
          })
           .end()
        })

        // `svg-sprite-loader`: 将svg图片以雪碧图的方式在项目中加载
        config.module
            .rule('svg')
            .test(/.svg$/) // 匹配svg文件
            .include.add(resolve('src/svg')) // 主要匹配src/svg
            .end() 
            .use('svg-sprite-loader')
            .loader('svg-sprite-loader') // 使用的loader,主要要npm该插件
            .options({symbolId: 'svg-[name]'}) // 参数配置
    }
}
// loader 默认是从下往上处理

// enforce: 决定现有规则调用顺序
// - pre 优先处理
// - normal 正常处理(默认)
// - inline 其次处理
// - post 最后处理
module.exports = {
    chainWebpack: config => {
        config.module
            .rule('lint') // 定义一个名叫 lint 的规则
                .test(/\.js$/) // 设置 lint 的匹配正则
                .pre()  // 指定当前规则的调用优先级
                .include // 设置当前规则的作用目录,只在当前目录下才执行当前规则
                    .add('src')
                    .end()
                .use('eslint') // 指定一个名叫 eslint 的 loader 配置
                    .loader('eslint-loader') // 该配置使用 eslint-loader 作为处理 loader
                    .options({ // 该 eslint-loader 的配置
                        rules: {
                            semi: 'off'
                        }
                    })
                    .end()
                .use('zidingyi') // 指定一个名叫 zidingyi 的 loader 配置
                    .loader('zidingyi-loader') // 该配置使用 zidingyi-loader 作为处理 loader
                    .options({ // 该 zidingyi-loader 的配置
                        rules: {
                            semi: 'off'
                        }
                    })
      
        config.module
            .rule('compile')
                .test(/\.js$/)
                .include
                    .add('src')
                    .add('test')
                    .end()
                .use('babel')
                    .loader('babel-loader')
                    .options({
                        presets: [
                            ['@babel/preset-env', { modules: false }]
                        ]
                    })
    }
}
// 最后将解析为如下配置:
{
    module: {
        rules: [
            /* config.module.rule('lint') */
            {
                test: /\.js$/,
                enforce: 'pre',
                include: ['src'],
                use: [
                    /* config.module.rule('lint').use('eslint') */
                    {
                        loader: 'eslint-loader',
                        options: {
                            rules: {
                                semi: 'off'
                            }
                        }
                    },
                    /* config.module.rule('lint').use('zidingyi') */
                    {
                        loader: 'zidingyi-loader',
                        options: {
                            rules: {
                                semi: 'off'
                            }
                        }
                    }
                ]
            },
            /* config.module.rule('compile') */
            {
                test: /\.js$/,
                include: ['src', 'test'],
                use: [
                    /* config.module.rule('compile').use('babel') */
                    {
                        loader: 'babel-loader',
                        options: {
                            presets: [
                                ['@babel/preset-env', { modules: false }]
                            ]
                        }
                    }
                ]
            }
        ]
    }
}

修改rule

config.module
  .rule(name)
    .use(name)
      .tap(options => newOptions)
module.exports = {
    chainWebpack: (config) => {
        // `url-loader`是webpack默认已经配置的,现在我们来修改它的参数
        config.module.rule('images')
            .use('url-loader')
            .tap(options => ({
                name: './assets/images/[name].[ext]',
                quality: 85,
                limit: 0,
                esModule: false,
            }))
    }
}

删除单个规则 rule

const moduleRule = config.module
// 删除命名为  js 的规则
moduleRule.rules.delete('js')

plugins

新增plugin

config
  .plugin(name)
  .use(WebpackPlugin, args)
const HotHashWebpackPlugin = require('hot-hash-webpack-plugin');
module.exports = {
    chainWebpack: (config) => {
        // 新增一个`hot-hash-webpack-plugin`
        // 注意:这里use的时候不需要使用`new HotHashWebpackPlugin()`
        config.plugin('hotHash')
              .use(HotHashWebpackPlugin, [{ version: '1.0.0' }]);
    }
}
module.exports = {
    chainWebpack: config => {
        // 用法:
        // config
        //     .plugin(name)
        //         .use(WebpackPlugin, args)
        
        // 示例:
        config
            .plugin('clean') // 创建一个名称为 clean 的插件
                .use(CleanPlugin, [['dist'], { root: '/dir' }]) // 不要用 new 去创建插件,因为已经为你做好了
    }
}

修改plugins参数

config
    .plugin(name)
    .tap(args => newArgs)
const HotHashWebpackPlugin = require('hot-hash-webpack-plugin');
module.exports = {
    chainWebpack: (config) => {
        // 修改打包时css抽离后的filename及抽离所属目录
        config.plugin('extract-css')
                .tap(args => [{
                    filename: 'css/[name].[contenthash:8].css',
                    chunkFilename: 'css/[name].[contenthash:8].css'
                }]);
        
        // 正式环境下,删除console和debugger
         config.optimization.minimizer('terser').tap((args) => {
            // 注释console.*
            args[0].terserOptions.compress.drop_console = true
            // remove debugger
            args[0].terserOptions.compress.drop_debugger = true
            // 移除 console.log
            args[0].terserOptions.compress.pure_funcs = ['console.log']
            // 去掉注释 如果需要看chunk-vendors公共部分插件,可以注释掉就可以看到注释了
            args[0].terserOptions.output = {
              comments: false
            };
            return args
          })
    }
}
// 用法:
config.plugin(name)
  .tap(args => newArgs)

// 示例:
config.plugin('env')
  .tap(args => [...args, 'SECRET_KEY'])

// 修改前:
/* config.plugin('optimize-css') */
new OptimizeCssnanoPlugin(
    {
        sourceMap: false,
        cssnanoOptions: {
            preset: [
                'default',
                {
                    mergeLonghand: false,
                    cssDeclarationSorter: false
                }
            ]
        },
    }
)

// 修改后:
config.plugin('optimize-css')
  .tap(args => {
      args[0].yy = 1
      return args
  })
/* config.plugin('optimize-css') */
new OptimizeCssnanoPlugin(
    {
        sourceMap: false,
        cssnanoOptions: {
            preset: [
                'default',
                {
                    mergeLonghand: false,
                    cssDeclarationSorter: false
                }
            ]
        },
        yy: 2
    }
)

config.plugin('optimize-css')
  .tap(args => {
      return [...args, { x: 1 }]
  })
/* config.plugin('optimize-css') */
new OptimizeCssnanoPlugin(
    {
        sourceMap: false,
        cssnanoOptions: {
            preset: [
                'default',
                {
                    mergeLonghand: false,
                    cssDeclarationSorter: false
                }
            ]
        }
    },
    {
        x: 1
    }
)

修改plugin实例

config.plugin(name)
  .init((Plugin, args) => new Plugin(...args))

删除plugin

config.plugins.delete(name)
module.exports = {
    chainWebpack: (config) => {
        // vue-cli3.X 会自动进行模块分割抽离,如果不需要进行分割,可以手动删除
        config.optimization.delete('splitChunks'); 
        
    }
}

修改plugin调用顺序

指定当前插件上下文应该在另一个指定插件之前/之后执行,你不能在同一个插件上同时使用 .before() 和 .after()

// 用法:
// 在 otherName 之前调用
config
  .plugin(name)
    .before(otherName)

// 在 otherName 之后调用
config
  .plugin(name)
    .after(otherName)

// 示例:
// 修改之前:
[
    /* config.plugin('named-chunks') */
    new NamedChunksPlugin(
        function () { /* omitted long function */ }
    ),
    /* config.plugin('copy') */
    new CopyWebpackPlugin(
        []
    )
]

// 修改后:
config.plugin('named-chunks')
  .after('copy')
[
    /* config.plugin('copy') */
    new CopyWebpackPlugin(
        []
    ),
    /* config.plugin('named-chunks') */
    new NamedChunksPlugin(
        function () { /* omitted long function */ }
    )
]

config.plugin('copy')
  .before('named-chunks')
[
    /* config.plugin('copy') */
    new CopyWebpackPlugin(
        []
    ),
    /* config.plugin('named-chunks') */
    new NamedChunksPlugin(
        function () { /* omitted long function */ }
    )
]

loader

config.module
  .rule('resolveNodeModules')
  .test(/\.m?jsx?$/)
  .include.add(/node_modules\/(vue2-editor|quill|quill-delta)\/.*/)
  .end()
  .use('babel-loader')
  .loader('babel-loader')

删除单个规则中的一个loader

// 删除前:
{
    test: /\.m?jsx?$/,
    exclude: [
        function () { /* omitted long function */ }
    ],
    use: [
        /* config.module.rule('js').use('cache-loader') */
        {
            loader: 'cache-loader',
            options: {
                cacheDirectory: 'D:\\webproject\\webapp-jy\\node_modules\\.cache\\babel-loader',
                cacheIdentifier: '519fc596'
            }
        },
        /* config.module.rule('js').use('babel-loader') */
        {
            loader: 'babel-loader'
        }
    ]
}


// 删除后:
const jsRule = config.module.rule('js')
// 删除 cache-loader
jsRule.uses.delete('cache-loader')
{
    test: /\.m?jsx?$/,
    exclude: [
        function () { /* omitted long function */ }
    ],
    use: [
        /* config.module.rule('js').use('babel-loader') */
        {
            loader: 'babel-loader'
        }
    ]
}

删除单个规则中的全部 loader

module.exports = {
  chainWebpack: config => {
    const svgRule = config.module.rule('svg')
    // 清楚已有的所有loader。
    // 如果你不这样做,接下来的loader会附加在该规则现有的 loader 之后。
    svgRule.uses.clear()
    // 添加要替换的 loader 
    svgRule
      .use('vue-svg-loader')
        .loader('vue-svg-loader')
  }
}

修改单个 loader 的配置

// 两种写法都可修改
config.module
  .rule('compile')
    .use('babel')
      .tap(options => merge(options, {
        plugins: ['@babel/plugin-proposal-class-properties']
      }))

config.module
  .rule('compile')
    .use('babel')
    .loader('babel-loader')
      .tap(options => merge(options, {
        plugins: ['@babel/plugin-proposal-class-properties']
      }))

修改多个loader的配置

const types = ['vue-modules', 'vue', 'normal-modules', 'normal'];
types.forEach(type => {
  let rule = config.module.rule('less').oneOf(type)
  rule.uses.delete('style-resources-loader')
  rule.use('happypack').loader('happypack/loader?id=styles').options({
    patterns: [ './public/themes/default/commom/commonFunc.less', './public/themes/global.less' ]
  });
});

const oneOfsMap = config.module.rule("less").oneOfs.store;
    oneOfsMap.forEach(item => {
      item
      .use("style-resources-loader")
       .loader("style-resources-loader")
       .options({
          patterns: [ './public/themes/default/commom/commonFunc.less', './public/themes/global.less' ]
      })
       .end()
    })

在多个 loader 中插入或者删除

config.module.rule('js').toConfig():
// {
//   test: /\.m?jsx?$/,
//   exclude: [ [Function] ],
//   use: [
//     { loader: 'cache-loader', options: [Object] },
//     { loader: 'babel-loader' }
//   ]
// }

// 这里以添加一个新的 loader 为例
// 1、拿到原来的 loader 
let originUse = config.module.rule('js').toConfig().use
// 2、添加 loader
let newLoader = { loader: 'eslint-loader' }
originUse.splice(1, 0, newLoader)
// 3、清空原来的 loader
config.module.rule('js').uses.clear()
// 4、重新设置新的 loader
config.module.rule('js').merge({ use: originUse})

// {
//   test: /\.m?jsx?$/,
//   exclude: [ [Function] ],
//   use: [
//     { loader: 'cache-loader', options: [Object] },
//     { loader: 'eslint-loader' },
//     { loader: 'babel-loader' }
//   ]
// }

optimization

修改 optimization

// 用法(v5版本可用):
config.optimization
  .minimizer(name)
  .tap(args => newArgs)

// 示例:
config.optimization
  .minimizer('css')
  .tap(args => [...args, { cssProcessorOptions: { safe: false } }])

增加 optimization.minimizers

// 用法(v5版本可用):
config.optimization
  .minimizer(name)
  .use(WebpackPlugin, args)

// 示例:
config.optimization
  .minimizer('css')
  .use(OptimizeCSSAssetsPlugin, [{ cssProcessorOptions: { safe: true } }])

查看配置

// 使用 .toConfig() 输出配置

// 示例1:
console.log('js config :>> ', config.module.rule('js').toConfig())
js config :>>  
{
  test: /\.m?jsx?$/,
  exclude: [ [Function] ],
  use: [
    { loader: 'cache-loader', options: [Object] },
    { loader: 'babel-loader' }
  ]
}

// ***************************

// 示例2:
console.log('cache-loader config :>> ', config.module.rule('js').use('cache-loader').toConfig())
cache-loader config :>>  
{
  loader: 'cache-loader',
  options: {
    cacheDirectory: 'D:\\webproject\\myproject\\node_modules\\.cache\\babel-loader',
    cacheIdentifier: '20a97f42'
  }
}

// ***************************

// 示例3:
console.log('plugin config :>> ', config.plugin('optimize-css').toConfig())
plugin config :>>  OptimizeCssnanoPlugin {
  options: { sourceMap: false, cssnanoOptions: { preset: [Array] } }
}


操作方法

chainWebpack 中,有两个大的数据分类:ChainedMap 和 ChainedSet
在操作的时候分别有如下方法可以链式调用:


ChainedMap

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

// 通过键值从 Map 移除单个配置.
// key: *
delete(key)

// 获取 Map 中相应键的值
// key: *
// returns: value
get(key)

// 获取 Map 中相应键的值
// 如果键在Map中不存在,则ChainedMap中该键的值会被配置为fn的返回值.
// key: *
// fn: Function () -> value
// returns: value
getOrCompute(key, fn)

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

// Map中是否存在一个配置值的特定键,返回 真或假
// key: *
// returns: Boolean
has(key)

// 返回 Map中已存储的所有值的数组
// returns: Array
values()

// 返回Map中全部配置的一个对象, 其中 键是这个对象属性,值是相应键的值,
// 如果Map是空,返回 `undefined`
// 使用 `.before() 或 .after()` 的ChainedMap, 则将按照属性名进行排序。
// returns: Object, undefined if empty
entries()

//  提供一个对象,这个对象的属性和值将 映射进 Map。
// 你也可以提供一个数组作为第二个参数以便忽略合并的属性名称。
// obj: Object
// omit: Optional Array
merge(obj, omit)

// 对当前配置上下文执行函数。
// handler: Function -> ChainedMap
  // 一个把ChainedMap实例作为单个参数的函数
batch(handler)

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


ChainedSet

// 添加/追加 给Set末尾位置一个值.
// value: *
add(value)

// 添加 给Set开始位置一个值.
// value: *
prepend(value)

// 移除Set中全部值.
clear()

// 移除Set中一个指定的值.
// value: *
delete(value)

// 检测Set中是否存在一个值.
// value: *
// returns: Boolean
has(value)

// 返回Set中值的数组.
// returns: Array
values()

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

// 对当前配置上下文执行函数。
// handler: Function -> ChainedSet
  // 一个把 ChainedSet 实例作为单个参数的函数
batch(handler)

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



vue.config.js中的应用

module.exports = {
	chainWebpack: (config) => {
     	if (isBuild) {
   			config.plugins.delete('prefetch');

			// 去除控制台打印
			config.optimization.minimizer('terser').tap((args) => {
		        // 注释console.*
		        args[0].terserOptions.compress.drop_console = true
		        // remove debugger
		        args[0].terserOptions.compress.drop_debugger = true
		        // 移除 console.log
		        args[0].terserOptions.compress.pure_funcs = ['console.log']
		        // 去掉注释 如果需要看chunk-vendors公共部分插件,可以注释掉就可以看到注释了
		        args[0].terserOptions.output = {
		          comments: false
		        };
		        return args
      		})

     	  	// 拆包
	      	config.optimization.splitChunks({
		        chunks: "all",
		        minSize: 20000, // 允许新拆出 chunk 的最小体积,也是异步 chunk 公共模块的强制拆分体积
		        cacheGroups: {
		          libs: { // 第三方库
		            name: "chunk-libs",
		            test: /[\\/]node_modules[\\/]/,
		            priority: 10,
		            chunks: "initial", // 只打包初始时依赖的第三方
		          },
		      	  ...
		        }
		      })

		     // 体积分析
		     config.plugin('bundle-analyzer')
		       .use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin, [{analyzerPort: 8880}])

		     // 分析 webpack 的总打包耗时以及每个 plugin 和 loader 的打包耗时
		     config.plugin('speed-measure')
		       .use(require('speed-measure-webpack-plugin'))

		     // 打包进度
		     config.plugin('progress')
		       .use(require("simple-progress-webpack-plugin"), [{format: 'minimal'}]);
    }

	 // 重复构建,使用缓存,加快构建
    config.plugin('hard-source')
           .use(require('hard-source-webpack-plugin'));

	  config.resolve.alias
	      .set("@", resolve("src"))
	      .set("@images", resolve("src/assets/images"));

	  // 注入cdn
	   config.plugin("html").tap((args) => {
	      args[0].cdn = cdn.build;
	      return args;
	    });

    // todo plugin的名字要唯一,不然后面同名的会覆盖
    config.plugin('happypack-babel').use(HappyPack, [{
      id: 'babel',
      loaders: ['babel-loader'],
      threadPool: happyThreadPool
    }])

    config.plugin('happypack-styles').use(HappyPack, [{
      id: 'styles',
      loaders: [{
        loader: 'style-resources-loader',
        options: {
          patterns: [
            ...
          ]
        }
      }],
      threadPool: happyThreadPool
    }])

    const jsRule = config.module.rule('js')
    jsRule.uses.delete('thread-loader')
    jsRule.uses.delete('babel-loader')
    jsRule.use('happypack').loader('happypack/loader?id=babel')

    const types = ['vue-modules', 'vue', 'normal-modules', 'normal'];
    types.forEach(type => {
      let rule = config.module.rule('less').oneOf(type)
      rule.uses.delete('style-resources-loader')
      rule.use('happypack').loader('happypack/loader?id=styles')
    });

    const oneOfsMap = config.module.rule("less").oneOfs.store;
    oneOfsMap.forEach(item => {
      item.uses.delete('style-resources-loader')
      item.use("happypack").loader('happypack/loader?id=styles')
    })

    let originUse = config.module.rule('images').toConfig().use
    let newLoader = { loader: 'thread-loader' }
    originUse.splice(0, 0, newLoader)
    config.module.rule('images').uses.clear()
    config.module.rule('images').merge({ use: originUse })


    const stylusRule = config.module.rule('stylus').oneOf('normal')
    stylusRule.uses.delete('postcss-loader')

    // 图片压缩
    config.module.rule('images')
           .use('image-webpack-loader')
           .loader('image-webpack-loader')
           .options({
              disable: process.env.NODE_ENV === 'development', // 开发模式下调试速度更快
            })
           .end()
	      
    
	 // 删除单个规则中的全部 loader
   	 const svgRule = config.module.rule('svg');
   	  svgRule.uses.clear();
	  svgRule.use('svg-inline-loader')
	     .loader('svg-inline-loader')
	     .options({symbolId: 'icon-[name]'})
	}
}

查看vue项目的webpack配置

idea终端/CMD终端,运行命令,会在项目根目录下生成webpack配置.js文件:

开发环境:npx vue-cli-service inspect --mode development >> webpack.config.development.js
生产环境:npx vue-cli-service inspect --mode production >> webpack.config.production.js
在产生的 js 文件开头,添加:module.exports =,然后格式化即可查看。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐