From c287558345323a0064e06db785f976181e1a256c Mon Sep 17 00:00:00 2001 From: Remco Haszing Date: Thu, 18 Apr 2024 14:22:11 +0200 Subject: [PATCH 1/6] Update JSX documentation This adds some missing information about using JSX in TypeScript. - Add the `react-jsx` and `react-jsxdev` modes in the introduction. Also mention they are called the classic and automatic runtime. - Specify where frameworks need to specify the `JSX` namespace. - Clarify which `jsx` options use which type definitions. - Specify that JSX runtimes must use package exports in order to support ESM. - Fix syntax highlighting in JSX code blocks. - Document that users may use the `@jsxRuntime` comment to override the `jsx` option on a per-file basis. --- .../documentation/copy/en/reference/JSX.md | 106 +++++++++++++++--- .../tsconfig-reference/copy/en/options/jsx.md | 16 +++ 2 files changed, 108 insertions(+), 14 deletions(-) diff --git a/packages/documentation/copy/en/reference/JSX.md b/packages/documentation/copy/en/reference/JSX.md index 33d6beb44ffe..4c3c52c76562 100644 --- a/packages/documentation/copy/en/reference/JSX.md +++ b/packages/documentation/copy/en/reference/JSX.md @@ -18,7 +18,7 @@ In order to use JSX you must do two things. 1. Name your files with a `.tsx` extension 2. Enable the [`jsx`](/tsconfig#jsx) option -TypeScript ships with three JSX modes: `preserve`, `react`, and `react-native`. +TypeScript ships with several JSX modes: `preserve`, `react` (classic runtime), `react-jsx` (automatic runtime), `react-jsxdev` (automatic development runtime), and `react-native`. These modes only affect the emit stage - type checking is unaffected. The `preserve` mode will keep the JSX as part of the output to be further consumed by another transform step (e.g. [Babel](https://babeljs.io/)). Additionally the output will have a `.jsx` file extension. @@ -70,6 +70,65 @@ This is important for two reasons: TypeScript uses the [same convention that React does](http://facebook.github.io/react/docs/jsx-in-depth.html#html-tags-vs.-react-components) for distinguishing between these. An intrinsic element always begins with a lowercase letter, and a value-based element always begins with an uppercase letter. +### The `JSX` namespace + +JSX in TypeScript is typed by the `JSX` namespace. The `JSX` namespace may be defined in various places, depending on the `jsx` compiler option. + +The `jsx` options `preserve`, `react`, and `react-native` use the type definitions for classic runtime. This means a variable needs to be in scope that’s determined by the `jsxFactory` compiler option. The `JSX` namespace needs to specified on the top-most identifier of the JSX factory. For example, React uses the default factory `React.createElement`. This means its `JSX` namespace should be defined as `React.JSX`. + +```ts +export function createElement(): any; + +export namespace JSX { + // … +} +``` + +And the user should always import React as `React`. + +```ts +import * as React; +``` + +Preact uses the JSX factory `h`. That means its types should be defined as the `h.JSX`. + +```ts +export function h(props: any): any; + +export namespace h.JSX { + // … +} +``` + +The user should use a named import to import `h`. + +```ts +import { h } from 'preact'; +``` + +For the `jsx` options `react-jsx` and `react-jsxdev`, the `JSX` namespace needs to be exported from the matching entry points. For `react-jsx` this is `${jsxImportSource}/jsx-runtime`. For `react-jsxdev`, this is `${jsxImportSource}/jsx-dev-runtime`. Since these don’t use a file extension, you must use the [`exports`](https://nodejs.org/api/packages.html#exports) field in `package.json` map in order to support ESM users. + +```json +{ + "exports": { + "./jsx-runtime": "./jsx-runtime.js", + "./jsx-dev-runtime": "./jsx-dev-runtime.js", + } +} +``` + +Then in `jsx-runtime.d.ts` and `jsx-dev-runtime.d.ts`: + +```ts +export namespace JSX { + // … +} +``` + +Note that while exporting the `JSX` namespace is sufficient for type checking, the production runtime needs the `jsx`, `jsxs`, and `Fragment` exports at runtime, and the development runtime needs `jsxDEV` and `Fragment`. Ideally you add types for those too. + +If the `JSX` namespace isn’t available in the appropriate location, both the classic and the automatic runtime fall back to the global `JSX` namespace. + ### Intrinsic elements Intrinsic elements are looked up on the special interface `JSX.IntrinsicElements`. @@ -77,7 +136,7 @@ By default, if this interface is not specified, then anything goes and intrinsic However, if this interface _is_ present, then the name of the intrinsic element is looked up as a property on the `JSX.IntrinsicElements` interface. For example: -```ts +```tsx declare namespace JSX { interface IntrinsicElements { foo: any; @@ -104,7 +163,7 @@ declare namespace JSX { Value-based elements are simply looked up by identifiers that are in scope. -```ts +```tsx import MyComponent from "./myComponent"; ; // ok @@ -123,7 +182,7 @@ Because these two types of value-based elements are indistinguishable from each As the name suggests, the component is defined as a JavaScript function where its first argument is a `props` object. TS enforces that its return type must be assignable to `JSX.Element`. -```ts +```tsx interface FooProp { name: string; X: number; @@ -211,7 +270,7 @@ const myComponent = MyFactoryFunction(); The element instance type is interesting because it must be assignable to `JSX.ElementClass` or it will result in an error. By default `JSX.ElementClass` is `{}`, but it can be augmented to limit the use of JSX to only those types that conform to the proper interface. -```ts +```tsx declare namespace JSX { interface ElementClass { render: any; @@ -244,7 +303,7 @@ This is slightly different between intrinsic and value-based elements. For intrinsic elements, it is the type of the property on `JSX.IntrinsicElements` -```ts +```tsx declare namespace JSX { interface IntrinsicElements { foo: { bar?: boolean }; @@ -262,7 +321,7 @@ It should be declared with a single property. The name of that property is then used. As of TypeScript 2.8, if `JSX.ElementAttributesProperty` is not provided, the type of first parameter of the class element's constructor or Function Component's call will be used instead. -```ts +```tsx declare namespace JSX { interface ElementAttributesProperty { props; // specify the property name to use @@ -283,7 +342,7 @@ class MyComponent { The element attribute type is used to type check the attributes in the JSX. Optional and required properties are supported. -```ts +```tsx declare namespace JSX { interface IntrinsicElements { foo: { requiredProp: string; optionalProp?: number }; @@ -304,7 +363,7 @@ Additionally, the `JSX.IntrinsicAttributes` interface can be used to specify ext The spread operator also works: -```ts +```tsx const props = { requiredProp: "bar" }; ; // ok @@ -326,7 +385,7 @@ declare namespace JSX { } ``` -```ts +```tsx

Hello

; @@ -345,7 +404,7 @@ const CustomComp = (props) =>
{props.children}
You can specify the type of _children_ like any other attribute. This will override the default type from, e.g. the [React typings](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/react) if you use them. -```ts +```tsx interface PropsType { children: JSX.Element name: string @@ -386,11 +445,30 @@ You can customize the type by specifying the `JSX.Element` interface. However, it is not possible to retrieve type information about the element, attributes or children of the JSX from this interface. It is a black box. +## The JSX function return type + +By default, JSX functions must return the `JSX.Element` type. However, this doesn’t always represent runtime behaviour. As of TypeScript 5.1, you can specify `JSX.ElementType` to override what is a valid JSX component type. The default looks something like this. + +```ts +namespace JSX { + export type ElementType = + // All the valid lowercase tags + keyof IntrinsicAttributes + // Function components + (props: any) => Element + // Class components + new (props: any) => ElementClass; + export interface IntrinsicAttributes extends /*...*/ {} + export type Element = /*...*/; + export type ElementClass = /*...*/; +} +``` + ## Embedding Expressions JSX allows you to embed expressions between tags by surrounding the expressions with curly braces (`{ }`). -```ts +```tsx const a = (
{["foo", "bar"].map((i) => ( @@ -403,7 +481,7 @@ const a = ( The above code will result in an error since you cannot divide a string by a number. The output, when using the `preserve` option, looks like: -```ts +```tsx const a = (
{["foo", "bar"].map(function (i) { @@ -418,7 +496,7 @@ const a = ( To use JSX with React you should use the [React typings](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/react). These typings define the `JSX` namespace appropriately for use with React. -```ts +```tsx /// interface Props { diff --git a/packages/tsconfig-reference/copy/en/options/jsx.md b/packages/tsconfig-reference/copy/en/options/jsx.md index 27ce2e65d95c..d9e909367c8b 100644 --- a/packages/tsconfig-reference/copy/en/options/jsx.md +++ b/packages/tsconfig-reference/copy/en/options/jsx.md @@ -93,3 +93,19 @@ declare module JSX { // @jsx: react-jsxdev export const HelloWorld = () =>

Hello world

; ``` + +This option can be used on a per-file basis too using an `@jsxRuntime` comment. + +Always use the classic runtime (`"react"`) for this file: + +```tsx +/* @jsxRuntime classic */ +export const HelloWorld = () =>

Hello world

; +``` + +Always use the automatic runtime (`"react-jsx"`) for this file: + +```tsx +/* @jsxRuntime automatic */ +export const HelloWorld = () =>

Hello world

; +``` From c848e45ada3e6763c331b3e98211f518aa7ddd01 Mon Sep 17 00:00:00 2001 From: Remco Haszing Date: Wed, 24 Apr 2024 12:21:35 +0200 Subject: [PATCH 2/6] Fix a syntax error in an example --- packages/documentation/copy/en/reference/JSX.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/documentation/copy/en/reference/JSX.md b/packages/documentation/copy/en/reference/JSX.md index 4c3c52c76562..206cdd964e1d 100644 --- a/packages/documentation/copy/en/reference/JSX.md +++ b/packages/documentation/copy/en/reference/JSX.md @@ -87,7 +87,7 @@ export namespace JSX { And the user should always import React as `React`. ```ts -import * as React; +import * as React from 'react'; ``` Preact uses the JSX factory `h`. That means its types should be defined as the `h.JSX`. From 0f02a44f3f077c15d98acbc7dbe192a65b04d77a Mon Sep 17 00:00:00 2001 From: Remco Haszing Date: Tue, 30 Apr 2024 14:47:28 +0200 Subject: [PATCH 3/6] Remove incorrect sentence The JSX mode does affect type checking. --- packages/documentation/copy/en/reference/JSX.md | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/documentation/copy/en/reference/JSX.md b/packages/documentation/copy/en/reference/JSX.md index 206cdd964e1d..e0366ff08e0a 100644 --- a/packages/documentation/copy/en/reference/JSX.md +++ b/packages/documentation/copy/en/reference/JSX.md @@ -19,7 +19,6 @@ In order to use JSX you must do two things. 2. Enable the [`jsx`](/tsconfig#jsx) option TypeScript ships with several JSX modes: `preserve`, `react` (classic runtime), `react-jsx` (automatic runtime), `react-jsxdev` (automatic development runtime), and `react-native`. -These modes only affect the emit stage - type checking is unaffected. The `preserve` mode will keep the JSX as part of the output to be further consumed by another transform step (e.g. [Babel](https://babeljs.io/)). Additionally the output will have a `.jsx` file extension. The `react` mode will emit `React.createElement`, does not need to go through a JSX transformation before use, and the output will have a `.js` file extension. From 9bddf7704014d7609c952e3c28cbcd0d1231ddad Mon Sep 17 00:00:00 2001 From: Remco Haszing Date: Thu, 6 Jun 2024 15:28:00 +0200 Subject: [PATCH 4/6] Apply suggestions from code review Co-authored-by: Sebastian Silbermann --- packages/documentation/copy/en/reference/JSX.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/documentation/copy/en/reference/JSX.md b/packages/documentation/copy/en/reference/JSX.md index e0366ff08e0a..141428e2a899 100644 --- a/packages/documentation/copy/en/reference/JSX.md +++ b/packages/documentation/copy/en/reference/JSX.md @@ -73,7 +73,7 @@ An intrinsic element always begins with a lowercase letter, and a value-based el JSX in TypeScript is typed by the `JSX` namespace. The `JSX` namespace may be defined in various places, depending on the `jsx` compiler option. -The `jsx` options `preserve`, `react`, and `react-native` use the type definitions for classic runtime. This means a variable needs to be in scope that’s determined by the `jsxFactory` compiler option. The `JSX` namespace needs to specified on the top-most identifier of the JSX factory. For example, React uses the default factory `React.createElement`. This means its `JSX` namespace should be defined as `React.JSX`. +The `jsx` options `preserve`, `react`, and `react-native` use the type definitions for classic runtime. This means a variable needs to be in scope that’s determined by the `jsxFactory` compiler option. The `JSX` namespace needs to be specified on the top-most identifier of the JSX factory. For example, React uses the default factory `React.createElement`. This means its `JSX` namespace should be defined as `React.JSX`. ```ts export function createElement(): any; @@ -446,7 +446,7 @@ It is a black box. ## The JSX function return type -By default, JSX functions must return the `JSX.Element` type. However, this doesn’t always represent runtime behaviour. As of TypeScript 5.1, you can specify `JSX.ElementType` to override what is a valid JSX component type. The default looks something like this. +By default, function components must return `JSX.Element | null`. However, this doesn’t always represent runtime behaviour. As of TypeScript 5.1, you can specify `JSX.ElementType` to override what is a valid JSX component type. The default looks something like this: ```ts namespace JSX { From 3435c68c586a76082fe0327ff0bc0ab2ff9b307e Mon Sep 17 00:00:00 2001 From: Remco Haszing Date: Fri, 7 Jun 2024 15:04:52 +0200 Subject: [PATCH 5/6] Use less strong words to specify where to define the JSX namespace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since it’s also possible to define the `JSX` namespace globally, `needs to` is a bit to strong. It was replaced with `should`. --- packages/documentation/copy/en/reference/JSX.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/documentation/copy/en/reference/JSX.md b/packages/documentation/copy/en/reference/JSX.md index 141428e2a899..bb32fe723fd2 100644 --- a/packages/documentation/copy/en/reference/JSX.md +++ b/packages/documentation/copy/en/reference/JSX.md @@ -73,7 +73,7 @@ An intrinsic element always begins with a lowercase letter, and a value-based el JSX in TypeScript is typed by the `JSX` namespace. The `JSX` namespace may be defined in various places, depending on the `jsx` compiler option. -The `jsx` options `preserve`, `react`, and `react-native` use the type definitions for classic runtime. This means a variable needs to be in scope that’s determined by the `jsxFactory` compiler option. The `JSX` namespace needs to be specified on the top-most identifier of the JSX factory. For example, React uses the default factory `React.createElement`. This means its `JSX` namespace should be defined as `React.JSX`. +The `jsx` options `preserve`, `react`, and `react-native` use the type definitions for classic runtime. This means a variable needs to be in scope that’s determined by the `jsxFactory` compiler option. The `JSX` namespace should be specified on the top-most identifier of the JSX factory. For example, React uses the default factory `React.createElement`. This means its `JSX` namespace should be defined as `React.JSX`. ```ts export function createElement(): any; @@ -105,7 +105,7 @@ The user should use a named import to import `h`. import { h } from 'preact'; ``` -For the `jsx` options `react-jsx` and `react-jsxdev`, the `JSX` namespace needs to be exported from the matching entry points. For `react-jsx` this is `${jsxImportSource}/jsx-runtime`. For `react-jsxdev`, this is `${jsxImportSource}/jsx-dev-runtime`. Since these don’t use a file extension, you must use the [`exports`](https://nodejs.org/api/packages.html#exports) field in `package.json` map in order to support ESM users. +For the `jsx` options `react-jsx` and `react-jsxdev`, the `JSX` namespace should be exported from the matching entry points. For `react-jsx` this is `${jsxImportSource}/jsx-runtime`. For `react-jsxdev`, this is `${jsxImportSource}/jsx-dev-runtime`. Since these don’t use a file extension, you must use the [`exports`](https://nodejs.org/api/packages.html#exports) field in `package.json` map in order to support ESM users. ```json { From 0e697b1c59dfd055a06e996bec3ebe61da30e1c2 Mon Sep 17 00:00:00 2001 From: Remco Haszing Date: Fri, 7 Jun 2024 15:09:44 +0200 Subject: [PATCH 6/6] =?UTF-8?q?Specify=20ElementType=20doesn=E2=80=99t=20s?= =?UTF-8?q?pecify=20the=20props=20type?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/documentation/copy/en/reference/JSX.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/documentation/copy/en/reference/JSX.md b/packages/documentation/copy/en/reference/JSX.md index bb32fe723fd2..202fc1551ea5 100644 --- a/packages/documentation/copy/en/reference/JSX.md +++ b/packages/documentation/copy/en/reference/JSX.md @@ -446,7 +446,7 @@ It is a black box. ## The JSX function return type -By default, function components must return `JSX.Element | null`. However, this doesn’t always represent runtime behaviour. As of TypeScript 5.1, you can specify `JSX.ElementType` to override what is a valid JSX component type. The default looks something like this: +By default, function components must return `JSX.Element | null`. However, this doesn’t always represent runtime behaviour. As of TypeScript 5.1, you can specify `JSX.ElementType` to override what is a valid JSX component type. Note that this doesn’t define what props are valid. The type of props is always defined by the first argument of the component that’s passed. The default looks something like this: ```ts namespace JSX {