【webpack-chain GitHub 中文文档】
// 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 }]
]
}
}
]
}
]
}
}
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,
}))
}
}
const moduleRule = config.module
// 删除命名为 js 的规则
moduleRule.rules.delete('js')
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 去创建插件,因为已经为你做好了
}
}
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
}
)
config.plugin(name)
.init((Plugin, args) => new Plugin(...args))
config.plugins.delete(name)
module.exports = {
chainWebpack: (config) => {
// vue-cli3.X 会自动进行模块分割抽离,如果不需要进行分割,可以手动删除
config.optimization.delete('splitChunks');
}
}
指定当前插件上下文应该在另一个指定插件之前/之后执行,你不能在同一个插件上同时使用 .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 */ }
)
]
config.module
.rule('resolveNodeModules')
.test(/\.m?jsx?$/)
.include.add(/node_modules\/(vue2-editor|quill|quill-delta)\/.*/)
.end()
.use('babel-loader')
.loader('babel-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'
}
]
}
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')
}
}
// 两种写法都可修改
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']
}))
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()
})
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' }
// ]
// }
// 用法(v5版本可用):
config.optimization
.minimizer(name)
.tap(args => newArgs)
// 示例:
config.optimization
.minimizer('css')
.tap(args => [...args, { cssProcessorOptions: { safe: false } }])
// 用法(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
在操作的时候分别有如下方法可以链式调用:
// 从 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)
// 添加/追加 给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)
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]'})
}
}
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 =
,然后格式化即可查看。