diff --git a/packages/react-is/README.md b/packages/react-is/README.md
new file mode 100644
index 0000000000000..f744d7d1de708
--- /dev/null
+++ b/packages/react-is/README.md
@@ -0,0 +1,83 @@
+# `react-is`
+
+This package allows you to test arbitrary values and see if they're a particular React type, e.g. React Elements.
+
+## Installation
+
+```sh
+# Yarn
+yarn add react-is
+
+# NPM
+npm install react-is --save
+```
+
+## Usage
+
+### AsyncMode
+
+```js
+import React from "react";
+import * as ReactIs from 'react-is';
+
+ReactIs.isAsyncMode(); // true
+ReactIs.typeOf() === ReactIs.AsyncMode; // true
+```
+
+### Context
+
+```js
+import React from "react";
+import * as ReactIs from 'react-is';
+
+const ThemeContext = React.createContext("blue");
+
+ReactIs.isContextConsumer(); // true
+ReactIs.isContextProvider(); // true
+ReactIs.typeOf() === ReactIs.ContextProvider; // true
+ReactIs.typeOf() === ReactIs.ContextConsumer; // true
+```
+
+### Element
+
+```js
+import React from "react";
+import * as ReactIs from 'react-is';
+
+ReactIs.isElement(
); // true
+ReactIs.typeOf() === ReactIs.Element; // true
+```
+
+### Fragment
+
+```js
+import React from "react";
+import * as ReactIs from 'react-is';
+
+ReactIs.isFragment(<>>); // true
+ReactIs.typeOf(<>>) === ReactIs.Fragment; // true
+```
+
+### Portal
+
+```js
+import React from "react";
+import ReactDOM from "react-dom";
+import * as ReactIs from 'react-is';
+
+const div = document.createElement("div");
+const portal = ReactDOM.createPortal(, div);
+
+ReactIs.isPortal(portal); // true
+ReactIs.typeOf(portal) === ReactIs.Portal; // true
+```
+
+### StrictMode
+
+```js
+import React from "react";
+import * as ReactIs from 'react-is';
+
+ReactIs.isStrictMode(); // true
+ReactIs.typeOf() === ReactIs.StrictMode; // true
+```
diff --git a/packages/react-is/index.js b/packages/react-is/index.js
new file mode 100644
index 0000000000000..f8c9ffdcd287c
--- /dev/null
+++ b/packages/react-is/index.js
@@ -0,0 +1,12 @@
+/**
+ * Copyright (c) 2013-present, Facebook, Inc.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ */
+
+'use strict';
+
+export * from './src/ReactIs';
diff --git a/packages/react-is/npm/index.js b/packages/react-is/npm/index.js
new file mode 100644
index 0000000000000..3ae098d078776
--- /dev/null
+++ b/packages/react-is/npm/index.js
@@ -0,0 +1,7 @@
+'use strict';
+
+if (process.env.NODE_ENV === 'production') {
+ module.exports = require('./cjs/react-is.production.min.js');
+} else {
+ module.exports = require('./cjs/react-is.development.js');
+}
diff --git a/packages/react-is/package.json b/packages/react-is/package.json
new file mode 100644
index 0000000000000..8fd899a90e05b
--- /dev/null
+++ b/packages/react-is/package.json
@@ -0,0 +1,23 @@
+{
+ "name": "react-is",
+ "version": "16.3.0-alpha.0",
+ "description": "Brand checking of React Elements.",
+ "main": "index.js",
+ "repository": "facebook/react",
+ "keywords": ["react"],
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/facebook/react/issues"
+ },
+ "homepage": "https://reactjs.org/",
+ "peerDependencies": {
+ "react": "^16.0.0 || 16.3.0-alpha.0"
+ },
+ "files": [
+ "LICENSE",
+ "README.md",
+ "index.js",
+ "cjs/",
+ "umd/"
+ ]
+}
\ No newline at end of file
diff --git a/packages/react-is/src/ReactIs.js b/packages/react-is/src/ReactIs.js
new file mode 100644
index 0000000000000..d26bc82f5e8c1
--- /dev/null
+++ b/packages/react-is/src/ReactIs.js
@@ -0,0 +1,86 @@
+/**
+ * Copyright (c) 2013-present, Facebook, Inc.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ */
+
+'use strict';
+
+import {
+ REACT_ASYNC_MODE_TYPE,
+ REACT_CONTEXT_TYPE,
+ REACT_ELEMENT_TYPE,
+ REACT_FRAGMENT_TYPE,
+ REACT_PORTAL_TYPE,
+ REACT_PROVIDER_TYPE,
+ REACT_STRICT_MODE_TYPE,
+} from 'shared/ReactSymbols';
+
+export function typeOf(object: any) {
+ if (typeof object === 'object' && object !== null) {
+ const $$typeof = object.$$typeof;
+
+ switch ($$typeof) {
+ case REACT_ELEMENT_TYPE:
+ const type = object.type;
+
+ switch (type) {
+ case REACT_ASYNC_MODE_TYPE:
+ case REACT_FRAGMENT_TYPE:
+ case REACT_STRICT_MODE_TYPE:
+ return type;
+ default:
+ const $$typeofType = type.$$typeof;
+
+ switch ($$typeofType) {
+ case REACT_CONTEXT_TYPE:
+ case REACT_PROVIDER_TYPE:
+ return $$typeofType;
+ default:
+ return $$typeof;
+ }
+ }
+ case REACT_PORTAL_TYPE:
+ return $$typeof;
+ }
+ }
+
+ return undefined;
+}
+
+export const AsyncMode = REACT_ASYNC_MODE_TYPE;
+export const ContextConsumer = REACT_CONTEXT_TYPE;
+export const ContextProvider = REACT_PROVIDER_TYPE;
+export const Element = REACT_ELEMENT_TYPE;
+export const Fragment = REACT_FRAGMENT_TYPE;
+export const Portal = REACT_PORTAL_TYPE;
+export const StrictMode = REACT_STRICT_MODE_TYPE;
+
+export function isAsyncMode(object: any) {
+ return typeOf(object) === REACT_ASYNC_MODE_TYPE;
+}
+export function isContextConsumer(object: any) {
+ return typeOf(object) === REACT_CONTEXT_TYPE;
+}
+export function isContextProvider(object: any) {
+ return typeOf(object) === REACT_PROVIDER_TYPE;
+}
+export function isElement(object: any) {
+ return (
+ typeof object === 'object' &&
+ object !== null &&
+ object.$$typeof === REACT_ELEMENT_TYPE
+ );
+}
+export function isFragment(object: any) {
+ return typeOf(object) === REACT_FRAGMENT_TYPE;
+}
+export function isPortal(object: any) {
+ return typeOf(object) === REACT_PORTAL_TYPE;
+}
+export function isStrictMode(object: any) {
+ return typeOf(object) === REACT_STRICT_MODE_TYPE;
+}
diff --git a/packages/react-is/src/__tests__/ReactIs-test.js b/packages/react-is/src/__tests__/ReactIs-test.js
new file mode 100644
index 0000000000000..6200388fd525d
--- /dev/null
+++ b/packages/react-is/src/__tests__/ReactIs-test.js
@@ -0,0 +1,103 @@
+/**
+ * Copyright (c) 2013-present, Facebook, Inc.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @emails react-core
+ */
+
+'use strict';
+
+let React;
+let ReactDOM;
+let ReactIs;
+
+describe('ReactIs', () => {
+ beforeEach(() => {
+ jest.resetModules();
+
+ React = require('react');
+ ReactDOM = require('react-dom');
+ ReactIs = require('react-is');
+ });
+
+ it('should return undefined for unknown/invalid types', () => {
+ expect(ReactIs.typeOf('abc')).toBe(undefined);
+ expect(ReactIs.typeOf(true)).toBe(undefined);
+ expect(ReactIs.typeOf(123)).toBe(undefined);
+ expect(ReactIs.typeOf({})).toBe(undefined);
+ expect(ReactIs.typeOf(null)).toBe(undefined);
+ expect(ReactIs.typeOf(undefined)).toBe(undefined);
+ });
+
+ it('should identify async mode', () => {
+ expect(ReactIs.typeOf()).toBe(
+ ReactIs.AsyncMode,
+ );
+ expect(ReactIs.isAsyncMode()).toBe(true);
+ expect(ReactIs.isAsyncMode({type: ReactIs.AsyncMode})).toBe(false);
+ expect(ReactIs.isAsyncMode()).toBe(false);
+ expect(ReactIs.isAsyncMode()).toBe(false);
+ });
+
+ it('should identify context consumers', () => {
+ const Context = React.createContext(false);
+ expect(ReactIs.typeOf()).toBe(ReactIs.ContextConsumer);
+ expect(ReactIs.isContextConsumer()).toBe(true);
+ expect(ReactIs.isContextConsumer()).toBe(false);
+ expect(ReactIs.isContextConsumer()).toBe(false);
+ });
+
+ it('should identify context providers', () => {
+ const Context = React.createContext(false);
+ expect(ReactIs.typeOf()).toBe(ReactIs.ContextProvider);
+ expect(ReactIs.isContextProvider()).toBe(true);
+ expect(ReactIs.isContextProvider()).toBe(false);
+ expect(ReactIs.isContextProvider()).toBe(false);
+ });
+
+ it('should identify elements', () => {
+ expect(ReactIs.typeOf()).toBe(ReactIs.Element);
+ expect(ReactIs.isElement()).toBe(true);
+ expect(ReactIs.isElement('div')).toBe(false);
+ expect(ReactIs.isElement(true)).toBe(false);
+ expect(ReactIs.isElement(123)).toBe(false);
+ expect(ReactIs.isElement(null)).toBe(false);
+ expect(ReactIs.isElement(undefined)).toBe(false);
+ expect(ReactIs.isElement({})).toBe(false);
+
+ // It should also identify more specific types as elements
+ const Context = React.createContext(false);
+ expect(ReactIs.isElement()).toBe(true);
+ expect(ReactIs.isElement()).toBe(true);
+ expect(ReactIs.isElement()).toBe(true);
+ expect(ReactIs.isElement()).toBe(true);
+ expect(ReactIs.isElement()).toBe(true);
+ });
+
+ it('should identify fragments', () => {
+ expect(ReactIs.typeOf()).toBe(ReactIs.Fragment);
+ expect(ReactIs.isFragment()).toBe(true);
+ expect(ReactIs.isFragment({type: ReactIs.Fragment})).toBe(false);
+ expect(ReactIs.isFragment('React.Fragment')).toBe(false);
+ expect(ReactIs.isFragment()).toBe(false);
+ expect(ReactIs.isFragment([])).toBe(false);
+ });
+
+ it('should identify portals', () => {
+ const div = document.createElement('div');
+ const portal = ReactDOM.createPortal(, div);
+ expect(ReactIs.typeOf(portal)).toBe(ReactIs.Portal);
+ expect(ReactIs.isPortal(portal)).toBe(true);
+ expect(ReactIs.isPortal(div)).toBe(false);
+ });
+
+ it('should identify strict mode', () => {
+ expect(ReactIs.typeOf()).toBe(ReactIs.StrictMode);
+ expect(ReactIs.isStrictMode()).toBe(true);
+ expect(ReactIs.isStrictMode({type: ReactIs.StrictMode})).toBe(false);
+ expect(ReactIs.isStrictMode()).toBe(false);
+ expect(ReactIs.isStrictMode()).toBe(false);
+ });
+});
diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js
index ff57b766998e0..b29c382d51d15 100644
--- a/scripts/rollup/bundles.js
+++ b/scripts/rollup/bundles.js
@@ -234,6 +234,16 @@ const bundles = [
global: 'ReactCallReturn',
externals: [],
},
+
+ /******* React Is *******/
+ {
+ label: 'react-is',
+ bundleTypes: [NODE_DEV, NODE_PROD, UMD_DEV, UMD_PROD],
+ moduleType: ISOMORPHIC,
+ entry: 'react-is',
+ global: 'ReactIs',
+ externals: [],
+ },
];
// Based on deep-freeze by substack (public domain)
diff --git a/scripts/rollup/results.json b/scripts/rollup/results.json
index 78fc29e1035e8..57d2013e37f29 100644
--- a/scripts/rollup/results.json
+++ b/scripts/rollup/results.json
@@ -398,6 +398,34 @@
"packageName": "react-reconciler",
"size": 41327,
"gzip": 13133
+ },
+ {
+ "filename": "react-is.development.js",
+ "bundleType": "NODE_DEV",
+ "packageName": "react-is",
+ "size": 3358,
+ "gzip": 1015
+ },
+ {
+ "filename": "react-is.production.min.js",
+ "bundleType": "NODE_PROD",
+ "packageName": "react-is",
+ "size": 1433,
+ "gzip": 607
+ },
+ {
+ "filename": "react-is.development.js",
+ "bundleType": "UMD_DEV",
+ "packageName": "react-is",
+ "size": 3547,
+ "gzip": 1071
+ },
+ {
+ "filename": "react-is.production.min.js",
+ "bundleType": "UMD_PROD",
+ "packageName": "react-is",
+ "size": 1515,
+ "gzip": 670
}
]
}
\ No newline at end of file
diff --git a/scripts/rollup/validate/eslintrc.umd.js b/scripts/rollup/validate/eslintrc.umd.js
index bfc5396ce52ec..7ee1e112f2b09 100644
--- a/scripts/rollup/validate/eslintrc.umd.js
+++ b/scripts/rollup/validate/eslintrc.umd.js
@@ -17,6 +17,7 @@ module.exports = {
// UMD wrapper code
// TODO: this is too permissive.
// Ideally we should only allow these *inside* the UMD wrapper.
+ exports: true,
module: true,
define: true,
require: true,