webpack-chain 速查手册之 ChainedMap
webpack-chain GitHub 中文文档
webpack-chain 速查手册之 ChainedSet
Set 和 Map 数据类型
Set 是一组值的集合,值允许任何类型,但不可以重复。会记住元素原始插入顺序所以是可迭代的。如果传入重复元素,会在Set中自动被过滤,可以利用Set来去重。
初始化Set需要传入一个数组,或者直接初始化空Set:new Set()
var s = new Set([1, 2, 3, 2, 1, '哈哈'])
console.log(s) // Set(2) {1, 2, 3, '哈哈'} 自动去除重复项
s.add(4) // 添加新元素 4
s.delete(3) // 删除元素 3
s.has(3) // 是否存在元素 3:false
s.clear() // 移除s中所有项
Map以[键,值]
的形式存储数据,并且能够记住键的原始插入顺序。键和值允许任何数据类型,键不可重复是惟一的,可以极高效地查找数据。
初始化Map需要传一个二维数组,或者直接初始化一个空Map:new Map()
var m = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]])
console.log(m) // Map(3) {"Michael" => 95, "Bob" => 75, "Tracy" => 85}
m.set('Adam', 67); // 添加新的key-value
m.has('Adam'); // 是否存在key 'Adam': true
m.get('Adam'); // 67
m.delete('Adam'); // 删除key 'Adam'
m.clear() // Map {}
获取它们的元素个数的属性是size
,如:
console.log(new Set([1,2,1,2]).size) // 2
它们都拥有遍历方法:
-
keys()
:返回所有键名 -
values()
:返回所有键值 -
entries()
:返回所有成员 -
forEach(value, key)
:遍历所有成员
由于Set中的数据没有键名只有值,或者说键和值是同一个,遍历时返回的 key 和 value 是一样的。
console.log(new Set([1,2,3,4]).values()) // SetIterator {1, 2, 3, 4}
console.log(new Set([1,2,3,4]).entries()) // SetIterator {1 => 1, 2 => 2, 3 => 3, 4 => 4}
console.log(new Map([['name','jackson'],['age',11]]).keys()) // MapIterator {"name", "age"}
new Map([['name','jackson'],['age',11]]).forEach((value, key) => { console.log(key, value)} )
// name jackson
// age 11
ChainedMap的API和方法:
操作类似于JavaScript Map。除非另有说明,否则这些方法将返回 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)
被标记为ChainedMap的属性
-
config
webpack配置实例 配置最顶级
const path = require('path')
config.set('context', path.resolve(__dirname, '.')) // 设置 webpack 上下文目录为项目根目录
配置速记方法:可以用键名作为方法名为ChainedMap设置一个值,从而简化ChainedMap.set(键, 值)
方法
// 在 ChainedMap 上设置一个值的 速记方法
config.mode('development') // 告诉 webpack 使用开发环境模式优化
// 等效于:
config.set('mode', 'development')
end()方法可以让我们回到上一级API实例
链式调用时我们会移动到API的下一级甚至更深层,这会更改当前的上下文。编写规范是到下一层遍缩进一个tab,同一层级缩进空格相等。如果想回到上一层级继续链式调用上级API的方法,调用** .end()**即可实现。因为全部的API调用都将在当前上下文中返回该API实例,关于webpack配置项层级关系请参阅 webpack文档层级结构 。
举例:
config.module
.rule('vue')
.test(/\.vue$/)
.use('cache-loader') // use 和 test 同级,都是 rule 下级 API
.loader(require.resolve('cache-loader'))
.end() // 调用end() 由回到 rule API 层级
.use('vue-loader')
.loader(require.resolve('vue-loader'))
-
config.output
webpack输出
config.
output
.path(path.join(__dirname, 'dist')) // 设置打包输出的目录为根目录的 dist
config.resolve.alias
.set('@', path.join(__dirname,'src'))
.set('@utils', path.join(__dirname,'src/utils'))
.delete('@api')
.clear()
-
config.resolve.plugin(name)
使用额外的解析插件
使用方式和下面config.plugin(name)
一致,只是前面多了个.resolve
而已 -
config.performance
性能
config.performance
.hints('error')
.maxEntrypointSize(400000)
.maxAssetSize(100000)
.assetFilter(assetFilename => assetFilename.endsWith('.js'))
// Minimizer 使用插件时可以通过它们的路径指定,从而允许在不使用插件或webpack配置的情况下跳过昂贵的 require
config.optimization
.minimizer('css')
.use(require.resolve('optimize-css-assets-webpack-plugin'), [{ cssProcessorOptions: { safe: true }
// 修改参数
config.optimization
.minimizer('css')
.tap(args => [...args, { cssProcessorOptions: { safe: false } }])
// 修改实例
config.optimization
.minimizer('css')
.init((Plugin, args) => new Plugin(...args))
// 移除
config.optimization.minimizers.delete('css')
顺便提下 when(condition, whenTruthy, whenFalsy)
的用法:
// 在生产环境中添加缩小插件,非生产环境devtool到源映射
config
.when(process.env.NODE_ENV === 'production',
config => {
const terserOptions = require('./terserOptions')
config.optimization
.minimizer(terser)
.use(require.resolve('terser-webpack-plugin'), [terserOptions(options)])
},
config => config.devtool('source-map')
)
-
config.plugin(name)
插件
// 添加
config
.plugin('env')
.use(require.resolve('webpack/lib/EnvironmentPlugin'), [{ 'VAR': false }])
// 修改参数
config
.plugin('env')
.tap(args => [...args, 'SECRET_KEY'])
// 修改实例
config
.plugin('env')
.init((Plugin, args) => new Plugin(...args))
// 移除
config.plugins.delete('env')
// 指定当前插件上下文应该在另一个指定插件之前执行
config
.plugin('html-template')
.use(HtmlWebpackTemplate)
.end()
.plugin('script-ext')
.use(ScriptExtWebpackPlugin)
.before('html-template')
// 指定当前插件上下文应该在另一个指定插件之后执行
config
.plugin('html-template')
.after('script-ext')
.use(HtmlWebpackTemplate)
.end()
.plugin('script-ext')
.use(ScriptExtWebpackPlugin)
-
config.node
配置node
config.node
.set('__dirname', 'mock')
.set('__filename', 'mock')
-
config.devServer
开发服务器(DevServer)
config.node
.set('__dirname', 'mock')
.set('__filename', 'mock')
-
config.module
模块(Module)
// 不解析的模块
config.module
.noParse(/^(vue|vue-router|vuex|vuex-router-sync)$/)
-
config.module.rule(name)
创建模块时匹配请求的规则数组中的一项
和 webpack 中module: { rules: [...] }
的配置方式不同,webpack-chain 规则是一项一项用.rule
分开写的。rule(name)
中的name
为自定义规则名称。 -
config.module.rule(name).use(name)
和 Rule.use 不同,这个use(name)
是每个loader入口的自定义名称,即.rule(name).set('use',name)
,为了后续针对性的修改,方便识别而自行取名的。一个use(name)
对应一个loader(loader)
属性,注意和loader入口数组区分(rules: [{ use: ['style-loader','css-loader'] })
)
.tap()
修改这个接口很实用,特别适合当在别处已经有做过某规则(如.rule(name)
)的配置时,不方便去原文件修改,直接修改loader配置的情况,也可以同时链式加其他loader。比如覆盖框架的默认webpack配置。
// 创建
config.module
.rule('compile')
.use('babel')
.loader('babel-loader')
.options({ presets: ['@babel/preset-env'] })
// 修改选项
config.module
.rule('compile')
.use('babel')
.tap(options => merge(options, {
plugins: ['@babel/plugin-proposal-class-properties']
}))
-
用
enforce(preOrPost)
定义loader执行顺序
先执行上面的loader(pre
) 或下面的loader (post
)。
根据文档的API和实测,这个配置和webpack官方的区别挺大,不要把这里的pre
和post
跟前置loader和后置loader混淆了。
用 webpack-chain 为一条规则配置多个loader时,也是按从下到上的顺序执行,即默认最后一个是前置loader(先执行),第一个是后置loader。我们可以用enforce
来调整这个顺序。
// 速记格式解读
config.module
.rule(name)
.test(test)
.pre() // 指代 .use(loader-name-pre)
.post() // 指代另一个 .use(loader-name-post)
.enforce(preOrPost) // 值为'pre' 则表示先执行上面的loader,即loader-name-pre;值为'post' 则先执行下方的loader,loader-name-post
// 本来必须把 image-webpack-loader 配置在后面才能先执行
// 用 enforce('pre') 手动设置先执行上面的 loader
config.module
.rule('images')
.use('image-webpack-loader') // 在前面表示 pre
.loader('image-webpack-loader')
.options({...})
.end() //回到use同层级
.use('url-loader') // 在后面表示 post
.loader(require.resolve('url-loader'))
.options({...})
.end()
.enforce('pre') // 先执行上面的 image-webpack-loader
-
config.module.rule(name).oneOf(name)
Rule.oneOf
通过匹配资源请求字符串的查询部分,如foo.css?inline
的inline
,一旦匹配到oneOf
的其中一条的resourceQuery,就跳过其他所有的loader。
可以通过.before()
或.after()
使某条oneOf先于或后于另一条去被尝试匹配。
config.module
.rule('css')
.oneOf('inline')
.resourceQuery(/inline/)
.use('url')
.loader('url-loader')
.end()
.end()
.oneOf('external')
.before('inline')
.resourceQuery(/external/)
.use('file')
.loader('file-loader')