Configuring JSX in Deno

Deno has built-in support for JSX in both .jsx files and .tsx files. JSX in Deno can be handy for server-side rendering or generating code for consumption in a browser.

Default configuration

The Deno CLI has a default configuration for JSX that is different than the defaults for tsc. Effectively Deno uses the following TypeScript compiler options by default:

  1. {
  2. "compilerOptions": {
  3. "jsx": "react",
  4. "jsxFactory": "React.createElement",
  5. "jsxFragmentFactory": "React.Fragment"
  6. }
  7. }

JSX import source

In React 17, the React team added what they called the new JSX transforms. This enhanced and modernized the API for JSX transforms as well as provided a mechanism to automatically import a JSX library into a module, instead of having to explicitly import it or make it part of the global scope. Generally this makes it easier to use JSX in your application.

As of Deno 1.16, initial support for these transforms was added. Deno supports both the JSX import source pragma as well as configuring a JSX import source in a configuration file.

JSX runtime

When using the automatic transforms, Deno will try to import a JSX runtime module that is expected to conform to the new JSX API and is located at either jsx-runtime or jsx-dev-runtime. For example if a JSX import source is configured to react, then the emitted code will add this to the emitted file:

  1. import { jsx as jsx_ } from "react/jsx-runtime";

Deno generally works off explicit specifiers, which means it will not try any other specifier at runtime than the one that is emitted. Which means to successfully load the JSX runtime, "react/jsx-runtime" would need to resolve to a module. Saying that, Deno supports remote modules, and most CDNs resolve the specifier easily.

For example, if you wanted to use Preact from the esm.sh CDN, you would use https://esm.sh/preact as the JSX import source, and esm.sh will resolve https://esm.sh/preact/jsx-runtime as a module, including providing a header in the response that tells Deno where to find the type definitions for Preact.

Using the JSX import source pragma

Whether you have a JSX import source configured for your project, or if you are using the default “legacy” configuration, you can add the JSX import source pragma to a .jsx or .tsx module, and Deno will respect it.

The @jsxImportSource pragma needs to be in the leading comments of the module. For example to use Preact from esm.sh, you would do something like this:

  1. /** @jsxImportSource https://esm.sh/preact */
  2. export function App() {
  3. return (
  4. <div>
  5. <h1>Hello, world!</h1>
  6. </div>
  7. );
  8. }

Using JSX import source in a configuration file

If you want to configure a JSX import source for a whole project, so you don’t need to insert the pragma on each module, you can use the "compilerOptions" in a configuration file to specify this. For example if you were using Preact as your JSX library from esm.sh, you would configure the following, in the configuration file:

  1. {
  2. "compilerOptions": {
  3. "jsx": "react-jsx",
  4. "jsxImportSource": "https://esm.sh/preact"
  5. }
  6. }

Using an import map

In situations where the import source plus /jsx-runtime or /jsx-dev-runtime is not resolvable to the correct module, an import map can be used to instruct Deno where to find the module. An import map can also be used to make the import source “cleaner”. For example, if you wanted to use Preact from skypack.dev and have skypack.dev include all the type information, you could setup an import map like this:

  1. {
  2. "imports": {
  3. "preact/jsx-runtime": "https://cdn.skypack.dev/preact/jsx-runtime?dts",
  4. "preact/jsx-dev-runtime": "https://cdn.skypack.dev/preact/jsx-dev-runtime?dts"
  5. }
  6. }

And then you could use the following pragma:

  1. /** @jsxImportSource preact */

Or you could configure it in the compiler options:

  1. {
  2. "compilerOptions": {
  3. "jsx": "react-jsx",
  4. "jsxImportSource": "preact"
  5. }
  6. }

You would then need to pass the --import-map option on the command line (along with the --config option is using a config file) or set the deno.importMap option (and deno.config option) in your IDE.

Current limitations

There are two current limitations of the support of the JSX import source:

  • A JSX module that does not have any imports or exports is not transpiled properly when type checking (see: microsoft/TypeScript#46723). Errors will be seen at runtime about _jsx not being defined. To work around the issue, add export {} to the file or use the --no-check flag which will cause the module to be emitted properly.
  • Using "jsx-reactdev" compiler option is not supported with --no-emit/bundling/compiling (see: swc-project/swc#2656). Various runtime errors will occur about not being able to load jsx-runtime modules. To work around the issue, use the "jsx-react" compiler option instead, or don’t use --no-emit, bundling or compiling.