diff --git a/package.json b/package.json
index 6ef4909eddf7b..2f2bda553eaa7 100644
--- a/package.json
+++ b/package.json
@@ -4,6 +4,7 @@
"packages/*"
],
"devDependencies": {
+ "@babel/plugin-syntax-jsx": "^7.2.0",
"@babel/cli": "^7.0.0",
"@babel/code-frame": "^7.0.0",
"@babel/core": "^7.0.0",
diff --git a/packages/babel-plugin-react-jsx/README.md b/packages/babel-plugin-react-jsx/README.md
new file mode 100644
index 0000000000000..3e2858d310c65
--- /dev/null
+++ b/packages/babel-plugin-react-jsx/README.md
@@ -0,0 +1,5 @@
+This package is intended to eventually replace the current `@babel/plugin-transform-react-jsx`, changing the JSX transform from targeting `React.createElement(type, props, children)` to `React.jsx(types, props, key)`.
+
+https://github.com/reactjs/rfcs/blob/createlement-rfc/text/0000-create-element-changes.md
+
+**This is experimental and not intended to be used directly.**
diff --git a/packages/babel-plugin-react-jsx/__tests__/TransformJSXToReactCreateElement-test.js b/packages/babel-plugin-react-jsx/__tests__/TransformJSXToReactCreateElement-test.js
new file mode 100644
index 0000000000000..6e44eaeaec95b
--- /dev/null
+++ b/packages/babel-plugin-react-jsx/__tests__/TransformJSXToReactCreateElement-test.js
@@ -0,0 +1,392 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+/* eslint-disable quotes */
+'use strict';
+
+const babel = require('@babel/core');
+const codeFrame = require('@babel/code-frame');
+const {wrap} = require('jest-snapshot-serializer-raw');
+
+function transform(input, options) {
+ return wrap(
+ babel.transform(input, {
+ configFile: false,
+ plugins: [
+ '@babel/plugin-syntax-jsx',
+ '@babel/plugin-transform-arrow-functions',
+ ...(options && options.development
+ ? [
+ '@babel/plugin-transform-react-jsx-source',
+ '@babel/plugin-transform-react-jsx-self',
+ ]
+ : []),
+ [
+ './packages/babel-plugin-react-jsx',
+ {
+ development: __DEV__,
+ useBuiltIns: true,
+ useCreateElement: true,
+ ...options,
+ },
+ ],
+ ],
+ }).code
+ );
+}
+
+describe('transform react to jsx', () => {
+ it('fragment with no children', () => {
+ expect(transform(`var x = <>>`)).toMatchSnapshot();
+ });
+
+ it('React.Fragment to set keys and source', () => {
+ expect(
+ transform(`var x =
`, {
+ development: true,
+ })
+ ).toMatchSnapshot();
+ });
+
+ it('normal fragments not to set key and source', () => {
+ expect(
+ transform(`var x = <>
>`, {
+ development: true,
+ })
+ ).toMatchSnapshot();
+ });
+
+ it('should properly handle comments adjacent to children', () => {
+ expect(
+ transform(`
+ var x = (
+
+ {/* A comment at the beginning */}
+ {/* A second comment at the beginning */}
+
+ {/* A nested comment */}
+
+ {/* A sandwiched comment */}
+
+ {/* A comment at the end */}
+ {/* A second comment at the end */}
+
+ );
+ `)
+ ).toMatchSnapshot();
+ });
+
+ it('adds appropriate new lines when using spread attribute', () => {
+ expect(transform(` `)).toMatchSnapshot();
+ });
+
+ it('arrow functions', () => {
+ expect(
+ transform(`
+ var foo = function () {
+ return () => ;
+ };
+
+ var bar = function () {
+ return () => ;
+ };
+
+ `)
+ ).toMatchSnapshot();
+ });
+
+ it('assignment', () => {
+ expect(
+ transform(`var div = `)
+ ).toMatchSnapshot();
+ });
+
+ it('concatenates adjacent string literals', () => {
+ expect(
+ transform(`
+ var x =
+
+ foo
+ {"bar"}
+ baz
+
+ buz
+ bang
+
+ qux
+ {null}
+ quack
+
+ `)
+ ).toMatchSnapshot();
+ });
+
+ it('should allow constructor as prop', () => {
+ expect(transform(` ;`)).toMatchSnapshot();
+ });
+
+ it('should allow deeper js namespacing', () => {
+ expect(
+ transform(` ;`)
+ ).toMatchSnapshot();
+ });
+
+ it('should allow elements as attributes', () => {
+ expect(transform(`
/>`)).toMatchSnapshot();
+ });
+
+ it('should allow js namespacing', () => {
+ expect(transform(` ;`)).toMatchSnapshot();
+ });
+
+ it('should allow nested fragments', () => {
+ expect(
+ transform(`
+
+ < >
+ <>
+ Hello
+ world
+ >
+ <>
+ Goodbye
+ world
+ >
+ >
+
+ `)
+ ).toMatchSnapshot();
+ });
+
+ it('should avoid wrapping in extra parens if not needed', () => {
+ expect(
+ transform(`
+ var x =
+
+
;
+
+ var x =
+ {props.children}
+
;
+
+ var x =
+ {props.children}
+ ;
+
+ var x =
+
+ ;
+ `)
+ ).toMatchSnapshot();
+ });
+
+ it('should convert simple tags', () => {
+ expect(transform(`var x =
;`)).toMatchSnapshot();
+ });
+
+ it('should convert simple text', () => {
+ expect(transform(`var x = text
;`)).toMatchSnapshot();
+ });
+
+ it('should disallow spread children', () => {
+ let _error;
+ const code = `{...children}
;`;
+ try {
+ transform(code);
+ } catch (error) {
+ _error = error;
+ }
+ expect(_error).toEqual(
+ new SyntaxError(
+ 'undefined: Spread children are not supported in React.' +
+ '\n' +
+ codeFrame.codeFrameColumns(
+ code,
+ {start: {line: 1, column: 6}},
+ {highlightCode: true}
+ )
+ )
+ );
+ });
+
+ it('should escape xhtml jsxattribute', () => {
+ expect(
+ transform(`
+
;
+
;
+
;
+ `)
+ ).toMatchSnapshot();
+ });
+
+ it('should escape xhtml jsxtext', () => {
+ /* eslint-disable no-irregular-whitespace */
+ expect(
+ transform(`
+ wow
;
+ wôw
;
+
+ w & w
;
+ w & w
;
+
+ w w
;
+ this should not parse as unicode: \u00a0
;
+ this should parse as nbsp:
;
+ this should parse as unicode: {'\u00a0 '}
;
+
+ w < w
;
+ `)
+ ).toMatchSnapshot();
+ /*eslint-enable */
+ });
+
+ it('should handle attributed elements', () => {
+ expect(
+ transform(`
+ var HelloMessage = React.createClass({
+ render: function() {
+ return Hello {this.props.name}
;
+ }
+ });
+
+ React.render(
+ Sebastian
+
+ } />, mountNode);
+ `)
+ ).toMatchSnapshot();
+ });
+
+ it('should handle has own property correctly', () => {
+ expect(
+ transform(`testing ;`)
+ ).toMatchSnapshot();
+ });
+
+ it('should have correct comma in nested children', () => {
+ expect(
+ transform(`
+ var x = ;
+ `)
+ ).toMatchSnapshot();
+ });
+
+ it('should insert commas after expressions before whitespace', () => {
+ expect(
+ transform(`
+ var x =
+
+
+ `)
+ ).toMatchSnapshot();
+ });
+
+ it('should not add quotes to identifier names', () => {
+ expect(
+ transform(`var e = ;`)
+ ).toMatchSnapshot();
+ });
+
+ it('should not strip nbsp even couple with other whitespace', () => {
+ expect(transform(`
;`)).toMatchSnapshot();
+ });
+
+ it('should not strip tags with a single child of nbsp', () => {
+ expect(transform(`
;`)).toMatchSnapshot();
+ });
+
+ it('should properly handle comments between props', () => {
+ expect(
+ transform(`
+ var x = (
+
+
+
+ );
+ `)
+ ).toMatchSnapshot();
+ });
+
+ it('should quote jsx attributes', () => {
+ expect(
+ transform(`Button `)
+ ).toMatchSnapshot();
+ });
+
+ it('should support xml namespaces if flag', () => {
+ expect(
+ transform(' ', {throwIfNamespace: false})
+ ).toMatchSnapshot();
+ });
+
+ it('should throw error namespaces if not flag', () => {
+ let _error;
+ const code = ` `;
+ try {
+ transform(code);
+ } catch (error) {
+ _error = error;
+ }
+ expect(_error).toEqual(
+ new SyntaxError(
+ "undefined: Namespace tags are not supported by default. React's " +
+ "JSX doesn't support namespace tags. You can turn on the " +
+ "'throwIfNamespace' flag to bypass this warning." +
+ '\n' +
+ codeFrame.codeFrameColumns(
+ code,
+ {start: {line: 1, column: 2}},
+ {highlightCode: true}
+ )
+ )
+ );
+ });
+
+ it('should transform known hyphenated tags', () => {
+ expect(transform(` `)).toMatchSnapshot();
+ });
+
+ it('wraps props in react spread for first spread attributes', () => {
+ expect(transform(` `)).toMatchSnapshot();
+ });
+
+ it('wraps props in react spread for last spread attributes', () => {
+ expect(transform(` `)).toMatchSnapshot();
+ });
+
+ it('wraps props in react spread for middle spread attributes', () => {
+ expect(transform(` `)).toMatchSnapshot();
+ });
+
+ it('useBuiltIns false uses extend instead of Object.assign', () => {
+ expect(
+ transform(` `, {useBuiltIns: false})
+ ).toMatchSnapshot();
+ });
+});
diff --git a/packages/babel-plugin-react-jsx/__tests__/TransformJSXToReactJSX-test.js b/packages/babel-plugin-react-jsx/__tests__/TransformJSXToReactJSX-test.js
new file mode 100644
index 0000000000000..86ee84f1fa260
--- /dev/null
+++ b/packages/babel-plugin-react-jsx/__tests__/TransformJSXToReactJSX-test.js
@@ -0,0 +1,483 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+/* eslint-disable quotes */
+'use strict';
+
+const babel = require('@babel/core');
+const codeFrame = require('@babel/code-frame');
+const {wrap} = require('jest-snapshot-serializer-raw');
+
+function transform(input, options) {
+ return wrap(
+ babel.transform(input, {
+ configFile: false,
+ plugins: [
+ '@babel/plugin-syntax-jsx',
+ '@babel/plugin-transform-arrow-functions',
+ ...(options && options.development
+ ? [
+ '@babel/plugin-transform-react-jsx-source',
+ '@babel/plugin-transform-react-jsx-self',
+ ]
+ : []),
+ [
+ './packages/babel-plugin-react-jsx',
+ {
+ useBuiltIns: true,
+ useCreateElement: false,
+ ...options,
+ },
+ ],
+ ],
+ }).code
+ );
+}
+
+describe('transform react to jsx', () => {
+ it('fragment with no children', () => {
+ expect(transform(`var x = <>>`)).toMatchSnapshot();
+ });
+
+ it('fragments', () => {
+ expect(transform(`var x = <>
>`)).toMatchSnapshot();
+ });
+
+ it('fragments to set keys', () => {
+ expect(
+ transform(`var x = `)
+ ).toMatchSnapshot();
+ });
+
+ it('React.fragment to set keys and source', () => {
+ expect(
+ transform(`var x = `, {
+ development: true,
+ })
+ ).toMatchSnapshot();
+ });
+
+ it('fragments in dev mode (no key and source)', () => {
+ expect(
+ transform(`var x = <>
>`, {
+ development: true,
+ })
+ ).toMatchSnapshot();
+ });
+
+ it('nonStatic children', () => {
+ expect(
+ transform(
+ `var x = (
+
+ {[ , ]}
+
+ );
+ `,
+ {
+ development: true,
+ }
+ )
+ ).toMatchSnapshot();
+ });
+
+ it('static children', () => {
+ expect(
+ transform(
+ `var x = (
+
+
+ {[ , ]}
+
+ );
+ `,
+ {
+ development: true,
+ }
+ )
+ ).toMatchSnapshot();
+ });
+
+ it('uses jsxDEV instead of jsx in dev mode', () => {
+ expect(
+ transform(`var x = Hi `, {development: true})
+ ).toMatchSnapshot();
+ });
+
+ it('properly passes in source and self', () => {
+ expect(
+ transform(`var x =
;`, {development: true})
+ ).toMatchSnapshot();
+ });
+
+ it('should properly handle potentially null variables', () => {
+ expect(
+ transform(`
+ var foo = null;
+ var x =
;
+ `)
+ ).toMatchSnapshot();
+ });
+
+ it('properly handles keys', () => {
+ expect(
+ transform(`var x = (
+
+ );`)
+ ).toMatchSnapshot();
+ });
+
+ it('uses createElement when the key comes after a spread', () => {
+ expect(
+ transform(`var x = (
+
+ );`)
+ ).toMatchSnapshot();
+ });
+
+ it('uses jsx when the key comes before a spread', () => {
+ expect(
+ transform(`var x = (
+
+ );`)
+ ).toMatchSnapshot();
+ });
+
+ it('should properly handle comments adjacent to children', () => {
+ expect(
+ transform(`
+ var x = (
+
+ {/* A comment at the beginning */}
+ {/* A second comment at the beginning */}
+
+ {/* A nested comment */}
+
+ {/* A sandwiched comment */}
+
+ {/* A comment at the end */}
+ {/* A second comment at the end */}
+
+ );
+ `)
+ ).toMatchSnapshot();
+ });
+
+ it('adds appropriate new lines when using spread attribute', () => {
+ expect(transform(` `)).toMatchSnapshot();
+ });
+
+ it('arrow functions', () => {
+ expect(
+ transform(`
+ var foo = function () {
+ return () => ;
+ };
+
+ var bar = function () {
+ return () => ;
+ };
+
+ `)
+ ).toMatchSnapshot();
+ });
+
+ it('assignment', () => {
+ expect(
+ transform(`var div = `)
+ ).toMatchSnapshot();
+ });
+
+ it('concatenates adjacent string literals', () => {
+ expect(
+ transform(`
+ var x =
+
+ foo
+ {"bar"}
+ baz
+
+ buz
+ bang
+
+ qux
+ {null}
+ quack
+
+ `)
+ ).toMatchSnapshot();
+ });
+
+ it('should allow constructor as prop', () => {
+ expect(transform(` ;`)).toMatchSnapshot();
+ });
+
+ it('should allow deeper js namespacing', () => {
+ expect(
+ transform(` ;`)
+ ).toMatchSnapshot();
+ });
+
+ it('should allow elements as attributes', () => {
+ expect(transform(`
/>`)).toMatchSnapshot();
+ });
+
+ it('should allow js namespacing', () => {
+ expect(transform(` ;`)).toMatchSnapshot();
+ });
+
+ it('should allow nested fragments', () => {
+ expect(
+ transform(`
+
+ < >
+ <>
+ Hello
+ world
+ >
+ <>
+ Goodbye
+ world
+ >
+ >
+
+ `)
+ ).toMatchSnapshot();
+ });
+
+ it('should avoid wrapping in extra parens if not needed', () => {
+ expect(
+ transform(`
+ var x =
+
+
;
+
+ var x =
+ {props.children}
+
;
+
+ var x =
+ {props.children}
+ ;
+
+ var x =
+
+ ;
+ `)
+ ).toMatchSnapshot();
+ });
+
+ it('should convert simple tags', () => {
+ expect(transform(`var x =
;`)).toMatchSnapshot();
+ });
+
+ it('should convert simple text', () => {
+ expect(transform(`var x = text
;`)).toMatchSnapshot();
+ });
+
+ it('should disallow spread children', () => {
+ let _error;
+ const code = `{...children}
;`;
+ try {
+ transform(code);
+ } catch (error) {
+ _error = error;
+ }
+ expect(_error).toEqual(
+ new SyntaxError(
+ 'undefined: Spread children are not supported in React.' +
+ '\n' +
+ codeFrame.codeFrameColumns(
+ code,
+ {start: {line: 1, column: 6}},
+ {highlightCode: true}
+ )
+ )
+ );
+ });
+
+ it('should escape xhtml jsxattribute', () => {
+ expect(
+ transform(`
+
;
+
;
+
;
+ `)
+ ).toMatchSnapshot();
+ });
+
+ it('should escape xhtml jsxtext', () => {
+ /* eslint-disable no-irregular-whitespace */
+ expect(
+ transform(`
+ wow
;
+ wôw
;
+
+ w & w
;
+ w & w
;
+
+ w w
;
+ this should not parse as unicode: \u00a0
;
+ this should parse as nbsp:
;
+ this should parse as unicode: {'\u00a0 '}
;
+
+ w < w
;
+ `)
+ ).toMatchSnapshot();
+ /*eslint-enable */
+ });
+
+ it('should handle attributed elements', () => {
+ expect(
+ transform(`
+ var HelloMessage = React.createClass({
+ render: function() {
+ return Hello {this.props.name}
;
+ }
+ });
+
+ React.render(
+ Sebastian
+
+ } />, mountNode);
+ `)
+ ).toMatchSnapshot();
+ });
+
+ it('should handle has own property correctly', () => {
+ expect(
+ transform(`testing ;`)
+ ).toMatchSnapshot();
+ });
+
+ it('should have correct comma in nested children', () => {
+ expect(
+ transform(`
+ var x = ;
+ `)
+ ).toMatchSnapshot();
+ });
+
+ it('should insert commas after expressions before whitespace', () => {
+ expect(
+ transform(`
+ var x =
+
+
+ `)
+ ).toMatchSnapshot();
+ });
+
+ it('should not add quotes to identifier names', () => {
+ expect(
+ transform(`var e = ;`)
+ ).toMatchSnapshot();
+ });
+
+ it('should not strip nbsp even couple with other whitespace', () => {
+ expect(transform(`
;`)).toMatchSnapshot();
+ });
+
+ it('should not strip tags with a single child of nbsp', () => {
+ expect(transform(`
;`)).toMatchSnapshot();
+ });
+
+ it('should properly handle comments between props', () => {
+ expect(
+ transform(`
+ var x = (
+
+
+
+ );
+ `)
+ ).toMatchSnapshot();
+ });
+
+ it('should quote jsx attributes', () => {
+ expect(
+ transform(`Button `)
+ ).toMatchSnapshot();
+ });
+
+ it('should support xml namespaces if flag', () => {
+ expect(
+ transform(' ', {throwIfNamespace: false})
+ ).toMatchSnapshot();
+ });
+
+ it('should throw error namespaces if not flag', () => {
+ let _error;
+ const code = ` `;
+ try {
+ transform(code);
+ } catch (error) {
+ _error = error;
+ }
+ expect(_error).toEqual(
+ new SyntaxError(
+ "undefined: Namespace tags are not supported by default. React's " +
+ "JSX doesn't support namespace tags. You can turn on the " +
+ "'throwIfNamespace' flag to bypass this warning." +
+ '\n' +
+ codeFrame.codeFrameColumns(
+ code,
+ {start: {line: 1, column: 2}},
+ {highlightCode: true}
+ )
+ )
+ );
+ });
+
+ it('should transform known hyphenated tags', () => {
+ expect(transform(` `)).toMatchSnapshot();
+ });
+
+ it('wraps props in react spread for first spread attributes', () => {
+ expect(transform(` `)).toMatchSnapshot();
+ });
+
+ it('wraps props in react spread for last spread attributes', () => {
+ expect(transform(` `)).toMatchSnapshot();
+ });
+
+ it('wraps props in react spread for middle spread attributes', () => {
+ expect(transform(` `)).toMatchSnapshot();
+ });
+
+ it('useBuiltIns false uses extend instead of Object.assign', () => {
+ expect(
+ transform(` `, {useBuiltIns: false})
+ ).toMatchSnapshot();
+ });
+});
diff --git a/packages/babel-plugin-react-jsx/__tests__/__snapshots__/TransformJSXToReactCreateElement-test.js.snap b/packages/babel-plugin-react-jsx/__tests__/__snapshots__/TransformJSXToReactCreateElement-test.js.snap
new file mode 100644
index 0000000000000..a74c7e1d15e82
--- /dev/null
+++ b/packages/babel-plugin-react-jsx/__tests__/__snapshots__/TransformJSXToReactCreateElement-test.js.snap
@@ -0,0 +1,213 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`transform react to jsx React.Fragment to set keys and source 1`] = `
+var _jsxFileName = "";
+var x = React.createElement(React.Fragment, {
+ key: "foo",
+ __source: {
+ fileName: _jsxFileName,
+ lineNumber: 1
+ },
+ __self: this
+}, React.createElement("div", {
+ __source: {
+ fileName: _jsxFileName,
+ lineNumber: 1
+ },
+ __self: this
+}));
+`;
+
+exports[`transform react to jsx adds appropriate new lines when using spread attribute 1`] = `
+React.createElement(Component, Object.assign({}, props, {
+ sound: "moo"
+}));
+`;
+
+exports[`transform react to jsx arrow functions 1`] = `
+var foo = function () {
+ var _this = this;
+
+ return function () {
+ return React.createElement(_this, null);
+ };
+};
+
+var bar = function () {
+ var _this2 = this;
+
+ return function () {
+ return React.createElement(_this2.foo, null);
+ };
+};
+`;
+
+exports[`transform react to jsx assignment 1`] = `
+var div = React.createElement(Component, Object.assign({}, props, {
+ foo: "bar"
+}));
+`;
+
+exports[`transform react to jsx concatenates adjacent string literals 1`] = `var x = React.createElement("div", null, "foo", "bar", "baz", React.createElement("div", null, "buz bang"), "qux", null, "quack");`;
+
+exports[`transform react to jsx fragment with no children 1`] = `var x = React.createElement(React.Fragment, null);`;
+
+exports[`transform react to jsx normal fragments not to set key and source 1`] = `
+var _jsxFileName = "";
+var x = React.createElement(React.Fragment, null, React.createElement("div", {
+ __source: {
+ fileName: _jsxFileName,
+ lineNumber: 1
+ },
+ __self: this
+}));
+`;
+
+exports[`transform react to jsx should allow constructor as prop 1`] = `
+React.createElement(Component, {
+ constructor: "foo"
+});
+`;
+
+exports[`transform react to jsx should allow deeper js namespacing 1`] = `React.createElement(Namespace.DeepNamespace.Component, null);`;
+
+exports[`transform react to jsx should allow elements as attributes 1`] = `
+React.createElement("div", {
+ attr: React.createElement("div", null)
+});
+`;
+
+exports[`transform react to jsx should allow js namespacing 1`] = `React.createElement(Namespace.Component, null);`;
+
+exports[`transform react to jsx should allow nested fragments 1`] = `React.createElement("div", null, React.createElement(React.Fragment, null, React.createElement(React.Fragment, null, React.createElement("span", null, "Hello"), React.createElement("span", null, "world")), React.createElement(React.Fragment, null, React.createElement("span", null, "Goodbye"), React.createElement("span", null, "world"))));`;
+
+exports[`transform react to jsx should avoid wrapping in extra parens if not needed 1`] = `
+var x = React.createElement("div", null, React.createElement(Component, null));
+var x = React.createElement("div", null, props.children);
+var x = React.createElement(Composite, null, props.children);
+var x = React.createElement(Composite, null, React.createElement(Composite2, null));
+`;
+
+exports[`transform react to jsx should convert simple tags 1`] = `var x = React.createElement("div", null);`;
+
+exports[`transform react to jsx should convert simple text 1`] = `var x = React.createElement("div", null, "text");`;
+
+exports[`transform react to jsx should escape xhtml jsxattribute 1`] = `
+React.createElement("div", {
+ id: "w\\xF4w"
+});
+React.createElement("div", {
+ id: "w"
+});
+React.createElement("div", {
+ id: "w < w"
+});
+`;
+
+exports[`transform react to jsx should escape xhtml jsxtext 1`] = `
+React.createElement("div", null, "wow");
+React.createElement("div", null, "w\\xF4w");
+React.createElement("div", null, "w & w");
+React.createElement("div", null, "w & w");
+React.createElement("div", null, "w \\xA0 w");
+React.createElement("div", null, "this should not parse as unicode: \\xA0");
+React.createElement("div", null, "this should parse as nbsp: \\xA0 ");
+React.createElement("div", null, "this should parse as unicode: ", ' ');
+React.createElement("div", null, "w < w");
+`;
+
+exports[`transform react to jsx should handle attributed elements 1`] = `
+var HelloMessage = React.createClass({
+ render: function () {
+ return React.createElement("div", null, "Hello ", this.props.name);
+ }
+});
+React.render(React.createElement(HelloMessage, {
+ name: React.createElement("span", null, "Sebastian")
+}), mountNode);
+`;
+
+exports[`transform react to jsx should handle has own property correctly 1`] = `React.createElement("hasOwnProperty", null, "testing");`;
+
+exports[`transform react to jsx should have correct comma in nested children 1`] = `var x = React.createElement("div", null, React.createElement("div", null, React.createElement("br", null)), React.createElement(Component, null, foo, React.createElement("br", null), bar), React.createElement("br", null));`;
+
+exports[`transform react to jsx should insert commas after expressions before whitespace 1`] = `
+var x = React.createElement("div", {
+ attr1: "foo" + "bar",
+ attr2: "foo" + "bar" + "baz" + "bug",
+ attr3: "foo" + "bar" + "baz" + "bug",
+ attr4: "baz"
+});
+`;
+
+exports[`transform react to jsx should not add quotes to identifier names 1`] = `
+var e = React.createElement(F, {
+ aaa: true,
+ new: true,
+ const: true,
+ var: true,
+ default: true,
+ "foo-bar": true
+});
+`;
+
+exports[`transform react to jsx should not strip nbsp even couple with other whitespace 1`] = `React.createElement("div", null, "\\xA0 ");`;
+
+exports[`transform react to jsx should not strip tags with a single child of nbsp 1`] = `React.createElement("div", null, "\\xA0");`;
+
+exports[`transform react to jsx should properly handle comments adjacent to children 1`] = `var x = React.createElement("div", null, React.createElement("span", null), React.createElement("br", null));`;
+
+exports[`transform react to jsx should properly handle comments between props 1`] = `
+var x = React.createElement("div", {
+ /* a multi-line
+ comment */
+ attr1: "foo"
+}, React.createElement("span", {
+ // a double-slash comment
+ attr2: "bar"
+}));
+`;
+
+exports[`transform react to jsx should quote jsx attributes 1`] = `
+React.createElement("button", {
+ "data-value": "a value"
+}, "Button");
+`;
+
+exports[`transform react to jsx should support xml namespaces if flag 1`] = `
+React.createElement("f:image", {
+ "n:attr": true
+});
+`;
+
+exports[`transform react to jsx should transform known hyphenated tags 1`] = `React.createElement("font-face", null);`;
+
+exports[`transform react to jsx useBuiltIns false uses extend instead of Object.assign 1`] = `
+function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
+
+React.createElement(Component, _extends({
+ y: 2
+}, x));
+`;
+
+exports[`transform react to jsx wraps props in react spread for first spread attributes 1`] = `
+React.createElement(Component, Object.assign({}, x, {
+ y: 2,
+ z: true
+}));
+`;
+
+exports[`transform react to jsx wraps props in react spread for last spread attributes 1`] = `
+React.createElement(Component, Object.assign({
+ y: 2,
+ z: true
+}, x));
+`;
+
+exports[`transform react to jsx wraps props in react spread for middle spread attributes 1`] = `
+React.createElement(Component, Object.assign({
+ y: 2
+}, x, {
+ z: true
+}));
+`;
diff --git a/packages/babel-plugin-react-jsx/__tests__/__snapshots__/TransformJSXToReactJSX-test.js.snap b/packages/babel-plugin-react-jsx/__tests__/__snapshots__/TransformJSXToReactJSX-test.js.snap
new file mode 100644
index 0000000000000..e5b844b12fc0d
--- /dev/null
+++ b/packages/babel-plugin-react-jsx/__tests__/__snapshots__/TransformJSXToReactJSX-test.js.snap
@@ -0,0 +1,374 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`transform react to jsx React.fragment to set keys and source 1`] = `
+var _jsxFileName = "";
+var x = React.jsxDEV(React.Fragment, {}, "foo", false, {
+ fileName: _jsxFileName,
+ lineNumber: 1
+}, this);
+`;
+
+exports[`transform react to jsx adds appropriate new lines when using spread attribute 1`] = `
+React.jsx(Component, Object.assign({}, props, {
+ sound: "moo"
+}));
+`;
+
+exports[`transform react to jsx arrow functions 1`] = `
+var foo = function () {
+ var _this = this;
+
+ return function () {
+ return React.jsx(_this, {});
+ };
+};
+
+var bar = function () {
+ var _this2 = this;
+
+ return function () {
+ return React.jsx(_this2.foo, {});
+ };
+};
+`;
+
+exports[`transform react to jsx assignment 1`] = `
+var div = React.jsx(Component, Object.assign({}, props, {
+ foo: "bar"
+}));
+`;
+
+exports[`transform react to jsx concatenates adjacent string literals 1`] = `
+var x = React.jsxs("div", {
+ children: ["foo", "bar", "baz", React.jsx("div", {
+ children: "buz bang"
+ }), "qux", null, "quack"]
+});
+`;
+
+exports[`transform react to jsx fragment with no children 1`] = `var x = React.jsx(React.Fragment, {});`;
+
+exports[`transform react to jsx fragments 1`] = `
+var x = React.jsx(React.Fragment, {
+ children: React.jsx("div", {})
+});
+`;
+
+exports[`transform react to jsx fragments in dev mode (no key and source) 1`] = `
+var _jsxFileName = "";
+var x = React.jsxDEV(React.Fragment, {
+ children: React.jsxDEV("div", {}, undefined, false, {
+ fileName: _jsxFileName,
+ lineNumber: 1
+ }, this)
+}, undefined, false);
+`;
+
+exports[`transform react to jsx fragments to set keys 1`] = `var x = React.jsx(React.Fragment, {}, "foo");`;
+
+exports[`transform react to jsx nonStatic children 1`] = `
+var _jsxFileName = "";
+var x = React.jsxDEV("div", {
+ children: [React.jsxDEV("span", {}, '0', false, {
+ fileName: _jsxFileName,
+ lineNumber: 3
+ }, this), React.jsxDEV("span", {}, '1', false, {
+ fileName: _jsxFileName,
+ lineNumber: 3
+ }, this)]
+}, undefined, false, {
+ fileName: _jsxFileName,
+ lineNumber: 2
+}, this);
+`;
+
+exports[`transform react to jsx properly handles keys 1`] = `
+var x = React.jsxs("div", {
+ children: [React.jsx("div", {}, "1"), React.jsx("div", {
+ meow: "wolf"
+ }, "2"), React.jsx("div", {}, "3")]
+});
+`;
+
+exports[`transform react to jsx properly passes in source and self 1`] = `
+var _jsxFileName = "";
+var x = React.jsxDEV("div", {}, undefined, false, {
+ fileName: _jsxFileName,
+ lineNumber: 1
+}, this);
+`;
+
+exports[`transform react to jsx should allow constructor as prop 1`] = `
+React.jsx(Component, {
+ constructor: "foo"
+});
+`;
+
+exports[`transform react to jsx should allow deeper js namespacing 1`] = `React.jsx(Namespace.DeepNamespace.Component, {});`;
+
+exports[`transform react to jsx should allow elements as attributes 1`] = `
+React.jsx("div", {
+ attr: React.jsx("div", {})
+});
+`;
+
+exports[`transform react to jsx should allow js namespacing 1`] = `React.jsx(Namespace.Component, {});`;
+
+exports[`transform react to jsx should allow nested fragments 1`] = `
+React.jsx("div", {
+ children: React.jsxs(React.Fragment, {
+ children: [React.jsxs(React.Fragment, {
+ children: [React.jsx("span", {
+ children: "Hello"
+ }), React.jsx("span", {
+ children: "world"
+ })]
+ }), React.jsxs(React.Fragment, {
+ children: [React.jsx("span", {
+ children: "Goodbye"
+ }), React.jsx("span", {
+ children: "world"
+ })]
+ })]
+ })
+});
+`;
+
+exports[`transform react to jsx should avoid wrapping in extra parens if not needed 1`] = `
+var x = React.jsx("div", {
+ children: React.jsx(Component, {})
+});
+var x = React.jsx("div", {
+ children: props.children
+});
+var x = React.jsx(Composite, {
+ children: props.children
+});
+var x = React.jsx(Composite, {
+ children: React.jsx(Composite2, {})
+});
+`;
+
+exports[`transform react to jsx should convert simple tags 1`] = `var x = React.jsx("div", {});`;
+
+exports[`transform react to jsx should convert simple text 1`] = `
+var x = React.jsx("div", {
+ children: "text"
+});
+`;
+
+exports[`transform react to jsx should escape xhtml jsxattribute 1`] = `
+React.jsx("div", {
+ id: "w\\xF4w"
+});
+React.jsx("div", {
+ id: "w"
+});
+React.jsx("div", {
+ id: "w < w"
+});
+`;
+
+exports[`transform react to jsx should escape xhtml jsxtext 1`] = `
+React.jsx("div", {
+ children: "wow"
+});
+React.jsx("div", {
+ children: "w\\xF4w"
+});
+React.jsx("div", {
+ children: "w & w"
+});
+React.jsx("div", {
+ children: "w & w"
+});
+React.jsx("div", {
+ children: "w \\xA0 w"
+});
+React.jsx("div", {
+ children: "this should not parse as unicode: \\xA0"
+});
+React.jsx("div", {
+ children: "this should parse as nbsp: \\xA0 "
+});
+React.jsxs("div", {
+ children: ["this should parse as unicode: ", ' ']
+});
+React.jsx("div", {
+ children: "w < w"
+});
+`;
+
+exports[`transform react to jsx should handle attributed elements 1`] = `
+var HelloMessage = React.createClass({
+ render: function () {
+ return React.jsxs("div", {
+ children: ["Hello ", this.props.name]
+ });
+ }
+});
+React.render(React.jsx(HelloMessage, {
+ name: React.jsx("span", {
+ children: "Sebastian"
+ })
+}), mountNode);
+`;
+
+exports[`transform react to jsx should handle has own property correctly 1`] = `
+React.jsx("hasOwnProperty", {
+ children: "testing"
+});
+`;
+
+exports[`transform react to jsx should have correct comma in nested children 1`] = `
+var x = React.jsxs("div", {
+ children: [React.jsx("div", {
+ children: React.jsx("br", {})
+ }), React.jsxs(Component, {
+ children: [foo, React.jsx("br", {}), bar]
+ }), React.jsx("br", {})]
+});
+`;
+
+exports[`transform react to jsx should insert commas after expressions before whitespace 1`] = `
+var x = React.jsx("div", {
+ attr1: "foo" + "bar",
+ attr2: "foo" + "bar" + "baz" + "bug",
+ attr3: "foo" + "bar" + "baz" + "bug",
+ attr4: "baz"
+});
+`;
+
+exports[`transform react to jsx should not add quotes to identifier names 1`] = `
+var e = React.jsx(F, {
+ aaa: true,
+ new: true,
+ const: true,
+ var: true,
+ default: true,
+ "foo-bar": true
+});
+`;
+
+exports[`transform react to jsx should not strip nbsp even couple with other whitespace 1`] = `
+React.jsx("div", {
+ children: "\\xA0 "
+});
+`;
+
+exports[`transform react to jsx should not strip tags with a single child of nbsp 1`] = `
+React.jsx("div", {
+ children: "\\xA0"
+});
+`;
+
+exports[`transform react to jsx should properly handle comments adjacent to children 1`] = `
+var x = React.jsxs("div", {
+ children: [React.jsx("span", {}), React.jsx("br", {})]
+});
+`;
+
+exports[`transform react to jsx should properly handle comments between props 1`] = `
+var x = React.jsx("div", {
+ /* a multi-line
+ comment */
+ attr1: "foo",
+ children: React.jsx("span", {
+ // a double-slash comment
+ attr2: "bar"
+ })
+});
+`;
+
+exports[`transform react to jsx should properly handle potentially null variables 1`] = `
+var foo = null;
+var x = React.jsx("div", Object.assign({}, foo));
+`;
+
+exports[`transform react to jsx should quote jsx attributes 1`] = `
+React.jsx("button", {
+ "data-value": "a value",
+ children: "Button"
+});
+`;
+
+exports[`transform react to jsx should support xml namespaces if flag 1`] = `
+React.jsx("f:image", {
+ "n:attr": true
+});
+`;
+
+exports[`transform react to jsx should transform known hyphenated tags 1`] = `React.jsx("font-face", {});`;
+
+exports[`transform react to jsx static children 1`] = `
+var _jsxFileName = "";
+var x = React.jsxDEV("div", {
+ children: [React.jsxDEV("span", {}, undefined, false, {
+ fileName: _jsxFileName,
+ lineNumber: 3
+ }, this), [React.jsxDEV("span", {}, '0', false, {
+ fileName: _jsxFileName,
+ lineNumber: 4
+ }, this), React.jsxDEV("span", {}, '1', false, {
+ fileName: _jsxFileName,
+ lineNumber: 4
+ }, this)]]
+}, undefined, true, {
+ fileName: _jsxFileName,
+ lineNumber: 2
+}, this);
+`;
+
+exports[`transform react to jsx useBuiltIns false uses extend instead of Object.assign 1`] = `
+function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
+
+React.jsx(Component, _extends({
+ y: 2
+}, x));
+`;
+
+exports[`transform react to jsx uses createElement when the key comes after a spread 1`] = `
+var x = React.createElement("div", Object.assign({}, props, {
+ key: "1",
+ foo: "bar"
+}));
+`;
+
+exports[`transform react to jsx uses jsx when the key comes before a spread 1`] = `
+var x = React.jsx("div", Object.assign({}, props, {
+ foo: "bar"
+}), "1");
+`;
+
+exports[`transform react to jsx uses jsxDEV instead of jsx in dev mode 1`] = `
+var _jsxFileName = "";
+var x = React.jsxDEV("span", {
+ propOne: "one",
+ children: "Hi"
+}, undefined, false, {
+ fileName: _jsxFileName,
+ lineNumber: 1
+}, this);
+`;
+
+exports[`transform react to jsx wraps props in react spread for first spread attributes 1`] = `
+React.jsx(Component, Object.assign({}, x, {
+ y: 2,
+ z: true
+}));
+`;
+
+exports[`transform react to jsx wraps props in react spread for last spread attributes 1`] = `
+React.jsx(Component, Object.assign({
+ y: 2,
+ z: true
+}, x));
+`;
+
+exports[`transform react to jsx wraps props in react spread for middle spread attributes 1`] = `
+React.jsx(Component, Object.assign({
+ y: 2
+}, x, {
+ z: true
+}));
+`;
diff --git a/packages/babel-plugin-react-jsx/index.js b/packages/babel-plugin-react-jsx/index.js
new file mode 100644
index 0000000000000..7401bda105727
--- /dev/null
+++ b/packages/babel-plugin-react-jsx/index.js
@@ -0,0 +1,3 @@
+'use strict';
+
+module.exports = require('./src/TransformJSXToReactBabelPlugin');
diff --git a/packages/babel-plugin-react-jsx/npm/index.js b/packages/babel-plugin-react-jsx/npm/index.js
new file mode 100644
index 0000000000000..af4ea6a44bb9a
--- /dev/null
+++ b/packages/babel-plugin-react-jsx/npm/index.js
@@ -0,0 +1,7 @@
+'use strict';
+
+if (process.env.NODE_ENV === 'production') {
+ module.exports = require('./cjs/react-jsx-babel.production.min.js');
+} else {
+ module.exports = require('./cjs/react-jsx-babel.development.js');
+}
diff --git a/packages/babel-plugin-react-jsx/package.json b/packages/babel-plugin-react-jsx/package.json
new file mode 100644
index 0000000000000..41243452d4f17
--- /dev/null
+++ b/packages/babel-plugin-react-jsx/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "babel-plugin-react-jsx",
+ "version": "0.1.0",
+ "private": true,
+ "description": "@babel/plugin-transform-react-jsx",
+ "main": "index.js",
+ "dependencies": {
+ "esutils": "^2.0.0"
+
+ },
+ "files": [
+ "README.md",
+ "index.js",
+ "build-info.json",
+ "cjs/",
+ "umd/"
+ ]
+}
diff --git a/packages/babel-plugin-react-jsx/src/TransformJSXToReactBabelPlugin.js b/packages/babel-plugin-react-jsx/src/TransformJSXToReactBabelPlugin.js
new file mode 100644
index 0000000000000..960f75a3a8ecb
--- /dev/null
+++ b/packages/babel-plugin-react-jsx/src/TransformJSXToReactBabelPlugin.js
@@ -0,0 +1,611 @@
+// MIT License
+
+// Copyright (c) 2014-present Sebastian McKenzie and other contributors
+
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+// © 2019 GitHub, Inc.
+'use strict';
+
+const esutils = require('esutils');
+
+function helper(babel, opts) {
+ const {types: t} = babel;
+
+ const visitor = {};
+
+ visitor.JSXNamespacedName = function(path, state) {
+ const throwIfNamespace =
+ state.opts.throwIfNamespace === undefined
+ ? true
+ : !!state.opts.throwIfNamespace;
+ if (throwIfNamespace) {
+ throw path.buildCodeFrameError(
+ `Namespace tags are not supported by default. React's JSX doesn't support namespace tags. \
+You can turn on the 'throwIfNamespace' flag to bypass this warning.`,
+ );
+ }
+ };
+
+ visitor.JSXSpreadChild = function(path) {
+ throw path.buildCodeFrameError(
+ 'Spread children are not supported in React.',
+ );
+ };
+
+ visitor.JSXElement = {
+ exit(path, file) {
+ let callExpr;
+ if (file.opts.useCreateElement || shouldUseCreateElement(path)) {
+ callExpr = buildCreateElementCall(path, file);
+ } else {
+ callExpr = buildJSXElementCall(path, file);
+ }
+
+ if (callExpr) {
+ path.replaceWith(t.inherits(callExpr, path.node));
+ }
+ },
+ };
+
+ visitor.JSXFragment = {
+ exit(path, file) {
+ if (opts.compat) {
+ throw path.buildCodeFrameError(
+ 'Fragment tags are only supported in React 16 and up.',
+ );
+ }
+ let callExpr;
+ if (file.opts.useCreateElement) {
+ callExpr = buildCreateElementFragmentCall(path, file);
+ } else {
+ callExpr = buildJSXFragmentCall(path, file);
+ }
+
+ if (callExpr) {
+ path.replaceWith(t.inherits(callExpr, path.node));
+ }
+ },
+ };
+
+ return visitor;
+
+ function convertJSXIdentifier(node, parent) {
+ if (t.isJSXIdentifier(node)) {
+ if (node.name === 'this' && t.isReferenced(node, parent)) {
+ return t.thisExpression();
+ } else if (esutils.keyword.isIdentifierNameES6(node.name)) {
+ node.type = 'Identifier';
+ } else {
+ return t.stringLiteral(node.name);
+ }
+ } else if (t.isJSXMemberExpression(node)) {
+ return t.memberExpression(
+ convertJSXIdentifier(node.object, node),
+ convertJSXIdentifier(node.property, node),
+ );
+ } else if (t.isJSXNamespacedName(node)) {
+ /**
+ * If there is flag "throwIfNamespace"
+ * print XMLNamespace like string literal
+ */
+ return t.stringLiteral(`${node.namespace.name}:${node.name.name}`);
+ }
+
+ return node;
+ }
+
+ function convertAttributeValue(node) {
+ if (t.isJSXExpressionContainer(node)) {
+ return node.expression;
+ } else {
+ return node;
+ }
+ }
+
+ function convertAttribute(node) {
+ const value = convertAttributeValue(node.value || t.booleanLiteral(true));
+
+ if (t.isStringLiteral(value) && !t.isJSXExpressionContainer(node.value)) {
+ value.value = value.value.replace(/\n\s+/g, ' ');
+
+ // "raw" JSXText should not be used from a StringLiteral because it needs to be escaped.
+ if (value.extra && value.extra.raw) {
+ delete value.extra.raw;
+ }
+ }
+
+ if (t.isJSXNamespacedName(node.name)) {
+ node.name = t.stringLiteral(
+ node.name.namespace.name + ':' + node.name.name.name,
+ );
+ } else if (esutils.keyword.isIdentifierNameES6(node.name.name)) {
+ node.name.type = 'Identifier';
+ } else {
+ node.name = t.stringLiteral(node.name.name);
+ }
+
+ return t.inherits(t.objectProperty(node.name, value), node);
+ }
+
+ // We want to use React.createElement, even in the case of
+ // jsx, for
to distinguish it
+ // from
. This is an intermediary
+ // step while we deprecate key spread from props. Afterwards,
+ // we will remove createElement entirely
+ function shouldUseCreateElement(path) {
+ const openingPath = path.get('openingElement');
+ const attributes = openingPath.node.attributes;
+
+ let seenPropsSpread = false;
+ for (let i = 0; i < attributes.length; i++) {
+ const attr = attributes[i];
+ if (
+ seenPropsSpread &&
+ t.isJSXAttribute(attr) &&
+ attr.name.name === 'key'
+ ) {
+ return true;
+ } else if (t.isJSXSpreadAttribute(attr)) {
+ seenPropsSpread = true;
+ }
+ }
+ return false;
+ }
+
+ // Builds JSX into:
+ // Production: React.jsx(type, arguments, key)
+ // Development: React.jsxDEV(type, arguments, key, isStaticChildren, source, self)
+ function buildJSXElementCall(path, file) {
+ if (opts.filter && !opts.filter(path.node, file)) {
+ return;
+ }
+
+ const openingPath = path.get('openingElement');
+ openingPath.parent.children = t.react.buildChildren(openingPath.parent);
+
+ const tagExpr = convertJSXIdentifier(
+ openingPath.node.name,
+ openingPath.node,
+ );
+ const args = [];
+
+ let tagName;
+ if (t.isIdentifier(tagExpr)) {
+ tagName = tagExpr.name;
+ } else if (t.isLiteral(tagExpr)) {
+ tagName = tagExpr.value;
+ }
+
+ const state = {
+ tagExpr: tagExpr,
+ tagName: tagName,
+ args: args,
+ };
+
+ if (opts.pre) {
+ opts.pre(state, file);
+ }
+
+ let attribs = [];
+ let key;
+ let source;
+ let self;
+
+ // for React.jsx, key, __source (dev), and __self (dev) is passed in as
+ // a separate argument rather than in the args object. We go through the
+ // props and filter out these three keywords so we can pass them in
+ // as separate arguments later
+ for (let i = 0; i < openingPath.node.attributes.length; i++) {
+ const attr = openingPath.node.attributes[i];
+ if (t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name)) {
+ if (attr.name.name === 'key') {
+ key = convertAttribute(attr).value;
+ } else if (attr.name.name === '__source') {
+ source = convertAttribute(attr).value;
+ } else if (attr.name.name === '__self') {
+ self = convertAttribute(attr).value;
+ } else {
+ attribs.push(attr);
+ }
+ } else {
+ attribs.push(attr);
+ }
+ }
+
+ if (attribs.length || path.node.children.length) {
+ attribs = buildJSXOpeningElementAttributes(
+ attribs,
+ file,
+ path.node.children,
+ );
+ } else {
+ // attributes should never be null
+ attribs = t.objectExpression([]);
+ }
+
+ args.push(attribs);
+
+ if (!file.opts.development) {
+ if (key !== undefined) {
+ args.push(key);
+ }
+ } else {
+ // isStaticChildren, __source, and __self are only used in development
+ args.push(
+ key === undefined ? t.identifier('undefined') : key,
+ t.booleanLiteral(path.node.children.length > 1),
+ source === undefined ? t.identifier('undefined') : source,
+ self === undefined ? t.identifier('undefined') : self,
+ );
+ }
+
+ if (opts.post) {
+ opts.post(state, file);
+ }
+ return (
+ state.call ||
+ t.callExpression(
+ path.node.children.length > 1 ? state.staticCallee : state.callee,
+ args,
+ )
+ );
+ }
+
+ // Builds props for React.jsx. This function adds children into the props
+ // and ensures that props is always an object
+ function buildJSXOpeningElementAttributes(attribs, file, children) {
+ let _props = [];
+ const objs = [];
+
+ const useBuiltIns = file.opts.useBuiltIns || false;
+ if (typeof useBuiltIns !== 'boolean') {
+ throw new Error(
+ 'transform-react-jsx currently only accepts a boolean option for ' +
+ 'useBuiltIns (defaults to false)',
+ );
+ }
+
+ while (attribs.length) {
+ const prop = attribs.shift();
+ if (t.isJSXSpreadAttribute(prop)) {
+ _props = pushProps(_props, objs);
+ objs.push(prop.argument);
+ } else {
+ _props.push(convertAttribute(prop));
+ }
+ }
+
+ // In React.JSX, children is no longer a separate argument, but passed in
+ // through the argument object
+ if (children && children.length > 0) {
+ if (children.length === 1) {
+ _props.push(t.objectProperty(t.identifier('children'), children[0]));
+ } else {
+ _props.push(
+ t.objectProperty(
+ t.identifier('children'),
+ t.arrayExpression(children),
+ ),
+ );
+ }
+ }
+
+ pushProps(_props, objs);
+
+ if (objs.length === 1) {
+ // only one object
+ if (!t.isObjectExpression(objs[0])) {
+ // if the prop object isn't an object, use Object.assign or _extends
+ // to ensure that the prop will always be an object (as opposed to a variable
+ // that could be null at some point)
+ const expressionHelper = useBuiltIns
+ ? t.memberExpression(t.identifier('Object'), t.identifier('assign'))
+ : file.addHelper('extends');
+
+ attribs = t.callExpression(expressionHelper, [
+ t.objectExpression([]),
+ objs[0],
+ ]);
+ } else {
+ attribs = objs[0];
+ }
+ } else {
+ // looks like we have multiple objects
+ if (!t.isObjectExpression(objs[0])) {
+ objs.unshift(t.objectExpression([]));
+ }
+
+ const expressionHelper = useBuiltIns
+ ? t.memberExpression(t.identifier('Object'), t.identifier('assign'))
+ : file.addHelper('extends');
+
+ // spread it
+ attribs = t.callExpression(expressionHelper, objs);
+ }
+
+ return attribs;
+ }
+
+ // Builds JSX Fragment <>> into
+ // Production: React.jsx(type, arguments)
+ // Development: React.jsxDEV(type, { children})
+ function buildJSXFragmentCall(path, file) {
+ if (opts.filter && !opts.filter(path.node, file)) {
+ return;
+ }
+
+ const openingPath = path.get('openingElement');
+ openingPath.parent.children = t.react.buildChildren(openingPath.parent);
+
+ const args = [];
+ const tagName = null;
+ const tagExpr = file.get('jsxFragIdentifier')();
+
+ const state = {
+ tagExpr: tagExpr,
+ tagName: tagName,
+ args: args,
+ };
+
+ if (opts.pre) {
+ opts.pre(state, file);
+ }
+
+ let childrenNode;
+ if (path.node.children.length > 0) {
+ if (path.node.children.length === 1) {
+ childrenNode = path.node.children[0];
+ } else {
+ childrenNode = t.arrayExpression(path.node.children);
+ }
+ }
+
+ args.push(
+ t.objectExpression(
+ childrenNode !== undefined
+ ? [t.objectProperty(t.identifier('children'), childrenNode)]
+ : [],
+ ),
+ );
+
+ if (file.opts.development) {
+ args.push(
+ t.identifier('undefined'),
+ t.booleanLiteral(path.node.children.length > 1),
+ );
+ }
+
+ if (opts.post) {
+ opts.post(state, file);
+ }
+
+ return (
+ state.call ||
+ t.callExpression(
+ path.node.children.length > 1 ? state.staticCallee : state.callee,
+ args,
+ )
+ );
+ }
+
+ // Builds JSX into:
+ // Production: React.createElement(type, arguments, children)
+ // Development: React.createElement(type, arguments, children, source, self)
+ function buildCreateElementCall(path, file) {
+ if (opts.filter && !opts.filter(path.node, file)) {
+ return;
+ }
+
+ const openingPath = path.get('openingElement');
+ openingPath.parent.children = t.react.buildChildren(openingPath.parent);
+
+ const tagExpr = convertJSXIdentifier(
+ openingPath.node.name,
+ openingPath.node,
+ );
+ const args = [];
+
+ let tagName;
+ if (t.isIdentifier(tagExpr)) {
+ tagName = tagExpr.name;
+ } else if (t.isLiteral(tagExpr)) {
+ tagName = tagExpr.value;
+ }
+
+ const state = {
+ tagExpr: tagExpr,
+ tagName: tagName,
+ args: args,
+ };
+
+ if (opts.pre) {
+ opts.pre(state, file);
+ }
+
+ let attribs = openingPath.node.attributes;
+ if (attribs.length) {
+ attribs = buildCreateElementOpeningElementAttributes(attribs, file);
+ } else {
+ attribs = t.nullLiteral();
+ }
+
+ args.push(attribs, ...path.node.children);
+
+ if (opts.post) {
+ opts.post(state, file);
+ }
+
+ return state.call || t.callExpression(state.oldCallee, args);
+ }
+
+ function pushProps(_props, objs) {
+ if (!_props.length) {
+ return _props;
+ }
+
+ objs.push(t.objectExpression(_props));
+ return [];
+ }
+
+ /**
+ * The logic for this is quite terse. It's because we need to
+ * support spread elements. We loop over all attributes,
+ * breaking on spreads, we then push a new object containing
+ * all prior attributes to an array for later processing.
+ */
+ function buildCreateElementOpeningElementAttributes(attribs, file) {
+ let _props = [];
+ const objs = [];
+
+ const useBuiltIns = file.opts.useBuiltIns || false;
+ if (typeof useBuiltIns !== 'boolean') {
+ throw new Error(
+ 'transform-react-jsx currently only accepts a boolean option for ' +
+ 'useBuiltIns (defaults to false)',
+ );
+ }
+
+ while (attribs.length) {
+ const prop = attribs.shift();
+ if (t.isJSXSpreadAttribute(prop)) {
+ _props = pushProps(_props, objs);
+ objs.push(prop.argument);
+ } else {
+ const attr = convertAttribute(prop);
+ _props.push(attr);
+ }
+ }
+
+ pushProps(_props, objs);
+
+ if (objs.length === 1) {
+ // only one object
+ attribs = objs[0];
+ } else {
+ // looks like we have multiple objects
+ if (!t.isObjectExpression(objs[0])) {
+ objs.unshift(t.objectExpression([]));
+ }
+
+ const expressionHelper = useBuiltIns
+ ? t.memberExpression(t.identifier('Object'), t.identifier('assign'))
+ : file.addHelper('extends');
+
+ // spread it
+ attribs = t.callExpression(expressionHelper, objs);
+ }
+
+ return attribs;
+ }
+
+ function buildCreateElementFragmentCall(path, file) {
+ if (opts.filter && !opts.filter(path.node, file)) {
+ return;
+ }
+
+ const openingPath = path.get('openingElement');
+ openingPath.parent.children = t.react.buildChildren(openingPath.parent);
+
+ const args = [];
+ const tagName = null;
+ const tagExpr = file.get('jsxFragIdentifier')();
+
+ const state = {
+ tagExpr: tagExpr,
+ tagName: tagName,
+ args: args,
+ };
+
+ if (opts.pre) {
+ opts.pre(state, file);
+ }
+
+ // no attributes are allowed with <> syntax
+ args.push(t.nullLiteral(), ...path.node.children);
+
+ if (opts.post) {
+ opts.post(state, file);
+ }
+
+ return state.call || t.callExpression(state.oldCallee, args);
+ }
+}
+
+module.exports = function(babel) {
+ const {types: t} = babel;
+
+ const createIdentifierParser = id => () => {
+ return id
+ .split('.')
+ .map(name => t.identifier(name))
+ .reduce((object, property) => t.memberExpression(object, property));
+ };
+
+ const visitor = helper(babel, {
+ pre(state) {
+ const tagName = state.tagName;
+ const args = state.args;
+ if (t.react.isCompatTag(tagName)) {
+ args.push(t.stringLiteral(tagName));
+ } else {
+ args.push(state.tagExpr);
+ }
+ },
+
+ post(state, pass) {
+ state.callee = pass.get('jsxIdentifier')();
+ state.staticCallee = pass.get('jsxStaticIdentifier')();
+ state.oldCallee = pass.get('oldJSXIdentifier')();
+ },
+ });
+
+ visitor.Program = {
+ enter(path, state) {
+ state.set(
+ 'oldJSXIdentifier',
+ createIdentifierParser('React.createElement'),
+ );
+ state.set(
+ 'jsxIdentifier',
+ createIdentifierParser(
+ state.opts.development ? 'React.jsxDEV' : 'React.jsx',
+ ),
+ );
+ state.set(
+ 'jsxStaticIdentifier',
+ createIdentifierParser(
+ state.opts.development ? 'React.jsxDEV' : 'React.jsxs',
+ ),
+ );
+ state.set('jsxFragIdentifier', createIdentifierParser('React.Fragment'));
+ },
+ };
+
+ visitor.JSXAttribute = function(path) {
+ if (t.isJSXElement(path.node.value)) {
+ path.node.value = t.jsxExpressionContainer(path.node.value);
+ }
+ };
+
+ return {
+ name: 'transform-react-jsx',
+ visitor,
+ };
+};
diff --git a/packages/react-events/src/dom/testing-library/domEnvironment.js b/packages/react-events/src/dom/testing-library/domEnvironment.js
index f0a65576a94ce..160d6183d0563 100644
--- a/packages/react-events/src/dom/testing-library/domEnvironment.js
+++ b/packages/react-events/src/dom/testing-library/domEnvironment.js
@@ -13,12 +13,17 @@
* Change environment support for PointerEvent.
*/
+const emptyFunction = function() {};
+
export function hasPointerEvent() {
return global != null && global.PointerEvent != null;
}
export function setPointerEvent(bool) {
- global.PointerEvent = bool ? function() {} : undefined;
+ const mock = bool ? emptyFunction : undefined;
+ global.PointerEvent = mock;
+ global.HTMLElement.prototype.setPointerCapture = mock;
+ global.HTMLElement.prototype.releasePointerCapture = mock;
}
/**
diff --git a/packages/react-events/src/dom/testing-library/domEventSequences.js b/packages/react-events/src/dom/testing-library/domEventSequences.js
index ebda2ec05779d..6272cd0da1a87 100644
--- a/packages/react-events/src/dom/testing-library/domEventSequences.js
+++ b/packages/react-events/src/dom/testing-library/domEventSequences.js
@@ -138,12 +138,18 @@ export function pointermove(target, payload) {
const dispatch = arg => target.dispatchEvent(arg);
const pointerType = getPointerType(payload);
if (hasPointerEvent()) {
- dispatch(domEvents.pointermove(payload));
- }
- if (pointerType === 'mouse') {
- dispatch(domEvents.mousemove(payload));
+ dispatch(
+ domEvents.pointermove({
+ pressure: pointerType === 'touch' ? 1 : 0.5,
+ ...payload,
+ }),
+ );
} else {
- dispatch(domEvents.touchmove(payload));
+ if (pointerType === 'mouse') {
+ dispatch(domEvents.mousemove(payload));
+ } else {
+ dispatch(domEvents.touchmove(payload));
+ }
}
}
diff --git a/packages/react-events/src/dom/testing-library/domEvents.js b/packages/react-events/src/dom/testing-library/domEvents.js
index 876ba4bd66d4b..e7b09c92c5ac9 100644
--- a/packages/react-events/src/dom/testing-library/domEvents.js
+++ b/packages/react-events/src/dom/testing-library/domEvents.js
@@ -64,6 +64,7 @@ function createPointerEvent(
altKey = false,
buttons = buttonsType.none,
ctrlKey = false,
+ detail = 1,
height,
metaKey = false,
movementX = 0,
@@ -76,6 +77,8 @@ function createPointerEvent(
pressure = 0,
preventDefault = emptyFunction,
pointerType = 'mouse',
+ screenX,
+ screenY,
shiftKey = false,
tangentialPressure = 0,
tiltX = 0,
@@ -87,6 +90,7 @@ function createPointerEvent(
} = {},
) {
const modifierState = {altKey, ctrlKey, metaKey, shiftKey};
+ const isMouse = pointerType === 'mouse';
return createEvent(type, {
altKey,
@@ -94,15 +98,11 @@ function createPointerEvent(
clientX: x,
clientY: y,
ctrlKey,
+ detail,
getModifierState(keyArg) {
createGetModifierState(keyArg, modifierState);
},
- height:
- pointerType === 'mouse'
- ? 1
- : height != null
- ? height
- : defaultPointerSize,
+ height: isMouse ? 1 : height != null ? height : defaultPointerSize,
metaKey,
movementX,
movementY,
@@ -114,15 +114,16 @@ function createPointerEvent(
pointerType,
pressure,
preventDefault,
- screenX: x,
- screenY: y + defaultBrowserChromeSize,
+ releasePointerCapture: emptyFunction,
+ screenX: screenX === 0 ? screenX : x,
+ screenY: screenY === 0 ? screenY : y + defaultBrowserChromeSize,
+ setPointerCapture: emptyFunction,
shiftKey,
tangentialPressure,
tiltX,
tiltY,
twist,
- width:
- pointerType === 'mouse' ? 1 : width != null ? width : defaultPointerSize,
+ width: isMouse ? 1 : width != null ? width : defaultPointerSize,
});
}
@@ -158,6 +159,7 @@ function createMouseEvent(
altKey = false,
buttons = buttonsType.none,
ctrlKey = false,
+ detail = 1,
metaKey = false,
movementX = 0,
movementY = 0,
@@ -166,81 +168,107 @@ function createMouseEvent(
pageX,
pageY,
preventDefault = emptyFunction,
+ screenX,
+ screenY,
shiftKey = false,
x = 0,
y = 0,
} = {},
- virtual = false,
) {
const modifierState = {altKey, ctrlKey, metaKey, shiftKey};
return createEvent(type, {
altKey,
buttons,
- clientX: virtual ? 0 : x,
- clientY: virtual ? 0 : y,
+ clientX: x,
+ clientY: y,
ctrlKey,
- detail: virtual ? 0 : 1,
+ detail,
getModifierState(keyArg) {
createGetModifierState(keyArg, modifierState);
},
metaKey,
- movementX: virtual ? 0 : movementX,
- movementY: virtual ? 0 : movementY,
- offsetX: virtual ? 0 : offsetX,
- offsetY: virtual ? 0 : offsetY,
- pageX: virtual ? 0 : pageX || x,
- pageY: virtual ? 0 : pageY || y,
+ movementX,
+ movementY,
+ offsetX,
+ offsetY,
+ pageX: pageX || x,
+ pageY: pageY || y,
preventDefault,
- screenX: virtual ? 0 : x,
- screenY: virtual ? 0 : y + defaultBrowserChromeSize,
+ screenX: screenX === 0 ? screenX : x,
+ screenY: screenY === 0 ? screenY : y + defaultBrowserChromeSize,
shiftKey,
});
}
-function createTouchEvent(
- type,
- {
- altKey = false,
- ctrlKey = false,
- height = defaultPointerSize,
- metaKey = false,
- pageX,
- pageY,
- pointerId = 1,
- preventDefault = emptyFunction,
- shiftKey = false,
- twist = 0,
- width = defaultPointerSize,
- x = 0,
- y = 0,
- } = {},
-) {
- const touch = {
- clientX: x,
- clientY: y,
- force: 1,
- identifier: pointerId,
- pageX: pageX || x,
- pageY: pageY || y,
- radiusX: width / 2,
- radiusY: height / 2,
- rotationAngle: twist,
- screenX: x,
- screenY: y + defaultBrowserChromeSize,
- };
+function createTouchEvent(type, payload) {
+ const touchesPayload = Array.isArray(payload) ? payload : [payload];
+ const firstTouch = touchesPayload[0];
+ let altKey = false;
+ let ctrlKey = false;
+ let metaKey = false;
+ let preventDefault = emptyFunction;
+ let shiftKey = false;
+ if (firstTouch != null) {
+ if (firstTouch.altKey != null) {
+ altKey = firstTouch.altKey;
+ }
+ if (firstTouch.ctrlKey != null) {
+ ctrlKey = firstTouch.ctrlKey;
+ }
+ if (firstTouch.metaKey != null) {
+ metaKey = firstTouch.metaKey;
+ }
+ if (firstTouch.preventDefault != null) {
+ preventDefault = firstTouch.preventDefault;
+ }
+ if (firstTouch.shiftKey != null) {
+ shiftKey = firstTouch.shiftKey;
+ }
+ }
+
+ const touches = touchesPayload.map(
+ ({
+ height = defaultPointerSize,
+ pageX,
+ pageY,
+ pointerId = 1,
+ twist = 0,
+ width = defaultPointerSize,
+ x = 0,
+ y = 0,
+ } = {}) => {
+ return {
+ clientX: x,
+ clientY: y,
+ force: 1,
+ identifier: pointerId,
+ pageX: pageX || x,
+ pageY: pageY || y,
+ radiusX: width / 2,
+ radiusY: height / 2,
+ rotationAngle: twist,
+ screenX: x,
+ screenY: y + defaultBrowserChromeSize,
+ };
+ },
+ );
- const activeTouch = type !== 'touchend' ? [touch] : null;
+ const activeTouches = type !== 'touchend' ? touches : null;
return createEvent(type, {
altKey,
- changedTouches: [touch],
+ changedTouches: touches,
ctrlKey,
+ detail: 0,
metaKey,
preventDefault,
shiftKey,
- targetTouches: activeTouch,
- touches: activeTouch,
+ sourceCapabilities: {
+ firesTouchEvents: true,
+ },
+ targetTouches: activeTouches,
+ touches: activeTouches,
});
}
@@ -253,11 +281,24 @@ export function blur({relatedTarget} = {}) {
}
export function click(payload) {
- return createMouseEvent('click', payload, false);
+ return createMouseEvent('click', payload);
}
export function virtualclick(payload) {
- return createMouseEvent('click', payload, true);
+ return createMouseEvent('click', {
+ ...payload,
+ buttons: 0,
+ detail: 0,
+ height: 1,
+ pageX: 0,
+ pageY: 0,
+ pressure: 0,
+ screenX: 0,
+ screenY: 0,
+ width: 1,
+ x: 0,
+ y: 0,
+ });
}
export function contextmenu(payload) {
@@ -301,11 +342,24 @@ export function lostpointercapture(payload) {
}
export function pointercancel(payload) {
- return createPointerEvent('pointercancel', payload);
+ return createPointerEvent('pointercancel', {
+ ...payload,
+ buttons: 0,
+ detail: 0,
+ height: 1,
+ pageX: 0,
+ pageY: 0,
+ pressure: 0,
+ screenX: 0,
+ screenY: 0,
+ width: 1,
+ x: 0,
+ y: 0,
+ });
}
export function pointerdown(payload) {
- const isTouch = payload != null && payload.pointerType === 'mouse';
+ const isTouch = payload != null && payload.pointerType === 'touch';
return createPointerEvent('pointerdown', {
buttons: buttonsType.primary,
pressure: isTouch ? 1 : 0.5,
@@ -337,6 +391,7 @@ export function pointerup(payload) {
return createPointerEvent('pointerup', {
...payload,
buttons: buttonsType.none,
+ pressure: 0,
});
}
diff --git a/packages/react-events/src/dom/testing-library/index.js b/packages/react-events/src/dom/testing-library/index.js
index 6c8f6f198894b..a8cebf0f77e1e 100644
--- a/packages/react-events/src/dom/testing-library/index.js
+++ b/packages/react-events/src/dom/testing-library/index.js
@@ -109,10 +109,35 @@ const createEventTarget = node => ({
},
});
+function describeWithPointerEvent(message, describeFn) {
+ const pointerEvent = 'PointerEvent';
+ const fallback = 'MouseEvent/TouchEvent';
+ describe.each`
+ value | name
+ ${true} | ${pointerEvent}
+ ${false} | ${fallback}
+ `(`${message}: $name`, entry => {
+ const hasPointerEvents = entry.value;
+ setPointerEvent(hasPointerEvents);
+ describeFn(hasPointerEvents);
+ });
+}
+
+function testWithPointerType(message, testFn) {
+ const table = hasPointerEvent()
+ ? ['mouse', 'touch', 'pen']
+ : ['mouse', 'touch'];
+ test.each(table)(`${message}: %s`, pointerType => {
+ testFn(pointerType);
+ });
+}
+
export {
buttonsType,
createEventTarget,
+ describeWithPointerEvent,
platform,
hasPointerEvent,
setPointerEvent,
+ testWithPointerType,
};
diff --git a/packages/react/src/ReactElement.js b/packages/react/src/ReactElement.js
index 9416a313b9648..163ae077f0aca 100644
--- a/packages/react/src/ReactElement.js
+++ b/packages/react/src/ReactElement.js
@@ -179,14 +179,24 @@ export function jsx(type, config, maybeKey) {
let key = null;
let ref = null;
- if (hasValidRef(config)) {
- ref = config.ref;
+ // Currently, key can be spread in as a prop. This causes a potential
+ // issue if key is also explicitly declared (ie.
+ // or
). We want to deprecate key spread,
+ // but as an intermediary step, we will use jsxDEV for everything except
+ //
, because we aren't currently able to tell if
+ // key is explicitly declared to be undefined or not.
+ if (maybeKey !== undefined) {
+ key = '' + maybeKey;
}
if (hasValidKey(config)) {
key = '' + config.key;
}
+ if (hasValidRef(config)) {
+ ref = config.ref;
+ }
+
// Remaining properties are added to a new props object
for (propName in config) {
if (
@@ -197,12 +207,6 @@ export function jsx(type, config, maybeKey) {
}
}
- // intentionally not checking if key was set above
- // this key is higher priority as it's static
- if (maybeKey !== undefined) {
- key = '' + maybeKey;
- }
-
// Resolve default props
if (type && type.defaultProps) {
const defaultProps = type.defaultProps;
@@ -239,14 +243,24 @@ export function jsxDEV(type, config, maybeKey, source, self) {
let key = null;
let ref = null;
- if (hasValidRef(config)) {
- ref = config.ref;
+ // Currently, key can be spread in as a prop. This causes a potential
+ // issue if key is also explicitly declared (ie.
+ // or
). We want to deprecate key spread,
+ // but as an intermediary step, we will use jsxDEV for everything except
+ //
, because we aren't currently able to tell if
+ // key is explicitly declared to be undefined or not.
+ if (maybeKey !== undefined) {
+ key = '' + maybeKey;
}
if (hasValidKey(config)) {
key = '' + config.key;
}
+ if (hasValidRef(config)) {
+ ref = config.ref;
+ }
+
// Remaining properties are added to a new props object
for (propName in config) {
if (
@@ -257,12 +271,6 @@ export function jsxDEV(type, config, maybeKey, source, self) {
}
}
- // intentionally not checking if key was set above
- // this key is higher priority as it's static
- if (maybeKey !== undefined) {
- key = '' + maybeKey;
- }
-
// Resolve default props
if (type && type.defaultProps) {
const defaultProps = type.defaultProps;
diff --git a/packages/react/src/ReactElementValidator.js b/packages/react/src/ReactElementValidator.js
index 6a1626a2e27e8..b81fe9c942036 100644
--- a/packages/react/src/ReactElementValidator.js
+++ b/packages/react/src/ReactElementValidator.js
@@ -43,6 +43,8 @@ if (__DEV__) {
propTypesMisspellWarningShown = false;
}
+const hasOwnProperty = Object.prototype.hasOwnProperty;
+
function getDeclarationErrorAddendum() {
if (ReactCurrentOwner.current) {
const name = getComponentName(ReactCurrentOwner.current.type);
@@ -334,12 +336,26 @@ export function jsxWithValidation(
// We don't want exception behavior to differ between dev and prod.
// (Rendering will throw with a helpful message and as soon as the type is
// fixed, the key warnings will appear.)
+
if (validType) {
const children = props.children;
if (children !== undefined) {
if (isStaticChildren) {
- for (let i = 0; i < children.length; i++) {
- validateChildKeys(children[i], type);
+ if (Array.isArray(children)) {
+ for (let i = 0; i < children.length; i++) {
+ validateChildKeys(children[i], type);
+ }
+
+ if (Object.freeze) {
+ Object.freeze(children);
+ }
+ } else {
+ warning(
+ false,
+ 'React.jsx: Static children should always be an array. ' +
+ 'You are likely explicitly calling React.jsxs or React.jsxDEV. ' +
+ 'Use the Babel transform instead.',
+ );
}
} else {
validateChildKeys(children, type);
@@ -347,7 +363,7 @@ export function jsxWithValidation(
}
}
- if (props.key !== undefined) {
+ if (hasOwnProperty.call(props, 'key')) {
warning(
false,
'React.jsx: Spreading a key to JSX is a deprecated pattern. ' +
diff --git a/packages/react/src/__tests__/ReactElementJSX-test.internal.js b/packages/react/src/__tests__/ReactElementJSX-test.internal.js
index db62a2c51ec15..23135d15de8e3 100644
--- a/packages/react/src/__tests__/ReactElementJSX-test.internal.js
+++ b/packages/react/src/__tests__/ReactElementJSX-test.internal.js
@@ -215,6 +215,18 @@ describe('ReactElement.jsx', () => {
);
});
+ it('warns when a jsxs is passed something that is not an array', () => {
+ const container = document.createElement('div');
+ expect(() =>
+ ReactDOM.render(React.jsxs('div', {children: 'foo'}, null), container),
+ ).toWarnDev(
+ 'React.jsx: Static children should always be an array. ' +
+ 'You are likely explicitly calling React.jsxs or React.jsxDEV. ' +
+ 'Use the Babel transform instead.',
+ {withoutStack: true},
+ );
+ });
+
it('should warn when `key` is being accessed on a host element', () => {
const element = React.jsxs('div', {}, '3');
expect(() => void element.props.key).toWarnDev(