检测React/ReactDOM的开发/生产构建。

22

React开发构建与生产构建有所不同,例如错误处理方式。

可以通过环境变量来确定使用的是哪个版本,但这仅适用于模块化环境,因为React包中使用了process.env.NODE_ENV

if (process.env.NODE_ENV === 'production') {
  module.exports = require('./cjs/react.production.min.js');
} else {
  module.exports = require('./cjs/react.development.js');
}

process.env 可能不适用的情况是 React 作为 UMD 模块全局使用时,window.Reactwindow.ReactDOM 的情况:

<script src="some-unknown-react-version.js"></script>

<script>
React // is it in production mode?
</script>

可能的用途包括:

  • 一个可以在模块化和全局环境中使用的组件(以UMD形式发布),并在生产中呈现不同结果。

  • 浏览器扩展或用户脚本,其中构建/生产模式是从ReactReactDOM对象检测到的。

如何在运行时准确地检测React开发/生产构建,而不需要借助环境?

我正在寻找可靠且清晰的解决方案,如果可能的话,适用于React 15和React 16。

这不是类似问题的重复,因为现有答案是通过process.env解决问题的。


你的部署设置是什么样子?没有一种“正确”的方法来做你想做的事情。我会关注生产/开发构建之间的差异,并创建自己的辅助函数。看看我的有关prop-types的答案。 - Michal
7个回答

34

有所不同。在开发模式下,React元素具有定义_self的属性,而在生产模式下该属性未定义。

因此,解决方案是使用以下代码测试该属性:

function ReactIsInDevelomentMode(){ 
    return '_self' in React.createElement('div');
}

2
请问你能否提供React文档的URL,以证明你所说的内容? - lmiguelvargasf
@lmiguelvargasf 这种行为并没有被React的创建者记录在文档中。这是我观察到的一些东西。 - Constantin Galbenu

2
你的问题很清晰,但你并没有说明你使用的构建系统,你是使用webpack还是parcel?是否有服务器端渲染?你运行已构建的应用程序时是使用node还是pm2?或者你只是构建了你的应用程序,然后将打包好的文件放在由其他技术(如PHPC#)制作的页面中?
实际上,以上问题可以确定你的答案,但肯定是要使用模块打包器,所以我建议在你的项目中使用一个解析config文件。
如果我处在你的位置,毫无疑问,我会使用webpack,两个webpack配置文件,一个用于开发,另一个用于生产模式。然后我创建一个文件夹,其中包含两个名为config.dev.jsconfig.prod.js的文件。在开发webpack中:
~~~
module.exports = {
        ~~~
        resolve: {
            extensions: ['.js', '.jsx'],
            alias: {
                ~~~
                Config: `${srcDir}/config/config.dev.js`,
                // srcDir is a defined variable for source directory
            }
        },
        ~~~

在生产环境中的webpack:

~~~
module.exports = {
        ~~~
        resolve: {
            extensions: ['.js', '.jsx'],
            alias: {
                ~~~
                Config: `${srcDir}/config/config.prod.js`,
                // srcDir is a defined variable for source directory
            }
        },
        ~~~

现在,您可以为每个构建类型(devprod)提供数据。例如,在您的config.dev.js中可以编写:

module.exports = {
    buildType: "dev"
};

当然,在你的config.prod.js文件中可以这样写:
module.exports = {
    buildType: "prod"
};

你绝对可以通过以下代码在你的react文件中访问配置数据:

import config from 'Config';

通过这个解决方案,您可以在应用程序的实时执行中了解构建类型。
注意:如需了解更多信息,请参阅我的Medium文章,如果您不熟悉长篇阅读,请查看存储库。另外,我的答案的新版本存储库包含配置。

正如我在评论中所解释的那样,我的构建设置并不重要。请参见问题中的“可能用途”。例如,组件是构建为UMD,并且可以被第三方使用。我不负责构建整个应用程序,只需检查它是开发环境还是生产环境的一部分即可。 - Estus Flask
好的,我在我的答案下面假设了你在评论中提到的情况,无论如何,你都应该捆绑你的组件,这样你就可以使用我的方式。 - AmerllicA
我不确定如何使用它。我需要检测捆绑组件所使用的环境,而不是我的环境。我会尝试解释一下。在构建组件时无法做出决定。如果我将组件作为UMD与“production”设置捆绑,并且开发人员在开发环境中使用它,则被视为开发环境,这就是我试图从React对象中检测到的内容。 - Estus Flask

2
通过使用 umd 构建在客户端检测开发 / 生产构建似乎有些牵强。如果存在这样的要求,为什么不使用 create-react-app 构建您的应用程序呢?
我不会评判您的决定,所以这里有一些有用的信息。
facebook 提供的 react-dev-tools 插件可以检测构建类型。
以下是上述插件的相关部分: https://github.com/facebook/react-devtools/blob/faa4b630a8c055d5ab4ff51536f1e92604d5c09c/backend/installGlobalHook.js#L23 希望这对您有所帮助。

谢谢!我没有查看DevTools,因为我没有想到它们会做这样的事情;在生产构建的情况下,它们只是挂起而没有有用的信息。请考虑将链接中的相关代码发布到答案中,以便不必访问外部资源即可理解。是的,该解决方案似乎过于hacky,不能在常规组件中使用。 - Estus Flask
ReactDOM代码包含了React Dev Tools的钩子,因此这种hack方法不可用。最好的解决方案是将适当的bundle类型添加到React/ReactDOM本身中。 - Dolf Barr
这个问题的主要目标是理解构建类型,使用谷歌浏览器插件如何帮助开发者收集数据? - AmerllicA
我在谈论这个答案中的解决方案:React Dev Tool包含检测React构建类型的功能,但它之所以有效是因为ReactDOM库包含了一个钩子用于React Dev Tool,并且它在ReactDOM初始化时运行。 因此,如果ReactDOM库可以包含React Dev Tool的钩子,那么它也可以包含构建类型,最好的解决方案是创建具有适当功能的拉取请求。 - Dolf Barr
@DolfBarr 我同意在ReactDOM本身具有此功能可能很有用。然而,无论React Dev Tool如何,都可以使用相同的方法来钩入常规脚本中的__REACT_DEVTOOLS_GLOBAL_HOOK__。我不会在常规组件中使用它,但对于其他目的(浏览器扩展或用户脚本)可能会有用。 - Estus Flask

1

有一个小的“技巧”,可以用来检查加载了哪个版本的React。

React对象在全局变量中是可用的,而React的生产构建与开发版本至少有一点不同:它通常是被压缩的。因此,我们可以尝试检查我们是否正在使用压缩版本。

要进行检查,您可以将函数名称与某些React对象方法的属性名称进行比较,例如:

let func = 'Component'
if (React[func].name === func) {
  // non-minified -> development build
}

这种方法不是关于检查生产和开发,而是关于检查代码压缩情况,由于生产构建通常会进行压缩,因此这非常有帮助。

遗憾的是,这将导致错误的负面结果,因为minified !== production。至于我,我倾向于在开发中使用minified代码。我只会将其用作process.env.NODE_ENV检查的后备。 - Estus Flask

0

有些人提到过利用生产环境总是经过压缩而开发环境没有被压缩的事实。下面是一个具体的解决方案:

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.6/react.js"></script>

<script>
const reactInfo = {
    version: React.version,
    development: (String((new React.Children.map()).constructor).length > 100)
};
console.log(reactInfo);
</script>

<div id="content"></div>

我已经在 React 的 v14 到 v16 版本中测试了大约十几个版本。你可以从这里看到,除了一年前的一个小修改(不会影响答案,因为它的字符太少),这段代码自编写以来没有被更改过(尽管我测试了 11 个月之前的版本,存在一定差距)。

注意

Dev 有 200 个字符,而 Prod 有 70 个字符,因此 Dev:Prod 字符比率为 3:1。我选择 100,因为添加 90 个字符的代码将会在 Prod 中增加 30 行代码,所以根据给定信息,100 是最佳位置(技术上是 ~105 或其他值)。对于如此简单的函数(一个只在 5 年中被修改一次且只有 20 个字符修改的函数),极不可能添加 90 个字符或删除 100 个字符,因此我认为这应该是稳定的。

为了更稳定,或者至少知道如果出现错误,您可以检查它是否在 70 和 200 的范围内,并在不在这个范围内时抛出一个错误。这应该能捕获到任何大的变化(我几乎可以百分之百的确定它不会隐藏 bug),但你可能会得到一些误报。你需要哪种取决于你的使用情况。

编辑:

查看缩小版,这是一个正则表达式,它将推断一个函数是否已经被缩小,因此您可以放心使用这个。如果 React.Children.map 不是一个函数(几乎肯定永远不会发生),它也会崩溃,你可以根据你想要处理错误的严格程度来捕获或不捕获这个错误(或者完全忽略它,因为他们为什么要改变它呢)。即使它被缩小成 [native code],函数签名仍然存在,所以我认为它具有较好的未来性。不过基于简单起见,我会选择第一个方法。

const reactInfo = {
    version: React.version,
    development: !/function\s?\w?\(\w(,\w)*\)/.test(String((new React.Children.map()).constructor).split("{")[0])
};

谢谢。非常hacky和脆弱,但如果未来的版本经过彻底测试可能会有用。 - Estus Flask

0

React提供了react.js的开发版和生产版链接:

开发版:

<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

生产:

<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>

来源


如果要在不使用环境变量的情况下知道它是开发模式还是生产模式,则必须通过显式声明某些变量来分配其模式:(将以下脚本与反应脚本一起包含)

<script>
var devMode = 'development';
</script>

并在必要时检查devMode。


或者,您可以检查其模式,例如:(您必须在脚本标记中添加ID)

var script_id = document.getElementById('script-attached-id');
console.log(script_id.getAttribute('src').includes('development'));

这样,您只需要更新源路径并检测模式即可。


最后一种选择,我可以读取文件本身并检测其模式,因为React在其注释中提到了以下内容:
开发:
/** @license React v16.5.2
 * react.development.js

生产:
/** @license React v16.5.2
 * react.production.min.js

所以,在读取文件后,只需检查第二行的模式即可。或者您可以测试react.development.js而无需逐行检查。


这是真的。这就是为什么我在问题中指定了其他条件的原因。some-react-version.js - 从URL中无法确定它是哪个版本(开发版还是生产版)。在生产环境下以不同方式呈现的组件,无论是模块化还是全局环境,都不应该知道特定的<script>标签。 - Estus Flask
为了让自己清楚,我是从开发人员的角度来问问题的,假设我没有 devMode 变量可供检查,并且可以访问 ReactReactDOM 对象,但没有其他任何内容。这可能是我编写的浏览器扩展程序,也可能是 React 库,在其中需要检测 React 生产模式,但不想限制库在具有 process.env 的模块化环境中使用。问题是如何在不使用 process.env 的情况下检测构建。 - Estus Flask
没有办法。您必须明确设置某些变量。 - Bhojendra Rauniyar
希望能够找到一种检测方法,因为生产环境和开发环境的构建存在某些差异。这可能需要对React源代码有很好的了解,而我目前并没有。 - Estus Flask

0

你的部署设置是什么样子的?没有一种“正确”的方法来做你想做的事情。我会专注于生产/开发构建之间的差异,并创建自己的辅助函数。

编辑:看起来不可能从React类中检测prod/dev版本。

两个想法:

  1. 我不确定应用程序是如何构建的,但PropTypes应该是一个很好的ENV标识符。
  2. 如果你的生产React应用程序被缩小了,那么你可以简单地检测React代码是否被缩小了。(这将是hacky的,但它应该可以工作,关注空格数量或行长度,...)

我注意到在下面的评论中,你说minified !== production如果你能让它这样工作,那么这可能是你最好的选择。你不需要缩小开发反应代码。


我很感谢您的建议。目前我的部署设置并不重要,因为这个问题主要适用于我的代码,它将与其他人的应用程序一起工作。请参见“可能的用途”。 - Estus Flask
@estus 我刚更新了我的答案。我有点希望会有一些React.method,但在生产构建中是否会缺失还不确定。我建议关注检测React代码是否已被压缩。 - Michal
在“可能的用途”中,我自己的代码的缩小并不重要。对于浏览器扩展,缩小不会影响任何东西。对于组件库,使用我的库的开发人员的设置很重要,而不是我的设置。另一个答案已经提到了缩小的问题,我对此发表了评论。可以使用它,但不可靠,并且只在非模块化环境中才有意义,其中React是全局的。是的,我寻找过这样的React.method,但没有一致的方法。另一个答案中链接的React devtools代码显示这是可能的,但是很麻烦。 - Estus Flask
@estus,不是想挑起争端,但不使用环境变量确实是一种hack技巧。 :D - Michal
1
正如我所说,组件可以在全局环境中使用而不需要 process.env,因为在基本设置中开发人员可能会跳过构建步骤。在硬编码到 process.env 之前,我想弄清楚我的选择。看起来暴露 productionMode 属性是这种情况下的一个合适的解决方法。 - Estus Flask

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接