为什么需要区分环境
在开发网页的时候,一般都会有多套运行环境,例如:
- 在开发过程中方便开发调试的环境;
- 发布到线上给用户使用的运行环境。
这两套不同的环境虽然都是由同一套源代码编译而来,但是代码内容却不一样,其差异包括:
- 线上代码被通过压缩代码中提到的方法压缩了;
- 开发用的代码包含一些用于提示开发者的日志,普通用户不可能去看这些日志;
- 开发用的代码所连接的后端数据接口地址也可能和线上环境不同,因为要避免在开发过程中造成对线上数据的影响。
为了尽可能复用代码,在构建的过程中需要根据目标代码要运行的环境输出不同的代码,我们需要一套机制在源码中去区分环境。幸运的是,Webpack
已经为我们实现了这一点。
cross-env
通过Node中的process对象中的env属性来设置一个环境变量NODE_ENV来区分当前webpack环境。但是不同电脑上设置的方式是不一样的,cross-env可以跨平台设置环境和使用环境变量,解决了windows环境下的问题。
yarn add cross-env -D
在package.json里配置:
"build": "cross-env NODE_ENV=production webpack",
"dev": "cross-env NODE_ENV=development webpack-dev-server"
2
webpack-merge
设置好环境变量之后,还需要将Webpack配置进行拆分,先在根目录下新建一个build目录,新建如下几个文件:
- webpack.config.common.js:公共配置;
- webpack.config.dev.js:开发环境配置;
- webpack.config.prod.js:生产环境配置。
使用webapck-merge插件,对配置进行合并使用。
yarn add webpack-merge -D
修改package.json里的配置:
"build": "cross-env NODE_ENV=production webpack --config ./build/webpack.config.prod.js",
"dev": "cross-env NODE_ENV=development webpack-dev-server --config ./build.\yanrwebpack.config.dev.js"
2
如何区分环境
具体区分方法很简单,在源码中通过如下方式:
if (process.env.NODE_ENV === 'production') {
console.log('你正在线上环境');
} else {
console.log('你正在使用开发环境');
}
2
3
4
5
其大概原理是通过环境变量的值去判断执行哪个分支。
当代码中出现了使用process
模块的语句时,Webpack
就会自动打包进process
模块的代码以支持非Node.js
的运行环境。当代码中没有使用process
时就不会打包进 process
模块的代码。这个注入的process
模块作用是为了模拟Node.js
中的 process
,以支持上面使用的process.env.NODE_ENV === 'production'
语句。
在构建线上环境代码时,需要给当前运行环境设置环境变量NODE_ENV = 'production'
,Webpack
相关配置如下:
const DefinePlugin = require('DefinePlugin');
module.exports = {
plugins: [
new DefinePlugin({
// 定义 NODE_ENV 环境变量为 production
'process.env': {
NODE_ENV: JSON.stringify('production')
}
}),
],
};
2
3
4
5
6
7
8
9
10
11
12
注意:在定义环境变量的值时用
JSON.stringify
包裹字符串的原因是,环境变量的值需要是一个由双引号包裹的字符串,而JSON.stringify('production')
的值正好等于'"production"'
。
执行构建后,你会在输出的文件中发现如下代码:
if (true) {
console.log('你正在使用线上环境');
} else {
console.log('你正在使用开发环境');
}
2
3
4
5
Webpack
定义的环境变量的值被代入到了源码中,process.env.NODE_ENV === 'production'
被直接替换成了true
。并且由于此时访问process
的语句被替换且不存在了,Webpack
也不会将process
模块包含到输出文件中了。
DefinePlugin
定义的环境变量只对Webpack
需要处理的代码有效,而不会影响 Node.js
运行时的环境变量的值。
通过Shell
脚本的方式定义的环境变量,例如NODE_ENV = production
,Webpack
是不认识的,对Webpack
需要处理的代码中的环境区分语句是没有作用的。
也就是说,只需要通过DefinePlugin
定义环境变量,就能使上面介绍的环境区分语句正常工作,没必要再次通过Shell
脚本的方式定义一遍。
如果你想让Webpack
使用通过Shell
脚本的方式定义的环境变量,可以使用 EnvironmentPlugin
,代码如下:
new webpack.EnvironmentPlugin(['NODE_ENV'])
// 以上这句代码实际上等价于:
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
})
2
3
4
5
6
结合UglifyJS
其实以上输出的代码还可以进一步优化,因为if(true)
语句永远只会执行前一个分支中的代码,也就是说最佳的输出应该直接是:
console.log('你正在线上环境');
Webpack
没有实现去除死代码的功能,但是UglifyJS
可以做这个事情,如何使用请阅读 压缩代码中的压缩JavaScript
相关内容。
第三方库中的环境区分
除了在自己写的源码中可以有环境区分的代码,很多第三方库也做了环境区分的优化。以React为例,它做了两套环境区分,分别是:
- 开发环境:包含类型检查、HTML元素检查等针对开发者的警告日志代码;
- 线上环境:去掉了所有针对开发者的代码,只保留让React能正常运行的部分,以优化大小和性能。
例如
React
源码中有大量类似下面这样的代码:
if (process.env.NODE_ENV !== 'production') {
warning(false, '%s(...): Can only update a mounted or mounting component.... ')
}
2
3
如果你不定义NODE_ENV=production
,那么这些警告日志就会被包含到输出的代码中,输出的文件将会非常大。
process.env.NODE_ENV !== 'production'
中的NODE_ENV
和'production'
两个值是社区的约定,通常使用这条判断语句在区分开发环境和线上环境。