Advanced Static Types in TypeScript

This course explores the capabilities of TypeScript’s type system and shows how to use advanced static types in practice.

Watch the Course

TypeScript 2.8: Per-File JSX Factories

TypeScript 2.8 allows you to specify JSX factory names on a per-file basis. Previously, you could only specify the JSX factory name via the --jsxFactory compiler option. This setting applies to each JSX file in the entire project. Now, you can override the project-wide --jsxFactory setting by adding a special @jsx comment to the beginning of the file.

Let's say we want to use Preact to render the string "Hello World!" into the <div id="app"> container. Preact uses the h function to create JSX elements. We can add the special /** @jsx h */ comment (also known as a "pragma") at the beginning of our .tsx file:

/** @jsx h */
import { h, render } from "preact";

render(
    <h1>Hello World!</h1>,
    document.getElementById("app")!
);

With the /** @jsx h */ pragma in place, the compiler will emit the following JavaScript code for the above file:

/** @jsx h */
import { h, render } from "preact";
render(
    h("h1", null, "Hello World!"),
    document.getElementById("app")
);

Here's the tsconfig.json file that I used to compile the code:

{
  "compilerOptions": {
    "target": "es5",
    "module": "es2015",
    "moduleResolution": "node",
    "jsx": "react",
    "strict": true
  }
}

Note that the compiler only recognizes the pragma if you use the /** ... */ block comment syntax. It won’t change the JSX factory setting if you use the // ... single-line comment syntax.

What is a JSX Factory?

JSX is not part of the ECMAScript standard; that is, it is not valid JavaScript on its own. A script or module that contains JSX therefore can't run directly in the browser. Just like files with type annotations, JSX files need to be compiled to plain JavaScript files first. The --jsxFactory option tells the TypeScript compiler how exactly it should compile JSX elements.

Notice how <h1>Hello World!</h1> was transformed into h("h1", null, "Hello World!"). Preact uses the function h to create virtual DOM elements, which is why we specified h as the JSX factory name. We also need to import h from the preact package so that it is available within the module.

Specifying the JSX Factory Per File vs. Per Project

So when do we need to specify the JSX factory on a per-file basis? If you only use JSX with a single JavaScript library in your project, you don't need a per-file configuration. In this case, it is easier to change the --jsxFactory option within tsconfig.json so that it applies to all JSX files in your project:

{
  "compilerOptions": {
    "target": "es5",
    "module": "es2015",
    "moduleResolution": "node",
    "jsx": "react",
    "jsxFactory": "h",
    "strict": true
  }
}

By default, the --jsxFactory option is set to React.createElement when using the --jsx react option. Therefore, if you’re using React, you don't need to specify the --jsxFactory option at all, nor do you have to add the /** @jsx ... */ pragma.

The per-file configuration of the JSX factory is useful if you're using multiple JavaScript libraries with JSX in the same project. For instance, you might want to add a Vue component to a web application that is written primarily in React. The /** @jsx ... */ pragma allows you to specify a different JSX factory for these files without having to have multiple tsconfig.json files.