Headshot of Marius Schulz
Marius Schulz Front End Engineer

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.

This post is part of the TypeScript Evolution series:

  1. TypeScript 2.0: Non-Nullable Types
  2. TypeScript 2.0: Control Flow Based Type Analysis
  3. TypeScript 2.0: Acquiring Type Declaration Files
  4. TypeScript 2.0: Read-Only Properties
  5. TypeScript 2.0: Tagged Union Types
  6. TypeScript 2.0: More Literal Types
  7. TypeScript 2.0: The never Type
  8. TypeScript 2.0: Built-In Type Declarations
  9. TypeScript 2.1: async/await for ES3/ES5
  10. TypeScript 2.1: External Helpers Library
  11. TypeScript 2.1: Object Rest and Spread
  12. TypeScript 2.1: keyof and Lookup Types
  13. TypeScript 2.1: Mapped Types
  14. TypeScript 2.1: Improved Inference for Literal Types
  15. TypeScript 2.1: Literal Type Widening
  16. TypeScript 2.1: Untyped Imports
  17. TypeScript 2.2: The object Type
  18. TypeScript 2.2: Dotted Properties and String Index Signatures
  19. TypeScript 2.2: Null-Checking for Expression Operands
  20. TypeScript 2.2: Mixin Classes
  21. TypeScript 2.3: Generic Parameter Defaults
  22. TypeScript 2.3: The --strict Compiler Option
  23. TypeScript 2.3: Type-Checking JavaScript Files with --checkJs
  24. TypeScript 2.3: Downlevel Iteration for ES3/ES5
  25. TypeScript 2.4: String Enums
  26. TypeScript 2.4: Weak Type Detection
  27. TypeScript 2.4: Spelling Correction
  28. TypeScript 2.4: Dynamic import() Expressions
  29. TypeScript 2.5: Optional catch Binding
  30. TypeScript 2.6: JSX Fragment Syntax
  31. TypeScript 2.7: Numeric Separators
  32. TypeScript 2.7: Strict Property Initialization
  33. TypeScript 2.8: Per-File JSX Factories
  34. TypeScript 2.8: Conditional Types