原文链接:https://juejin.im/post/6844903917629734926
作者:Jess15088
===============================
实现 development
和 production
模式执行不同的代码,这依赖于你的JS代码编译流程(当然前提是你有JS编译流程……)。在Facebook,我们大概这样写代码:
if (__DEV__) {
doSomethingDev();
} else {
doSomethingProd();
}
复制代码
上面代码里,__DEV__
不是一个真实存在的变量。它在JS代码编译阶段,会被一个常量来替换,通常在 development
下是 true
,在 production
模式下是 false
。在不同模式下,编译出来的最终代码长这样:
// In development:
if (true) {
doSomethingDev(); // 👈
} else {
doSomethingProd();
}
// In production:
if (false) {
doSomethingDev();
} else {
doSomethingProd(); // 👈
}
复制代码
在 production
模式下,你通常会使用一些压缩工具(比如 terser)来压缩JS代码。同时,大多数的JS代码压缩工具,都会进行一些 死码消除,比如在生成的代码中,删除掉 if(false){}
这样的代码分支。 因此,在 production
模式下,经过压缩之后,最终产出的代码长这样:
// In production (after minification):
doSomethingProd();
复制代码
你在实际项目中,用到的可能并不是 __DEV__
这个标记常量,如果你用的是 webpack
这样的JS编译打包工具,那通常用的是另外一种常量标记方式,来实现这个功能。比如,在 webpack
社区中,通常是这样来区分 development
和 production
的代码分支:
if (process.env.NODE_ENV !== 'production') {
doSomethingDev();
} else {
doSomethingProd();
}
复制代码
当你使用一些打包工具(比如webpack)来从 npm
包的引入 React、Vue这样的类库时,这些类库里区分 development
和 production
的方式和上面这段代码是一样的(译注: 通过 process.env.NODE_ENV
来区分不同模式,应该是 web
前端开发中的一种约定了吧)。如果你是通过 <script>
标签的方式,来直接加载已经提前编译好的版本,那么通常是以JS代码的文件后缀 .js
和 .min.js
来区分 development
和 production
模式的代码。
通过 process.env.NODE_ENV
标记来区分 development
和 production
环境的约定,最初是来自于 Node.js
。在 Node.js 中,有一个全局变量 process
,并且可以通过 process.env
这个对象来访问到代码执行时候的环境变量。但是,在前端代码(译注:指运行在浏览器里的那种JS代码)里,并不存在全局的 process
变量🤯。
实际上,类似我们前面提到的 __DEV__
,process.env.NODE_ENV
也是在代码编译阶段会被 development
或者 production
常量替换掉。替换之后的代码如下:
// In development:
if ('development' !== 'production') { // true
doSomethingDev(); // 👈
} else {
doSomethingProd();
}
// In production:
if ('production' !== 'production') { // false
doSomethingDev();
} else {
doSomethingProd(); // 👈
}
复制代码
从上面可以看出,替换之后, if
里的表达式是不变的('production' !== 'production'
的值永远都是 false
),代码压缩工具能够移出掉对应的分支代码。最终 production
模式下,压缩之后的代码是这样的:
// In production (after minification):
doSomethingProd();
复制代码
在 import
和 export
被加入JS语言标准之前的很多年,存在好几个相互竞争的JS模块化方案。Node.js 采用了来自 CommonJS的 require()
和 module.exports
。
最初发布到 npm
上的包,都是给 Node.js 使用的,并不是给前端代码用的。作为曾经是(或许现在也是?)最流行的 Node.js 服务端框架,Express使用NODE_ENV这个环境变量来开启 production
模式。在这之后,其他的一些 npm
包也采用了 process.env.NODE_ENV
来区分不同环境。
像 browserify 这样的早期JavaScript代码打包工具希望能在前端工程中,引入 npm
包提供的代码。(是的,那时候几乎没有前端开发者使用npm来发布自己的代码,你能想象到吗?) 。因此,这些早期的打包工具采纳了 Node.js 生态中已经广泛采用的约定,使用 process.env.NODE_ENV
来区分不同环境。
从 React 发布的那天起,在提供预编译好的JS代码之后,React 还提供了对应的 npm
版本。伴随着 React 的流行,越来越多的前端开发者也基于 CommonJS 的方式来发布前端代码到 npm 仓库。
React 需要在 production
模式中移出掉 development
模式下的一些代码。正好 Browserify 对这个问题有了解决方案,因此 React 也采用了这个方案,在 npm 包中通过 process.env.NODE_ENV
来区分不同环境。到后来,越来越多的类库和工具,比如 Vue 和 webpack,都采纳了这个方案。
到2019年,browserify 已经不再流行。但是,在JavaScript代码编译阶段,将 process.env.NODE_ENV
替换成 development
或 production
继续像之前一样被社区广泛采用。
还有一个事情,可能会让你困惑。在 React 的 Github 源码中,你会发现 __DEV__
这样的环境标记。但是在 npm 上的 React 代码里,使用的却是 process.env.NODE_ENV
。这是神马情况呢?
由于历史原因,为了和 Facebook 内部代码保持一致,我们使用了 __DEV__
这个标记。在很长的一段时间里,React 代码都是直接被拷贝到 Facebook 的代码仓库,因此需要遵守相同的规则。因此,在发布到 npm 之前,我们会有一个替换步骤,将 __DEV__
替换为 process.env.NODE_ENV !== 'production'
。
在一些场景下,这会导致问题。一段基于 Node.js 约定的代码,在 npm 下运行的很好,但是在 Facebook 内部却出错;或者是相反的问题。
因此,从 React 16 开始,我们做出了一些调整。针对每个环境(包括 <script>
引入的预编译代码,npm,以及 Facebook内部仓库),我们都会 编译对应的bundle。
这意味着,针对 React 中的源码 if (__DEV__)
,我们实际上针对每个包都会生成2个bundle:一个是设置了 __DEV__ = true
编译出来的bundle;另一个是设置了 __DEV__ = false
编译的bundle。
举个例子:
if (process.env.NODE_ENV === 'production') {
module.exports = require('./cjs/react.production.min.js');
} else {
module.exports = require('./cjs/react.development.js');
}
复制代码
这个入口JS,是惟一一处你的打包工具需要替换 process.env.NODE_ENV
的地方,也是在这个入口JS,打包工具会忽略掉 development
的 require
。
react.production.min.js
和 react.development.js
两个文件中,都 没有包含 process.env.NODE_ENV
这样的检查代码。这很好,因为在 Node.js 里访问 process.env
是 有些慢的。提前编译好两个模式下的 React 代码,也能让我们的最初JS文件大小,不管我们使用哪个打包工具,都 更加的一致。
作者:Jess15088
链接:https://juejin.im/post/6844903917629734926
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。