webpack-chain 🔗 https://github.com/Yatoo2018/webpack-chain
“version”: “7.0.0-dev”
webpack-chain
是用来简化webpack
配置的工具
基础使用 🔗 1
2
3
4
5
6
const Config = require ( 'webpack-chain' )
const config = new Config ();
console . log ( config . toString ());
通过webpack-chain
进行实例化,实例化后的config通过一系列的操作函数进行添加修改配置文件,最终通过.toString()
方法输出字符串
工程目录 🔗 image.png
源码分析 🔗 ChainedMap、ChainedSet 🔗 webpack-chain
中2个比较重要的工具,类似js中的Map
和Set
,两个类都继承Chainable
这个类
Chainable内部对自身进行缓存,在end方法中返回了自身
ChainedMap 🔗 ChainedMap
是一个基础类,后续的接口都是继承于这个类进行封装, 实现了它对应的接口
ChainedMap
中有一个私有变量
1
this . store = new Map ();
store变量还是使用的Map实例
extend 🔗 对实例的方法进行扩展,最终调用的是实例上的set
方法
1
2
3
4
5
6
7
extend ( methods ) {
this . shorthands = methods ;
methods . forEach (( method ) => {
this [ method ] = ( value ) => this . set ( method , value );
});
return this ;
}
set 🔗 在store中存入对应的值
1
2
3
4
set ( key , value ) {
this . store . set ( key , value );
return this ;
}
get 🔗 获取对应的key的值
1
2
3
get ( key ) {
return this . store . get ( key );
}
has 🔗 判断store中是否含有对应的key
1
2
3
has ( key ) {
return this . store . has ( key );
}
clear 🔗 清空store
1
2
3
4
clear () {
this . store . clear ();
return this ;
}
delete 🔗 删除对应的key
1
2
3
4
delete ( key ) {
this . store . delete ( key );
return this ;
}
getOrCompute 🔗 和get不同的是,会先判断是否含有对应的key,如果不存在,就会执行第2个参数函数,如果存在,就返回对应的store中对应的key
1
2
3
4
5
6
getOrCompute ( key , fn ) {
if ( ! this . has ( key )) {
this . set ( key , fn ());
}
return this . get ( key );
}
when 🔗 用来进行条件判断,当条件满足时执行true
or false
1
2
3
4
5
6
7
8
9
10
11
12
13
when (
condition ,
whenTruthy = Function . prototype ,
whenFalsy = Function . prototype ,
) {
if ( condition ) {
whenTruthy ( this );
} else {
whenFalsy ( this );
}
return this ;
}
merge 🔗 把配置对象合并到当前实例的store上,遇到值是对象的同时,递归合并
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
merge ( obj , omit = []) {
Object . keys ( obj ). forEach (( key ) => {
if ( omit . includes ( key )) {
return ;
}
const value = obj [ key ];
if (
( ! Array . isArray ( value ) && typeof value !== 'object' ) ||
value === null ||
! this . has ( key )
) {
this . set ( key , value );
} else {
this . set ( key , merge ( this . get ( key ), value ));
}
});
return this ;
}
clean 🔗 清楚对象中的空值,最终返回对象中值非null的对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
clean ( obj ) {
return Object . keys ( obj ). reduce (( acc , key ) => {
const value = obj [ key ];
if ( value === undefined ) {
return acc ;
}
if ( Array . isArray ( value ) && ! value . length ) {
return acc ;
}
if (
Object . prototype . toString . call ( value ) === '[object Object]' &&
! Object . keys ( value ). length
) {
return acc ;
}
acc [ key ] = value ;
return acc ;
}, {});
}
order 🔗 对当前store进行整理
把当前store转换成为一个对象{} 遍历当前对象,当对象含有__before
或者__after
对应的值时,把值插入到对应的位置 最终返回对象和排序后names
组成的数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
order () {
const entries = [... this . store ]. reduce (( acc , [ key , value ]) => {
acc [ key ] = value ;
return acc ;
}, {});
const names = Object . keys ( entries );
const order = [... names ];
names . forEach (( name ) => {
if ( ! entries [ name ]) {
return ;
}
const { __before , __after } = entries [ name ];
if ( __before && order . includes ( __before )) {
order . splice ( order . indexOf ( name ), 1 );
order . splice ( order . indexOf ( __before ), 0 , name );
} else if ( __after && order . includes ( __after )) {
order . splice ( order . indexOf ( name ), 1 );
order . splice ( order . indexOf ( __after ) + 1 , 0 , name );
}
});
return { entries , order };
}
entries/values 🔗 基于order方法进行实现,返回entries
对象和values
组成的数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
entries () {
const { entries , order } = this . order ();
if ( order . length ) {
return entries ;
}
return undefined ;
}
values () {
const { entries , order } = this . order ();
return order . map (( name ) => entries [ name ]);
}
ChainedSet 🔗 ChainedSet 在 constructor 函数中实例化了一个store, 实际上就是Set的实例
add/clear/delete/has 🔗 分别实现就是Set实例的对应的方法
最后返回this,以实现链式调用
prepend/merge 🔗 分别是使用Set实例结构后就是数组,
最后返回this,以实现链式调用
1
2
3
4
prepend ( value ) {
this . store = new Set ([ value , ... this . store ]);
return this ;
}
values 🔗 返回解构后的store
1
2
3
values () {
return [... this . store ];
}
when 🔗 条件判断调用,分别是条件为true或者false的回调函数
最后也会返回this, 实现链式调用
1
2
3
4
5
6
7
8
9
10
11
12
13
when (
condition ,
whenTruthy = Function . prototype ,
whenFalsy = Function . prototype ,
) {
if ( condition ) {
whenTruthy ( this );
} else {
whenFalsy ( this );
}
return this ;
}
Orderable 🔗 orderable
是webpack-chain
中另一个高阶类函数
通过继承类的形式对类进行扩展,最后返回一个扩展后的类
1
2
3
const Orderable = Cls => class extends Cls {
// ....
}
before/after 🔗 这两个函数分别实现了一个__before
属性和一个__after
属性,
最终对于实例的属性进行扩展
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
before ( name ) {
if ( this . __after ) {
throw new Error (
`Unable to set .before( ${ JSON . stringify (
name ,
) } ) with existing value for .after()` ,
);
}
this . __before = name ;
return this ;
}
after ( name ) {
if ( this . __before ) {
throw new Error (
`Unable to set .after( ${ JSON . stringify (
name ,
) } ) with existing value for .before()` ,
);
}
this . __after = name ;
return this ;
}
这里正好看到在ChainedMap中看到的order方法中的__before
方法和__after
方法, 应该就是这里进行扩展的
merge 🔗 1
2
3
4
5
6
7
8
9
10
11
merge ( obj , omit = []) {
if ( obj . before ) {
this . before ( obj . before );
}
if ( obj . after ) {
this . after ( obj . after );
}
return super . merge ( obj , [... omit , 'before' , 'after' ]);
}
Config 🔗 Config.js
是本项目的入口文件,也就是外部在使用的时候其实就是实例化的这个文件导出的类。
1
2
3
const Config = require ( 'webpack-chain' );
const config = new Config ();
默认构造函数在实例化的时候,同时也新建了许多其他实例,像devServer
、module
等实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
constructor () {
super ();
this . devServer = new DevServer ( this );
this . entryPoints = new ChainedMap ( this );
this . module = new Module ( this );
this . node = new ChainedMap ( this );
this . optimization = new Optimization ( this );
this . output = new Output ( this );
this . performance = new Performance ( this );
this . plugins = new ChainedMap ( this );
this . resolve = new Resolve ( this );
this . resolveLoader = new ResolveLoader ( this );
this . extend ([
'amd' ,
'bail' ,
'cache' ,
'context' ,
'devtool' ,
'externals' ,
'loader' ,
'mode' ,
'name' ,
'parallelism' ,
'profile' ,
'recordsInputPath' ,
'recordsPath' ,
'recordsOutputPath' ,
'stats' ,
'target' ,
'watch' ,
'watchOptions' ,
]);
}
在构造函数中同时使用extend方法,扩展实例上的方法
1
this [ method ] = value => this . set ( method , value );
这样我们就可以在实例上直接用[实例].
的形式直接调用
Config
上还有一个plugin
方法
1
2
3
plugin ( name ) {
return this . plugins . getOrCompute ( name , () => new Plugin ( this , name ));
}
plugin
通过注册一个name
,对应一个回调函数,回调函数返回一个Plugin实例
getOrCompute
方法用于确定this.plugins
中对于同一个name
,有且只有一个Plugin实例
1
2
3
config
. plugin ( 'clean' )
. use ( CleanPlugin , [[ 'dist' ], { root : '/dir' }]);
entry
方法用于注册入口文件,webpack首先上是支持多入口的,所以这里也通过getOrCompute
方法,对于同一个入口,返回一个实例
1
2
3
4
5
6
7
config
. entry ( 'index' )
. add ( 'src/index.js' )
. end ()
. entry ( 'index2' )
. add ( 'src/index2.js' )
. end ()
toString
方法用于返回最后实例化后的配置对象,实际上调用的是toConfig
方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
toConfig () {
const entryPoints = this . entryPoints . entries () || {};
return this . clean (
Object . assign ( this . entries () || {}, {
node : this . node . entries (),
output : this . output . entries (),
resolve : this . resolve . toConfig (),
resolveLoader : this . resolveLoader . toConfig (),
devServer : this . devServer . toConfig (),
module : this . module . toConfig (),
optimization : this . optimization . toConfig (),
plugins : this . plugins . values (). map (( plugin ) => plugin . toConfig ()),
performance : this . performance . entries (),
entry : Object . keys ( entryPoints ). reduce (
( acc , key ) =>
Object . assign ( acc , { [ key ] : entryPoints [ key ]. values () }),
{},
),
}),
);
}
返回一个去除了对象空值的配置后的对象
Module 🔗 Module对应于webpack.config.js
中的module
对象
webpack.config.js
1
2
3
4
5
module . exports = {
module : {
// ...
}
}
Module
中通过2个方法defaultRule
和rule
定义module中的具名规则rule
1
2
3
4
5
6
7
8
9
10
defaultRule ( name ) {
return this . defaultRules . getOrCompute (
name ,
() => new Rule ( this , name , 'defaultRule' ),
);
}
rule ( name ) {
return this . rules . getOrCompute ( name , () => new Rule ( this , name , 'rule' ));
}
具名的rule进行定义
1
2
3
4
5
config . module
. rule ( 'lint' )
. test ( /\.js$/ )
. rule ( 'compile' )
. test ( /\.js$/ )