diff --git a/packages/babel-plugin-react-jsx/__tests__/TransformJSXToReactCreateElement-test.js b/packages/babel-plugin-react-jsx/__tests__/TransformJSXToReactCreateElement-test.js
deleted file mode 100644
index d8340607faa91..0000000000000
--- a/packages/babel-plugin-react-jsx/__tests__/TransformJSXToReactCreateElement-test.js
+++ /dev/null
@@ -1,392 +0,0 @@
-/**
- * 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(
- 'unknown: Spread children are not supported in React.' +
- '\n' +
- codeFrame.codeFrameColumns(
- code,
- {start: {line: 1, column: 6}, end: {line: 1, column: 19}},
- {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(
- "unknown: 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}, end: {line: 1, column: 9}},
- {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
index ce7179e2b0ac1..e01259c851a39 100644
--- a/packages/babel-plugin-react-jsx/__tests__/TransformJSXToReactJSX-test.js
+++ b/packages/babel-plugin-react-jsx/__tests__/TransformJSXToReactJSX-test.js
@@ -11,14 +11,15 @@ const babel = require('@babel/core');
const codeFrame = require('@babel/code-frame');
const {wrap} = require('jest-snapshot-serializer-raw');
-function transform(input, options) {
+function transform(input, pluginOpts, babelOpts) {
return wrap(
babel.transform(input, {
configFile: false,
+ sourceType: 'module',
plugins: [
'@babel/plugin-syntax-jsx',
'@babel/plugin-transform-arrow-functions',
- ...(options && options.development
+ ...(pluginOpts && pluginOpts.development
? [
'@babel/plugin-transform-react-jsx-source',
'@babel/plugin-transform-react-jsx-self',
@@ -29,15 +30,380 @@ function transform(input, options) {
{
useBuiltIns: true,
useCreateElement: false,
- ...options,
+ ...pluginOpts,
},
],
],
+ ...babelOpts,
}).code
);
}
describe('transform react to jsx', () => {
+ it('auto import pragma overrides regular pragma', () => {
+ expect(
+ transform(
+ `/** @jsxAutoImport defaultExport */
+ var x =
+ `,
+ {
+ autoImport: 'namespace',
+ importSource: 'foobar',
+ }
+ )
+ ).toMatchSnapshot();
+ });
+
+ it('import source pragma overrides regular pragma', () => {
+ expect(
+ transform(
+ `/** @jsxImportSource baz */
+ var x =
+ `,
+ {
+ autoImport: 'namespace',
+ importSource: 'foobar',
+ }
+ )
+ ).toMatchSnapshot();
+ });
+
+ it('multiple pragmas work', () => {
+ expect(
+ transform(
+ `/** Some comment here
+ * @jsxImportSource baz
+ * @jsxAutoImport defaultExport
+ */
+ var x =
+ `,
+ {
+ autoImport: 'namespace',
+ importSource: 'foobar',
+ }
+ )
+ ).toMatchSnapshot();
+ });
+
+ it('throws error when sourceType is module and autoImport is require', () => {
+ const code = `var x =
`;
+ expect(() => {
+ transform(code, {
+ autoImport: 'require',
+ });
+ }).toThrow(
+ 'Babel `sourceType` must be set to `script` for autoImport ' +
+ 'to use `require` syntax. See Babel `sourceType` for details.\n' +
+ codeFrame.codeFrameColumns(
+ code,
+ {start: {line: 1, column: 1}, end: {line: 1, column: 28}},
+ {highlightCode: true}
+ )
+ );
+ });
+
+ it('throws error when sourceType is script and autoImport is not require', () => {
+ const code = `var x =
`;
+ expect(() => {
+ transform(
+ code,
+ {
+ autoImport: 'namespace',
+ },
+ {sourceType: 'script'}
+ );
+ }).toThrow(
+ 'Babel `sourceType` must be set to `module` for autoImport ' +
+ 'to use `namespace` syntax. See Babel `sourceType` for details.\n' +
+ codeFrame.codeFrameColumns(
+ code,
+ {start: {line: 1, column: 1}, end: {line: 1, column: 28}},
+ {highlightCode: true}
+ )
+ );
+ });
+
+ it("auto import that doesn't exist should throw error", () => {
+ const code = `var x =
`;
+ expect(() => {
+ transform(code, {
+ autoImport: 'foo',
+ });
+ }).toThrow(
+ 'autoImport must be one of the following: none, require, namespace, defaultExport, namedExports\n' +
+ codeFrame.codeFrameColumns(
+ code,
+ {start: {line: 1, column: 1}, end: {line: 1, column: 28}},
+ {highlightCode: true}
+ )
+ );
+ });
+
+ it('auto import can specify source', () => {
+ expect(
+ transform(`var x =
`, {
+ autoImport: 'namespace',
+ importSource: 'foobar',
+ })
+ ).toMatchSnapshot();
+ });
+
+ it('auto import require', () => {
+ expect(
+ transform(
+ `var x = (
+ <>
+
+ >
+ );`,
+ {
+ autoImport: 'require',
+ },
+ {
+ sourceType: 'script',
+ }
+ )
+ ).toMatchSnapshot();
+ });
+
+ it('auto import namespace', () => {
+ expect(
+ transform(
+ `var x = (
+ <>
+
+ >
+ );`,
+ {
+ autoImport: 'namespace',
+ }
+ )
+ ).toMatchSnapshot();
+ });
+
+ it('auto import default', () => {
+ expect(
+ transform(
+ `var x = (
+ <>
+
+ >
+ );`,
+ {
+ autoImport: 'defaultExport',
+ }
+ )
+ ).toMatchSnapshot();
+ });
+
+ it('auto import named exports', () => {
+ expect(
+ transform(
+ `var x = (
+ <>
+
+ >
+ );`,
+ {
+ autoImport: 'namedExports',
+ }
+ )
+ ).toMatchSnapshot();
+ });
+
+ it('auto import with no JSX', () => {
+ expect(
+ transform(
+ `var foo = "
"`,
+ {
+ autoImport: 'require',
+ },
+ {
+ sourceType: 'script',
+ }
+ )
+ ).toMatchSnapshot();
+ });
+
+ it('complicated scope require', () => {
+ expect(
+ transform(
+ `
+ const Bar = () => {
+ const Foo = () => {
+ const Component = ({thing, ..._react}) => {
+ if (!thing) {
+ var _react2 = "something useless";
+ var b = _react3();
+ var c = _react5();
+ var jsx = 1;
+ var _jsx = 2;
+ return
;
+ };
+ return ;
+ };
+ }
+ }
+ `,
+ {
+ autoImport: 'require',
+ },
+ {
+ sourceType: 'script',
+ }
+ )
+ ).toMatchSnapshot();
+ });
+
+ it('complicated scope named exports', () => {
+ expect(
+ transform(
+ `
+ const Bar = () => {
+ const Foo = () => {
+ const Component = ({thing, ..._react}) => {
+ if (!thing) {
+ var _react2 = "something useless";
+ var b = _react3();
+ var jsx = 1;
+ var _jsx = 2;
+ return
;
+ };
+ return ;
+ };
+ }
+ }
+ `,
+ {
+ autoImport: 'namedExports',
+ }
+ )
+ ).toMatchSnapshot();
+ });
+
+ it('auto import in dev', () => {
+ expect(
+ transform(
+ `var x = (
+ <>
+
+ >
+ );`,
+ {
+ autoImport: 'namedExports',
+ development: true,
+ }
+ )
+ ).toMatchSnapshot();
+ });
+
+ it('auto import none', () => {
+ expect(
+ transform(
+ `var x = (
+ <>
+
+ >
+ );`,
+ {
+ autoImport: 'none',
+ }
+ )
+ ).toMatchSnapshot();
+ });
+
+ it('auto import undefined', () => {
+ expect(
+ transform(
+ `var x = (
+ <>
+
+ >
+ );`
+ )
+ ).toMatchSnapshot();
+ });
+
+ it('auto import with namespaces already defined', () => {
+ expect(
+ transform(
+ `
+ import * as _react from "foo";
+ const react = _react(1);
+ const _react1 = react;
+ const _react2 = react;
+ var x = (
+
+ );`,
+ {
+ autoImport: 'namespace',
+ }
+ )
+ ).toMatchSnapshot();
+ });
+
+ it('auto import with react already defined', () => {
+ expect(
+ transform(
+ `
+ import * as react from "react";
+ var y = react.createElement("div", {foo: 1});
+ var x = (
+
+ );`,
+
+ {
+ autoImport: 'namespace',
+ }
+ )
+ ).toMatchSnapshot();
+ });
+
it('fragment with no children', () => {
expect(transform(`var x = <>>`)).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
deleted file mode 100644
index a74c7e1d15e82..0000000000000
--- a/packages/babel-plugin-react-jsx/__tests__/__snapshots__/TransformJSXToReactCreateElement-test.js.snap
+++ /dev/null
@@ -1,213 +0,0 @@
-// 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
index 22b83d9ec1513..cb8a89cc046de 100644
--- a/packages/babel-plugin-react-jsx/__tests__/__snapshots__/TransformJSXToReactJSX-test.js.snap
+++ b/packages/babel-plugin-react-jsx/__tests__/__snapshots__/TransformJSXToReactJSX-test.js.snap
@@ -38,6 +38,230 @@ var div = React.jsx(Component, Object.assign({}, props, {
}));
`;
+exports[`transform react to jsx auto import can specify source 1`] = `
+import * as _foobar from "foobar";
+
+var x = _foobar.jsx("div", {
+ children: _foobar.jsx("span", {})
+});
+`;
+
+exports[`transform react to jsx auto import default 1`] = `
+import _default from "react";
+
+var x = _default.jsx(_default.Fragment, {
+ children: _default.jsxs("div", {
+ children: [_default.jsx("div", {}, "1"), _default.jsx("div", {
+ meow: "wolf"
+ }, "2"), _default.jsx("div", {}, "3"), _default.createElement("div", Object.assign({}, props, {
+ key: "4"
+ }))]
+ })
+});
+`;
+
+exports[`transform react to jsx auto import in dev 1`] = `
+import { createElement as _createElement } from "react";
+import { jsxDEV as _jsxDEV } from "react";
+import { Fragment as _Fragment } from "react";
+var _jsxFileName = "";
+
+var x = _jsxDEV(_Fragment, {
+ children: _jsxDEV("div", {
+ children: [_jsxDEV("div", {}, "1", false, {
+ fileName: _jsxFileName,
+ lineNumber: 4
+ }, this), _jsxDEV("div", {
+ meow: "wolf"
+ }, "2", false, {
+ fileName: _jsxFileName,
+ lineNumber: 5
+ }, this), _jsxDEV("div", {}, "3", false, {
+ fileName: _jsxFileName,
+ lineNumber: 6
+ }, this), _createElement("div", Object.assign({}, props, {
+ key: "4",
+ __source: {
+ fileName: _jsxFileName,
+ lineNumber: 7
+ },
+ __self: this
+ }))]
+ }, undefined, true, {
+ fileName: _jsxFileName,
+ lineNumber: 3
+ }, this)
+}, undefined, false);
+`;
+
+exports[`transform react to jsx auto import named exports 1`] = `
+import { createElement as _createElement } from "react";
+import { jsx as _jsx } from "react";
+import { jsxs as _jsxs } from "react";
+import { Fragment as _Fragment } from "react";
+
+var x = _jsx(_Fragment, {
+ children: _jsxs("div", {
+ children: [_jsx("div", {}, "1"), _jsx("div", {
+ meow: "wolf"
+ }, "2"), _jsx("div", {}, "3"), _createElement("div", Object.assign({}, props, {
+ key: "4"
+ }))]
+ })
+});
+`;
+
+exports[`transform react to jsx auto import namespace 1`] = `
+import * as _react from "react";
+
+var x = _react.jsx(_react.Fragment, {
+ children: _react.jsxs("div", {
+ children: [_react.jsx("div", {}, "1"), _react.jsx("div", {
+ meow: "wolf"
+ }, "2"), _react.jsx("div", {}, "3"), _react.createElement("div", Object.assign({}, props, {
+ key: "4"
+ }))]
+ })
+});
+`;
+
+exports[`transform react to jsx auto import none 1`] = `
+var x = React.jsx(React.Fragment, {
+ children: React.jsxs("div", {
+ children: [React.jsx("div", {}, "1"), React.jsx("div", {
+ meow: "wolf"
+ }, "2"), React.jsx("div", {}, "3"), React.createElement("div", Object.assign({}, props, {
+ key: "4"
+ }))]
+ })
+});
+`;
+
+exports[`transform react to jsx auto import pragma overrides regular pragma 1`] = `
+import _default from "foobar";
+
+/** @jsxAutoImport defaultExport */
+var x = _default.jsx("div", {
+ children: _default.jsx("span", {})
+});
+`;
+
+exports[`transform react to jsx auto import require 1`] = `
+var _react = require("react");
+
+var x = _react.jsx(_react.Fragment, {
+ children: _react.jsxs("div", {
+ children: [_react.jsx("div", {}, "1"), _react.jsx("div", {
+ meow: "wolf"
+ }, "2"), _react.jsx("div", {}, "3"), _react.createElement("div", Object.assign({}, props, {
+ key: "4"
+ }))]
+ })
+});
+`;
+
+exports[`transform react to jsx auto import undefined 1`] = `
+var x = React.jsx(React.Fragment, {
+ children: React.jsxs("div", {
+ children: [React.jsx("div", {}, "1"), React.jsx("div", {
+ meow: "wolf"
+ }, "2"), React.jsx("div", {}, "3"), React.createElement("div", Object.assign({}, props, {
+ key: "4"
+ }))]
+ })
+});
+`;
+
+exports[`transform react to jsx auto import with namespaces already defined 1`] = `
+import * as _react3 from "react";
+import * as _react from "foo";
+
+const react = _react(1);
+
+const _react1 = react;
+const _react2 = react;
+
+var x = _react3.jsxs("div", {
+ children: [_react3.jsx("div", {}, "1"), _react3.jsx("div", {
+ meow: "wolf"
+ }, "2"), _react3.jsx("div", {}, "3"), _react3.createElement("div", Object.assign({}, props, {
+ key: "4"
+ }))]
+});
+`;
+
+exports[`transform react to jsx auto import with no JSX 1`] = `var foo = "
";`;
+
+exports[`transform react to jsx auto import with react already defined 1`] = `
+import * as _react from "react";
+import * as react from "react";
+var y = react.createElement("div", {
+ foo: 1
+});
+
+var x = _react.jsxs("div", {
+ children: [_react.jsx("div", {}, "1"), _react.jsx("div", {
+ meow: "wolf"
+ }, "2"), _react.jsx("div", {}, "3"), _react.createElement("div", Object.assign({}, props, {
+ key: "4"
+ }))]
+});
+`;
+
+exports[`transform react to jsx complicated scope named exports 1`] = `
+import { jsx as _jsx2 } from "react";
+
+const Bar = function () {
+ const Foo = function () {
+ const Component = function ({
+ thing,
+ ..._react
+ }) {
+ if (!thing) {
+ var _react2 = "something useless";
+
+ var b = _react3();
+
+ var jsx = 1;
+ var _jsx = 2;
+ return _jsx2("div", {});
+ }
+
+ ;
+ return _jsx2("span", {});
+ };
+ };
+};
+`;
+
+exports[`transform react to jsx complicated scope require 1`] = `
+var _react4 = require("react");
+
+const Bar = function () {
+ const Foo = function () {
+ const Component = function ({
+ thing,
+ ..._react
+ }) {
+ if (!thing) {
+ var _react2 = "something useless";
+
+ var b = _react3();
+
+ var c = _react5();
+
+ var jsx = 1;
+ var _jsx = 2;
+ return _react4.jsx("div", {});
+ }
+
+ ;
+ return _react4.jsx("span", {});
+ };
+ };
+};
+`;
+
exports[`transform react to jsx concatenates adjacent string literals 1`] = `
var x = React.jsxs("div", {
children: ["foo", "bar", "baz", React.jsx("div", {
@@ -92,6 +316,27 @@ var x = React.jsxDEV(React.Fragment, {
exports[`transform react to jsx fragments to set keys 1`] = `var x = React.jsx(React.Fragment, {}, "foo");`;
+exports[`transform react to jsx import source pragma overrides regular pragma 1`] = `
+import * as _baz from "baz";
+
+/** @jsxImportSource baz */
+var x = _baz.jsx("div", {
+ children: _baz.jsx("span", {})
+});
+`;
+
+exports[`transform react to jsx multiple pragmas work 1`] = `
+import _default from "baz";
+
+/** Some comment here
+ * @jsxImportSource baz
+ * @jsxAutoImport defaultExport
+ */
+var x = _default.jsx("div", {
+ children: _default.jsx("span", {})
+});
+`;
+
exports[`transform react to jsx nonStatic children 1`] = `
var _jsxFileName = "";
var x = React.jsxDEV("div", {
diff --git a/packages/babel-plugin-react-jsx/package.json b/packages/babel-plugin-react-jsx/package.json
index 41243452d4f17..c33a20e06965b 100644
--- a/packages/babel-plugin-react-jsx/package.json
+++ b/packages/babel-plugin-react-jsx/package.json
@@ -5,8 +5,8 @@
"description": "@babel/plugin-transform-react-jsx",
"main": "index.js",
"dependencies": {
+ "@babel/helper-module-imports": "^7.0.0",
"esutils": "^2.0.0"
-
},
"files": [
"README.md",
diff --git a/packages/babel-plugin-react-jsx/src/TransformJSXToReactBabelPlugin.js b/packages/babel-plugin-react-jsx/src/TransformJSXToReactBabelPlugin.js
index 308fc7116f8c0..2d8ebc8c6ec99 100644
--- a/packages/babel-plugin-react-jsx/src/TransformJSXToReactBabelPlugin.js
+++ b/packages/babel-plugin-react-jsx/src/TransformJSXToReactBabelPlugin.js
@@ -24,6 +24,50 @@
'use strict';
const esutils = require('esutils');
+const {
+ isModule,
+ addNamespace,
+ addNamed,
+ addDefault,
+} = require('@babel/helper-module-imports');
+
+// These are all the valid auto import types (under the config autoImport)
+// that a user can specific
+const IMPORT_TYPES = {
+ none: 'none', // default option. Will not import anything
+ require: 'require', // var _react = require("react");
+ namespace: 'namespace', // import * as _react from "react";
+ defaultExport: 'defaultExport', // import _default from "react";
+ namedExports: 'namedExports', // import { jsx } from "react";
+};
+
+const JSX_AUTO_IMPORT_ANNOTATION_REGEX = /\*?\s*@jsxAutoImport\s+([^\s]+)/;
+const JSX_IMPORT_SOURCE_ANNOTATION_REGEX = /\*?\s*@jsxImportSource\s+([^\s]+)/;
+
+// 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, types) {
+ 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 &&
+ types.isJSXAttribute(attr) &&
+ attr.name.name === 'key'
+ ) {
+ return true;
+ } else if (types.isJSXSpreadAttribute(attr)) {
+ seenPropsSpread = true;
+ }
+ }
+ return false;
+}
function helper(babel, opts) {
const {types: t} = babel;
@@ -52,7 +96,7 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`,
visitor.JSXElement = {
exit(path, file) {
let callExpr;
- if (file.opts.useCreateElement || shouldUseCreateElement(path)) {
+ if (shouldUseCreateElement(path, t)) {
callExpr = buildCreateElementCall(path, file);
} else {
callExpr = buildJSXElementCall(path, file);
@@ -71,12 +115,7 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`,
'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);
- }
+ let callExpr = buildJSXFragmentCall(path, file);
if (callExpr) {
path.replaceWith(t.inherits(callExpr, path.node));
@@ -147,31 +186,6 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`,
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)
@@ -262,6 +276,7 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`,
if (opts.post) {
opts.post(state, file);
}
+
return (
state.call ||
t.callExpression(
@@ -561,38 +576,6 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`,
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) {
@@ -623,25 +606,183 @@ module.exports = function(babel) {
},
});
- visitor.Program = {
- enter(path, state) {
- state.set(
- 'oldJSXIdentifier',
- createIdentifierParser('React.createElement'),
+ const createIdentifierName = (path, autoImport, name, importName) => {
+ if (autoImport === IMPORT_TYPES.none) {
+ return `React.${name}`;
+ } else if (autoImport === IMPORT_TYPES.namedExports) {
+ if (importName) {
+ const identifierName = `${importName[name]}`;
+ return identifierName;
+ }
+ } else {
+ return `${importName}.${name}`;
+ }
+ };
+
+ function getImportNames(parentPath, state) {
+ const imports = {};
+ parentPath.traverse({
+ JSXElement(path) {
+ if (shouldUseCreateElement(path, t)) {
+ imports.createElement = true;
+ } else if (path.node.children.length > 1) {
+ const importName = state.development ? 'jsxDEV' : 'jsxs';
+ imports[importName] = true;
+ } else {
+ const importName = state.development ? 'jsxDEV' : 'jsx';
+ imports[importName] = true;
+ }
+ },
+
+ JSXFragment(path) {
+ imports.Fragment = true;
+ },
+ });
+ return imports;
+ }
+
+ function hasJSX(parentPath) {
+ let fileHasJSX = false;
+ parentPath.traverse({
+ JSXElement(path) {
+ fileHasJSX = true;
+ path.stop();
+ },
+
+ JSXFragment(path) {
+ fileHasJSX = true;
+ path.stop();
+ },
+ });
+
+ return fileHasJSX;
+ }
+
+ function addAutoImports(path, state) {
+ if (state.autoImport === IMPORT_TYPES.none) {
+ return;
+ }
+
+ if (IMPORT_TYPES[state.autoImport] === undefined) {
+ throw path.buildCodeFrameError(
+ 'autoImport must be one of the following: ' +
+ Object.keys(IMPORT_TYPES).join(', '),
);
- state.set(
- 'jsxIdentifier',
- createIdentifierParser(
- state.opts.development ? 'React.jsxDEV' : 'React.jsx',
- ),
+ }
+ if (state.autoImport === IMPORT_TYPES.require && isModule(path)) {
+ throw path.buildCodeFrameError(
+ 'Babel `sourceType` must be set to `script` for autoImport ' +
+ 'to use `require` syntax. See Babel `sourceType` for details.',
);
- state.set(
- 'jsxStaticIdentifier',
- createIdentifierParser(
- state.opts.development ? 'React.jsxDEV' : 'React.jsxs',
- ),
+ }
+ if (state.autoImport !== IMPORT_TYPES.require && !isModule(path)) {
+ throw path.buildCodeFrameError(
+ 'Babel `sourceType` must be set to `module` for autoImport to use `' +
+ state.autoImport +
+ '` syntax. See Babel `sourceType` for details.',
);
- state.set('jsxFragIdentifier', createIdentifierParser('React.Fragment'));
+ }
+
+ // import {jsx} from "react";
+ // import {createElement} from "react";
+ if (state.autoImport === IMPORT_TYPES.namedExports) {
+ const imports = getImportNames(path, state);
+ const importMap = {};
+
+ Object.keys(imports).forEach(importName => {
+ importMap[importName] = addNamed(path, importName, state.source).name;
+ });
+
+ return importMap;
+ }
+
+ // add import to file and get the import name
+ let name;
+ if (state.autoImport === IMPORT_TYPES.require) {
+ // var _react = require("react");
+ name = addNamespace(path, state.source, {
+ importedInterop: 'uncompiled',
+ }).name;
+ } else if (state.autoImport === IMPORT_TYPES.namespace) {
+ // import * as _react from "react";
+ name = addNamespace(path, state.source).name;
+ } else if (state.autoImport === IMPORT_TYPES.defaultExport) {
+ // import _default from "react";
+ name = addDefault(path, state.source).name;
+ }
+
+ return name;
+ }
+
+ visitor.Program = {
+ enter(path, state) {
+ if (hasJSX(path)) {
+ let autoImport = state.opts.autoImport || IMPORT_TYPES.none;
+ let source = state.opts.importSource || 'react';
+ const {file} = state;
+
+ if (file.ast.comments) {
+ for (let i = 0; i < file.ast.comments.length; i++) {
+ const comment = file.ast.comments[i];
+ const jsxAutoImportMatches = JSX_AUTO_IMPORT_ANNOTATION_REGEX.exec(
+ comment.value,
+ );
+ if (jsxAutoImportMatches) {
+ autoImport = jsxAutoImportMatches[1];
+ }
+ const jsxImportSourceMatches = JSX_IMPORT_SOURCE_ANNOTATION_REGEX.exec(
+ comment.value,
+ );
+ if (jsxImportSourceMatches) {
+ source = jsxImportSourceMatches[1];
+ }
+ }
+ }
+
+ const importName = addAutoImports(path, {
+ ...state.opts,
+ autoImport,
+ source,
+ });
+
+ state.set(
+ 'oldJSXIdentifier',
+ createIdentifierParser(
+ createIdentifierName(path, autoImport, 'createElement', importName),
+ ),
+ );
+
+ state.set(
+ 'jsxIdentifier',
+ createIdentifierParser(
+ createIdentifierName(
+ path,
+ autoImport,
+ state.opts.development ? 'jsxDEV' : 'jsx',
+ importName,
+ ),
+ ),
+ );
+
+ state.set(
+ 'jsxStaticIdentifier',
+ createIdentifierParser(
+ createIdentifierName(
+ path,
+ autoImport,
+ state.opts.development ? 'jsxDEV' : 'jsxs',
+ importName,
+ ),
+ ),
+ );
+
+ state.set(
+ 'jsxFragIdentifier',
+ createIdentifierParser(
+ createIdentifierName(path, autoImport, 'Fragment', importName),
+ ),
+ );
+ }
},
};