Add emit support for jsx/jsxs experimental jsx runtime api by weswigham · Pull Request #39199 · microsoft/TypeScript

This adds two new options for the jsx compiler option - react-jsx, and react-jsxdev. They use the jsx and jsxDEV constructors and the react/jsx-runtime and react/jsx-dev-runtime entrypoints, respectively. The primary differences, as noted in the linked issue, are that children are passed as part of the props object, and the key is passed as a separate argument. (The jsxDEV constructor further takes some debug data.) In addition, this adds a jsxImportSource compiler option (and @jsxImportSource file pragma) to control the root specifier the jsx runtime is imported from (which defaults to react). In it's current state, I believe this is usable, but there are some known flaws:

  • The checker/program need to load the jsxImportSource module and use it as the source for the JSX namespace automatically. Presently you need to include it in the program yourself (ie, with a types directive or compiler option). This is moreso a TODO for myself, since I don't think there's any question to what we should do (implicitly include the package or @types version in the program). This is more interesting for the jsxImportSource pragma, which, for all intents and purposes, is an import statement. We have a lot of services for real import statements - should they be applied to the pragma, where possible?
  • Babel additionally supports a jsxRuntime pragma (with values classic or automatic) to swap between the old emit and the new one. This is supportable; however currently I only key our emit off the jsx compiler option, and the presence of the jsxImportSource pragma.
  • In some cases, the new transform still falls back to createElement (when a key prop follows a spread) - this is a kind of soft deprecation; however it still implicitly imports the jsx runtime and pulls in createElement. This PR does not yet do this, as, awkwardly enough, the module createElement is supposed to come from is different from the jsx-runtime module entrypoint. I could make this work, but I think this is something we should provide some feedback on - so long as there's still a createElement fallback required in the API, why can't it be exported by the same entrypoint exporting jsx or jsxDEV?
  • Without an import or export, a file is not marked as a module, and will not have an import automatically added. Should this issue an error? Or should, under these jsx modes, the presence of a jsx tag imply module-ness?

Fixes #34547

This is currently experimental in babel, and AFAIK not yet supported in a stable version of react; as such, we should probably continue to experiment with it for awhile before merging it into a stable release.

TL;DR

When jsx: react-jsx (and target: esnext) is set,

export default (props: {className: string}) => <div className={props.className} key="stablekey">childtext</div>

compiles to

import { jsx as _jsx } from "react/jsx-runtime";
export default (props) => _jsx("div", Object.assign({ className: props.className }, { children: "childtext" }), "stablekey");