webpack

webpack.DefinePlugin介绍

基本用法

这个插件用来定义全局变量,在webpack打包的时候会对这些变量做替换。
先看个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//webpack.config.js
var webpack = require('webpack');
var path = require('path');
module.exports = {
entry: {
index: "./js/index.js"
},
output: {
path: "./dist/",
filename: "js/[name].js",
chunkFilename: "js/[name].js"
},
plugins: [
new webpack.DefinePlugin({
SOMETHINE: 'This is something we needed.'
})
]
};
//index.js
console.log(SOMETHINE);

编译完的结果如下:

1
2
3
function(module, exports, __webpack_require__) {
console.log((This Is The Test Text.));
}

可以看到代码中 SOMETHINE 被直接替换为 This is something we needed. 但是我们的本意中 SOMETHINE 是一个字符串,而直接替换后却不是一个字符串。怎么办呢?

方法一:可以将 SOMETHINE 的值写成

1
SOMETHINE: '"This is something we needed."'

方法二: 借助 JSON.tringify ,转为字符串

1
SOMETHINE: JSON.stringify('This is something we needed.')

推荐使用方法二,它不仅可以处理字符串,还可以处理Object中的字符串和Array。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//webpack.config.js
plugins: [
new webpack.DefinePlugin({
OBJ: JSON.stringify({"key1": "this is value"}),
OBJ2: {"key1": "this is value"},
OBJ3: {"key1": "'this is value'"},
ARRAY: JSON.stringify(["value1", "value2"]),
ARRAY2: ["value1", "value2"],
ARRAY3: ["'value1'", "'value2'"]
})
]
//index.js
console.log(OBJ);
console.log(OBJ2);
console.log(OBJ3);
console.log(ARRAY);
console.log(ARRAY2);
console.log(ARRAY3);

编译结果

1
2
3
4
5
6
console.log(({"key1":"this is value"})); // OBJ 正确
console.log(({"key1":this is value})); // OBJ2 this is value 被直接替换了,而非字符串
console.log(({"key1":'this is value'})); // OBJ3 正确
console.log((["value1","value2"])); // ARRAY 正确
console.log(({"0":value1,"1":value2})); // ARRAY2 直接写[]的形式,会被替换为object的类型,value1 和 value2 不是字符串
console.log(({"0":'value1',"1":'value2'})); // ARRAY3 正确

还剩下 Number 和 Boolean 两种变量类型,对于这两种类型,就不像上面介绍的这么麻烦了,直接写就行

1
2
3
4
5
6
7
8
9
10
11
12
//webpack.config.js
plugins: [
new webpack.DefinePlugin({
NUMBER: 12,
BOOL: true
})
]
//index.js
console.log(NUMBER);
console.log(BOOL);

编译结果

1
2
console.log((12));
console.log((true));
实际运用

介绍了这么多,在实际使用中, DefinePlugin 最为常用的用途就是用来处理我们开发环境和生产环境的不同。比如一些 debug 的功能在生产环境中需要关闭、开发环境中和生产环境中 api 地址的不同。以 vue-cli 生成的打包文件为例子,来看看在实际中的使用。

vue-cli 生成的目录中编译、打包相关的有两个文件夹 build 和 config 。

build 文件夹中找到 wbepack.dev.conf.js 和 webpack.prod.conf.js ,这两个文件中都通过 DefinePlugin 插件定义了 process.env 这个变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// webpack.dev.conf.js
var config = require('../config')
...
new webpack.DefinePlugin({
'process.env': config.dev.env
})
// webpack.prod.conf.js
var config = require('../config')
var env = config.build.env
...
new webpack.DefinePlugin({
'process.env': env
})

config 中对 env 的定义分别放在 config/dev.env.js 和 config/prod.env.js 中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//config/index.js
module.exports = {
build: {
env: require('./prod.env'),
...
},
dev: {
env: require('./dev.env'),
...
}
}
//config/prod.env.js
module.exports = {
NODE_ENV: '"production"'
}
//config/dev.env.js
var merge = require('webpack-merge')
var prodEnv = require('./prod.env')
module.exports = merge(prodEnv, {
NODE_ENV: '"development"'
})

到此可以看到,在 wbepack.dev.conf.js 中最终为

1
'process.env': {NODE_ENV: '"development"'}

在 webpack.prod.conf.js 中最终为

1
'process.env': {NODE_ENV: '"production"'}

在我们的代码中,如果部分只是在开发环境下才执行的逻辑,那么可以通过下面的方式

1
2
3
4
5
if ('development' === process.env.NODE_ENV) {
// 开发环境下的逻辑
} else {
// 生产环境下
}
阅读全部

Webpack.merge插件

webpack-merge插件可以用来merge两个webpack的配置。它的底层引用了lodash中的merge,但针对webpack的特点,专门对loaders,preLoaders,postLoaders专门做了处理。

一般情况下,对于以下两个module

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var module: {
loaders: [
{
test: /\.css$/,
loaders: ['style', 'css'],
},
]
}
var moduleExtend = {
loaders: [
{
test: /\.jsx?$/,
loader: 'babel?stage=1',
include: path.join(ROOT_PATH, 'app'),
},
]
}

进行merge(module, moduleExtend)后会得到如下结果:

1
2
3
4
5
6
7
8
9
moduleMerged = {
loaders: [
{
test: /\.jsx?$/,
loader: 'babel?stage=1',
include: path.join(ROOT_PATH, 'app'),
},
]
}

因为loaders是数组,所以结果中的css的loader信息丢失了。所以为了保证merge之后,loaders,preLoaders,postLoaders中的内容不会丢失,webpack-merge中对于loaders,preLoaders,postLoaders进行了特殊处理,采用concat对数组内容进行合并

1
2
3
4
if (isLoader(key)) {
return b.concat(a);
}
return a.concat(b);

如此,merge的结果为

1
2
3
4
5
6
7
8
9
10
11
loaders: [
{
test: /\.css$/,
loaders: ['style', 'css'],
},
{
test: /\.jsx?$/,
loader: 'babel?stage=1',
include: path.join(ROOT_PATH, 'app'),
}
]

但是如果遇到两个test值相同的loader时,仅仅用concat的话,会产生两条结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
merge({
loaders: [{
test: /\.js$/,
loader: 'babel'
}]
}, {
loaders: [{
test: /\.js$/,
loader: 'coffee'
}]
});
// 结果为
{
loaders: [{
test: /\.js$/,
loader: 'coffee'
},
{
test: /\.js$/,
loader: 'babel'
}]
}

这时对于js文件会先用babel-loader处理,然后再用coffee-loader处理。但是更多的,我们希望merge的结果是,后一个可以覆盖掉前一个。此外,在loaders中,除了可以写loader:’coffee’,还会有loaders:[‘xx’]的形式,如果一律采用concat的形式,显然过于粗暴了。所以,webpack-merge提供了更为聪明的的merge.smart函数,可以将test值相同的loader(preLoaders,postLoaders)进行合并

  • 对于loader,后面的会覆盖前面的属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
merge.smart({
loaders: [{
test: /\.js$/,
loader: 'babel'
}]
}, {
loaders: [{
test: /\.js$/,
loader: 'coffee'
}]
});
// 结果为
{
loaders: [{
test: /\.js$/,
loader: 'coffee'
}]
}
  • loaders将会合并为一个去重后的数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
merge.smart({
loaders: [{
test: /\.js$/,
loaders: ['babel']
}]
}, {
loaders: [{
test: /\.js$/,
loaders: ['coffee']
}]
});
// 结果为
{
loaders: [{
test: /\.js$/,
// 由于webpack处理loadersz中的值是按照从右往左的顺序,所以'coffee'放在'babel'的前面
// 这种从右往左的方式,让我们可以构造loaders处理链
loaders: ['coffee', 'babel']
}]
}

注意:对于loaders中带参数的形式,如babel?plugins[]=object-assign,在smart merge时会认为和babel、babel?…为同一类,后一个会对前一个产生覆盖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
merge.smart({
loaders: [{
test: /\.js$/,
loaders: ['babel?plugins[]=object-assign']
}]
}, {
loaders: [{
test: /\.js$/,
loaders: ['babel', 'coffee']
}]
});
// 结果为
{
loaders: [{
test: /\.js$/,
loaders: ['babel', 'coffee']
}]
}
  • 如果后一个loaders,前一个为loader,则merge时会将loader转化为loaders,然后进行merge
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
merge.smart({
loaders: [{
test: /\.js$/,
loader: 'babel'
}]
}, {
loaders: [{
test: /\.js$/,
loaders: ['coffee']
}]
});
// will become
{
loaders: [{
test: /\.js$/,
// 由于webpack处理loadersz中的值是按照从右往左的顺序,所以'coffee'放在'babel'的前面
loaders: ['coffee', 'babel']
}]
}
  • 如果后一个为loader,前一个为loaders,则merge的结果为loader
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
merge.smart({
loaders: [{
test: /\.js$/,
loaders: ['babel']
}]
}, {
loaders: [{
test: /\.js$/,
loader: 'coffee'
}]
});
// will become
{
loaders: [{
test: /\.js$/,
loader: 'coffee'
}]
}

注意:include和exclude的配置同时也会影响loader是否会被合并。如果两个loaders有不同的include或者exclude值(后一个loaders不包含include或exclude除外),那么loaders最终不会进行合并,而是像merge中进行了concat。

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
merge.smart({
loaders: [{
test: /\.js$/,
include: '/dir1',
loader: 'babel'
}]
}, {
loaders: [{
test: /\.js$/,
include: '/dir2',
loader: 'coffee'
}]
});
//merge结果为
{
loaders: [{
test: /\.js$/,
include: '/dir2',
loader: 'coffee'
},
{
test: /\.js$/,
include: '/dir1',
loader: 'babel'
}]
}
阅读全部