diff --git a/packages/babel-plugin-transform-typescript/src/index.js b/packages/babel-plugin-transform-typescript/src/index.js index bc2dab189a45..aa6b6be88529 100644 --- a/packages/babel-plugin-transform-typescript/src/index.js +++ b/packages/babel-plugin-transform-typescript/src/index.js @@ -47,7 +47,8 @@ export default declare( ( api, { - jsxPragma = "React", + jsxPragma = "React.createElement", + jsxPragmaFrag = "React.Fragment", allowNamespaces = false, allowDeclareFields = false, onlyRemoveTypeImports = false, @@ -55,7 +56,7 @@ export default declare( ) => { api.assertVersion(7); - const JSX_ANNOTATION_REGEX = /\*?\s*@jsx\s+([^\s]+)/; + const JSX_PRAGMA_REGEX = /\*?\s*@jsx((?:Frag)?)\s+([^\s]+)/; const classMemberVisitors = { field(path) { @@ -157,6 +158,7 @@ export default declare( Program(path, state) { const { file } = state; let fileJsxPragma = null; + let fileJsxPragmaFrag = null; if (!GLOBAL_TYPES.has(path.node)) { GLOBAL_TYPES.set(path.node, new Set()); @@ -164,9 +166,14 @@ export default declare( if (file.ast.comments) { for (const comment of (file.ast.comments: Array)) { - const jsxMatches = JSX_ANNOTATION_REGEX.exec(comment.value); + const jsxMatches = JSX_PRAGMA_REGEX.exec(comment.value); if (jsxMatches) { - fileJsxPragma = jsxMatches[1]; + if (jsxMatches[1]) { + // isFragment + fileJsxPragmaFrag = jsxMatches[2]; + } else { + fileJsxPragma = jsxMatches[2]; + } } } } @@ -176,6 +183,11 @@ export default declare( [pragmaImportName] = pragmaImportName.split("."); } + let pragmaFragImportName = fileJsxPragmaFrag || jsxPragmaFrag; + if (pragmaFragImportName) { + [pragmaFragImportName] = pragmaFragImportName.split("."); + } + // remove type imports for (let stmt of path.get("body")) { if (t.isImportDeclaration(stmt)) { @@ -210,7 +222,8 @@ export default declare( isImportTypeOnly({ binding, programPath: path, - jsxPragma: pragmaImportName, + pragmaImportName, + pragmaFragImportName, }) ) { importsToRemove.push(binding.path); @@ -445,25 +458,31 @@ export default declare( // 'access' and 'readonly' are only for parameter properties, so constructor visitor will handle them. } - function isImportTypeOnly({ binding, programPath, jsxPragma }) { + function isImportTypeOnly({ + binding, + programPath, + pragmaImportName, + pragmaFragImportName, + }) { for (const path of binding.referencePaths) { if (!isInType(path)) { return false; } } - if (binding.identifier.name !== jsxPragma) { + if ( + binding.identifier.name !== pragmaImportName && + binding.identifier.name !== pragmaFragImportName + ) { return true; } - // "React" or the JSX pragma is referenced as a value if there are any JSX elements in the code. + // "React" or the JSX pragma is referenced as a value if there are any JSX elements/fragments in the code. let sourceFileHasJsx = false; programPath.traverse({ - JSXElement() { - sourceFileHasJsx = true; - }, - JSXFragment() { + "JSXElement|JSXFragment"(path) { sourceFileHasJsx = true; + path.stop(); }, }); return !sourceFileHasJsx; diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-jsx-pragmaFrag-namespace-no/input.ts b/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-jsx-pragmaFrag-namespace-no/input.ts new file mode 100644 index 000000000000..e97a799784f6 --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-jsx-pragmaFrag-namespace-no/input.ts @@ -0,0 +1,4 @@ +/* @jsxFrag jsx.htm */ +// Don't elide htm if a JSX fragment appears somewhere. +import * as jsx from "fake-jsx-package"; +<>; diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-jsx-pragmaFrag-namespace-no/options.json b/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-jsx-pragmaFrag-namespace-no/options.json new file mode 100644 index 000000000000..2c7aa7bce294 --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-jsx-pragmaFrag-namespace-no/options.json @@ -0,0 +1,3 @@ +{ + "plugins": [["transform-typescript", { "isTSX": true }]] +} diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-jsx-pragmaFrag-namespace-no/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-jsx-pragmaFrag-namespace-no/output.mjs new file mode 100644 index 000000000000..e97a799784f6 --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-jsx-pragmaFrag-namespace-no/output.mjs @@ -0,0 +1,4 @@ +/* @jsxFrag jsx.htm */ +// Don't elide htm if a JSX fragment appears somewhere. +import * as jsx from "fake-jsx-package"; +<>; diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-jsx-pragmaFrag-no/input.ts b/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-jsx-pragmaFrag-no/input.ts new file mode 100644 index 000000000000..0d84aa8520bf --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-jsx-pragmaFrag-no/input.ts @@ -0,0 +1,4 @@ +/* @jsxFrag htm */ +// Don't elide htm if a JSX fragment appears somewhere. +import { htm } from "fake-jsx-package"; +<>; diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-jsx-pragmaFrag-no/options.json b/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-jsx-pragmaFrag-no/options.json new file mode 100644 index 000000000000..2c7aa7bce294 --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-jsx-pragmaFrag-no/options.json @@ -0,0 +1,3 @@ +{ + "plugins": [["transform-typescript", { "isTSX": true }]] +} diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-jsx-pragmaFrag-no/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-jsx-pragmaFrag-no/output.mjs new file mode 100644 index 000000000000..0d84aa8520bf --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-jsx-pragmaFrag-no/output.mjs @@ -0,0 +1,4 @@ +/* @jsxFrag htm */ +// Don't elide htm if a JSX fragment appears somewhere. +import { htm } from "fake-jsx-package"; +<>; diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-preact-no-2/input.ts b/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-preact-no-2/input.ts new file mode 100644 index 000000000000..c07a3ce21083 --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-preact-no-2/input.ts @@ -0,0 +1,3 @@ +// Don't elide Preact if a JSX fragment appears somewhere. +import { Fragment, render } from "preact"; +<>; diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-preact-no-2/options.json b/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-preact-no-2/options.json new file mode 100644 index 000000000000..7672b90ff149 --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-preact-no-2/options.json @@ -0,0 +1,8 @@ +{ + "plugins": [ + [ + "transform-typescript", + { "jsxPragma": "h", "jsxPragmaFrag": "Fragment", "isTSX": true } + ] + ] +} diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-preact-no-2/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-preact-no-2/output.mjs new file mode 100644 index 000000000000..69895bc7d521 --- /dev/null +++ b/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-preact-no-2/output.mjs @@ -0,0 +1,3 @@ +// Don't elide Preact if a JSX fragment appears somewhere. +import { Fragment } from "preact"; +<>; diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-preact-no/input.ts b/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-preact-no/input.ts index b2ac3334b3aa..39adb36375a1 100644 --- a/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-preact-no/input.ts +++ b/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-preact-no/input.ts @@ -1,3 +1,4 @@ // Don't elide Preact if a JSX element appears somewhere. -import { h, render } from "preact"; +import { h, Fragment, render } from "preact";
; +<>; diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-preact-no/options.json b/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-preact-no/options.json index 7991464b9fc9..7672b90ff149 100644 --- a/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-preact-no/options.json +++ b/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-preact-no/options.json @@ -1,3 +1,8 @@ { - "plugins": [["transform-typescript", { "jsxPragma": "h", "isTSX": true }]] + "plugins": [ + [ + "transform-typescript", + { "jsxPragma": "h", "jsxPragmaFrag": "Fragment", "isTSX": true } + ] + ] } diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-preact-no/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-preact-no/output.mjs index 8260408197df..16d75bed3f1c 100644 --- a/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-preact-no/output.mjs +++ b/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-preact-no/output.mjs @@ -1,3 +1,4 @@ // Don't elide Preact if a JSX element appears somewhere. -import { h } from "preact"; +import { h, Fragment } from "preact";
; +<>; diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-preact/input.ts b/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-preact/input.ts index b656b79fdc6f..3ce0a72c25e6 100644 --- a/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-preact/input.ts +++ b/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-preact/input.ts @@ -1,2 +1,2 @@ -import { FooBar, h } from "preact"; +import { FooBar, h, Fragment } from "preact"; const x: FooBar = 0; diff --git a/packages/babel-preset-typescript/src/index.js b/packages/babel-preset-typescript/src/index.js index 74f885371395..a6376d79dc11 100644 --- a/packages/babel-preset-typescript/src/index.js +++ b/packages/babel-preset-typescript/src/index.js @@ -9,12 +9,17 @@ export default declare( allowDeclareFields, allowNamespaces, jsxPragma, + jsxPragmaFrag = "React.Fragment", isTSX = false, onlyRemoveTypeImports, }, ) => { api.assertVersion(7); + if (typeof jsxPragmaFrag !== "string") { + throw new Error(".jsxPragmaFrag must be a string, or undefined"); + } + if (typeof allExtensions !== "boolean") { throw new Error(".allExtensions must be a boolean, or undefined"); } @@ -32,6 +37,7 @@ export default declare( allowNamespaces, isTSX, jsxPragma, + jsxPragmaFrag, onlyRemoveTypeImports, });