From 9f77526060107c78619254025d2665a35fbf4d55 Mon Sep 17 00:00:00 2001 From: JodliDev Date: Wed, 16 Jul 2025 14:15:05 +0200 Subject: [PATCH 01/23] Add section for TSX --- docs/jsx.md | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 118 insertions(+), 3 deletions(-) diff --git a/docs/jsx.md b/docs/jsx.md index 10df264f..a650eeb4 100644 --- a/docs/jsx.md +++ b/docs/jsx.md @@ -5,9 +5,12 @@ Explanation, examples, and build notes on how to use JSX in your Mithril.js-base # JSX - [Description](#description) -- [Setup](#setup) +- [Setup JSX](#setup-jsx) - [Production build](#production-build) - [Using Babel with Webpack](#using-babel-with-webpack) +- [Setup TSX](#setup-tsx-jsx-in-typescript) +- [Enabling Fragments](#enable-fragments) +- [Using Closure Components in TSX](#using-closure-components-in-tsx) - [Differences with React](#differences-with-react) - [JSX vs hyperscript](#jsx-vs-hyperscript) - [Tips and Tricks](#tips-and-tricks) @@ -58,9 +61,9 @@ m.render(document.body, ) --- -### Setup +### Setup JSX -The simplest way to use JSX is via a [Babel](https://babeljs.io/) plugin. +When using JavaScript, the simplest way to use JSX is via a [Babel](https://babeljs.io/) plugin. When using using [TypeScript](https://www.typescriptlang.org/) follow the [instructions below](#setup-tsx-jsx-in-typescript) Babel requires npm, which is automatically installed when you install [Node.js](https://nodejs.org/en/). Once npm is installed, create a project folder and run this command: @@ -241,6 +244,118 @@ See [the Webpack docs](https://webpack.js.org/plugins/provide-plugin/) for more --- +### Setup TSX (JSX in TypeScript) + +Since TypeScript is already transpiled, Babel is not necessary. All you need to do is tell TypeScript how to handle JSX code correctly (more information about JSX in TypeScript [here](https://www.typescriptlang.org/docs/handbook/jsx.html)). + +Add `jsx` and `jsxFactory` to `compilerOptions` in your `tsconfig.json`: + +```json +{ + "compilerOptions": { + "jsx": "react", + "jsxFactory": "m" + } +} +``` + +This setup should be enough to get most JSX functionality working. But there are a few gotchas that you might want to fix as well: + +#### Enabling Fragments + +With the setup above, you will not be able to use Fragments (e.g. `<>bla`). +To enable Fragments, first add `jsxFragmentFactory` to `compilerOptions` in your `tsconfig.json`: + +```json +{ + "compilerOptions": { + "jsxFragmentFactory": "m.fragment" + } +} +``` +`m.fragment` also needs to be defined globally. The easiest way of doing that, is adding the following line to the entry point of your application (depending on your project structure, that might be `src/index.ts`): + +```typescript +m.fragment = { view: (vNode: Vnode) => vNode.children } as any; +``` + +#### Using Closure Components in TSX +When using [Closure Components](components.md#closure-component-state) in JSX, TypeScript only expects an attribute object as a parameter for a Function Component. But Mithril provides a `Vnode` object instead. This leads to the IDE showing faulty parameters even though the JSX would compile correctly. + +Example: +The following code will compile correctly but show this error: +``` +TS2739: Type { greet: string; } is missing the following properties from type Vnode<{}, {}>: tag, attrs, state +TS2786: LoadingSpinner cannot be used as a JSX component. +``` + +```typescript jsx +interface Attributes { + greet: string +} +function ChildComponent(vNode: Vnode): m.Component { + return { + view:
{vNode.attrs.greet}
+ }; +} + +function ParentComponent() { + return { + view:
//This line will compile correctly but shows the errors above + }; +} +``` + +There are several options to circumvent that problem: +1) Instead of `
`, use `
{m(ChildComponent, {greet: "Hello World"})}
` instead. +2) Use [Class Components](components.md#class-component-state) instead. Class Components will not show any errors. But TypeScript will not be able to autocomplete or inspect attributes (in this example `greet` would be unknown when used in `ParentComponent`). +3) Create a "translation function" (see `TranslatedComponent()` in the example below) to trick TypeScript. + +The following code will work without errors: + +```typescript jsx +/** + * Use TranslatedComponent to create Closure Components that can be inspected by TypeScript. + */ +export function TranslatedComponent(create: m.ClosureComponent) { + return (attrs: T) => { + const vNode = attrs as m.Vnode; + return create(vNode) as unknown as JSX.Element; + } +} + + +interface Attributes { + greet: string +} +//We slightly altered the definition of ChildComponent by using TranslatedComponent() +const ChildComponent = TranslationdComponent(vNode => { + return { + view:
{vNode.attrs.greet}
+ }; +}) + +function ParentComponent() { + return { + view:
+ }; +} + +``` + +When you need generics for your closure component, you can use the following definition style: + +```typescript jsx +function ChildComponentImpl() { + ... +} + +const ChildComponent = TranslatedComponent(ChildComponentImpl); //for TranslatedComponent, see above + +const jsx =
/>
+``` + + ### Differences with React JSX in Mithril has some subtle but important differences compared to React's JSX. From 5f041de1855b48722588e1a955af8fe3cde5ceaa Mon Sep 17 00:00:00 2001 From: JodliDev Date: Sat, 9 Aug 2025 11:50:51 +0200 Subject: [PATCH 02/23] Improve closure compiler example Co-authored-by: Claudia Meadows --- docs/jsx.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/jsx.md b/docs/jsx.md index a650eeb4..89d0fe53 100644 --- a/docs/jsx.md +++ b/docs/jsx.md @@ -282,12 +282,7 @@ m.fragment = { view: (vNode: Vnode) => vNode.children } as any; #### Using Closure Components in TSX When using [Closure Components](components.md#closure-component-state) in JSX, TypeScript only expects an attribute object as a parameter for a Function Component. But Mithril provides a `Vnode` object instead. This leads to the IDE showing faulty parameters even though the JSX would compile correctly. -Example: -The following code will compile correctly but show this error: -``` -TS2739: Type { greet: string; } is missing the following properties from type Vnode<{}, {}>: tag, attrs, state -TS2786: LoadingSpinner cannot be used as a JSX component. -``` +For example, if you try to compile this code: ```typescript jsx interface Attributes { @@ -306,6 +301,13 @@ function ParentComponent() { } ``` +TypeScript will report this error: + +``` +TS2739: Type { greet: string; } is missing the following properties from type Vnode<{}, {}>: tag, attrs, state +TS2786: LoadingSpinner cannot be used as a JSX component. +``` + There are several options to circumvent that problem: 1) Instead of `
`, use `
{m(ChildComponent, {greet: "Hello World"})}
` instead. 2) Use [Class Components](components.md#class-component-state) instead. Class Components will not show any errors. But TypeScript will not be able to autocomplete or inspect attributes (in this example `greet` would be unknown when used in `ParentComponent`). From 58f5601953c67863a5e1f871b25c580bbf95f74f Mon Sep 17 00:00:00 2001 From: JodliDev Date: Sat, 9 Aug 2025 12:02:23 +0200 Subject: [PATCH 03/23] Fix TsClosureComponent Co-authored-by: Claudia Meadows --- docs/jsx.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/jsx.md b/docs/jsx.md index 89d0fe53..d8ca5b12 100644 --- a/docs/jsx.md +++ b/docs/jsx.md @@ -320,10 +320,9 @@ The following code will work without errors: * Use TranslatedComponent to create Closure Components that can be inspected by TypeScript. */ export function TranslatedComponent(create: m.ClosureComponent) { - return (attrs: T) => { - const vNode = attrs as m.Vnode; - return create(vNode) as unknown as JSX.Element; - } + return create as any as ( + (attrs: T & Mithril.CommonAttributes) => JSX.Element + ) } From e7505e6ca778971d81cd743bd0fea3d2a81f8594 Mon Sep 17 00:00:00 2001 From: JodliDev Date: Sat, 9 Aug 2025 12:07:56 +0200 Subject: [PATCH 04/23] Improve introduction to TSX Co-authored-by: Claudia Meadows --- docs/jsx.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/jsx.md b/docs/jsx.md index d8ca5b12..2ebd0e19 100644 --- a/docs/jsx.md +++ b/docs/jsx.md @@ -246,7 +246,7 @@ See [the Webpack docs](https://webpack.js.org/plugins/provide-plugin/) for more ### Setup TSX (JSX in TypeScript) -Since TypeScript is already transpiled, Babel is not necessary. All you need to do is tell TypeScript how to handle JSX code correctly (more information about JSX in TypeScript [here](https://www.typescriptlang.org/docs/handbook/jsx.html)). +When using [TypeScript](https://www.typescriptlang.org/), all you need to do is tell TypeScript how to handle JSX code correctly. Since TypeScript can transpile JSX and TSX on its own, you don't need any other tools like Babel to do it for you. (More information can be found about JSX in TypeScript [here](https://www.typescriptlang.org/docs/handbook/jsx.html).) Add `jsx` and `jsxFactory` to `compilerOptions` in your `tsconfig.json`: From ac6ab354edb9c03510591033063bb93b9509634c Mon Sep 17 00:00:00 2001 From: JodliDev Date: Sat, 9 Aug 2025 11:31:30 +0200 Subject: [PATCH 05/23] Remove section `Enabling Fragments` since it is unnecessary. --- docs/jsx.md | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/docs/jsx.md b/docs/jsx.md index 2ebd0e19..ec811db2 100644 --- a/docs/jsx.md +++ b/docs/jsx.md @@ -9,7 +9,6 @@ Explanation, examples, and build notes on how to use JSX in your Mithril.js-base - [Production build](#production-build) - [Using Babel with Webpack](#using-babel-with-webpack) - [Setup TSX](#setup-tsx-jsx-in-typescript) -- [Enabling Fragments](#enable-fragments) - [Using Closure Components in TSX](#using-closure-components-in-tsx) - [Differences with React](#differences-with-react) - [JSX vs hyperscript](#jsx-vs-hyperscript) @@ -254,30 +253,13 @@ Add `jsx` and `jsxFactory` to `compilerOptions` in your `tsconfig.json`: { "compilerOptions": { "jsx": "react", - "jsxFactory": "m" + "jsxFactory": "m", + "jsxFragmentFactory": "m.Fragment" } } ``` -This setup should be enough to get most JSX functionality working. But there are a few gotchas that you might want to fix as well: - -#### Enabling Fragments - -With the setup above, you will not be able to use Fragments (e.g. `<>bla`). -To enable Fragments, first add `jsxFragmentFactory` to `compilerOptions` in your `tsconfig.json`: - -```json -{ - "compilerOptions": { - "jsxFragmentFactory": "m.fragment" - } -} -``` -`m.fragment` also needs to be defined globally. The easiest way of doing that, is adding the following line to the entry point of your application (depending on your project structure, that might be `src/index.ts`): - -```typescript -m.fragment = { view: (vNode: Vnode) => vNode.children } as any; -``` +This setup should be enough to get most JSX functionality working. #### Using Closure Components in TSX When using [Closure Components](components.md#closure-component-state) in JSX, TypeScript only expects an attribute object as a parameter for a Function Component. But Mithril provides a `Vnode` object instead. This leads to the IDE showing faulty parameters even though the JSX would compile correctly. From 0ddb5a67a63ac2397147b0479b63f0d96473af2f Mon Sep 17 00:00:00 2001 From: JodliDev Date: Sat, 9 Aug 2025 11:37:06 +0200 Subject: [PATCH 06/23] Shorten referral to Typescript --- docs/jsx.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/jsx.md b/docs/jsx.md index ec811db2..dfe10dd9 100644 --- a/docs/jsx.md +++ b/docs/jsx.md @@ -62,7 +62,7 @@ m.render(document.body, ) ### Setup JSX -When using JavaScript, the simplest way to use JSX is via a [Babel](https://babeljs.io/) plugin. When using using [TypeScript](https://www.typescriptlang.org/) follow the [instructions below](#setup-tsx-jsx-in-typescript) +When using JavaScript, the simplest way to use JSX is via a [Babel](https://babeljs.io/) plugin. (For TypeScript follow the [instructions below](#setup-tsx-jsx-in-typescript)). Babel requires npm, which is automatically installed when you install [Node.js](https://nodejs.org/en/). Once npm is installed, create a project folder and run this command: From 5545b6e1e9509cbaa49c50a26f46e4b92f611603 Mon Sep 17 00:00:00 2001 From: JodliDev Date: Sat, 9 Aug 2025 11:49:33 +0200 Subject: [PATCH 07/23] Simplify introduction to closure components in TSX --- docs/jsx.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/jsx.md b/docs/jsx.md index dfe10dd9..d9a2745d 100644 --- a/docs/jsx.md +++ b/docs/jsx.md @@ -261,8 +261,10 @@ Add `jsx` and `jsxFactory` to `compilerOptions` in your `tsconfig.json`: This setup should be enough to get most JSX functionality working. -#### Using Closure Components in TSX -When using [Closure Components](components.md#closure-component-state) in JSX, TypeScript only expects an attribute object as a parameter for a Function Component. But Mithril provides a `Vnode` object instead. This leads to the IDE showing faulty parameters even though the JSX would compile correctly. +#### Using closure components in TSX +Because of https://github.com/microsoft/TypeScript/issues/21699, we advise against using [closure components](components.md#closure-component-state) in TypeScript. Either use [class components](components.md#class-component-state) or Hyperscript instead (see a list of alternatives below). +If you really want to use them, you have to add a simple hack to trick the TypeScript error checking to resolve faulty errors. +When using closure components in JSX, TypeScript only expects an attribute object as a parameter. But Mithril.js provides a `Vnode` object instead. This leads to the editor showing faulty parameters even though the JSX would compile correctly. For example, if you try to compile this code: From 3be690cc8a3bd50c886e18253bc65458335a6155 Mon Sep 17 00:00:00 2001 From: JodliDev Date: Sat, 9 Aug 2025 11:55:03 +0200 Subject: [PATCH 08/23] Replace `Mithril` with `Mithril.js` --- docs/jsx.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/jsx.md b/docs/jsx.md index d9a2745d..839bcbad 100644 --- a/docs/jsx.md +++ b/docs/jsx.md @@ -343,19 +343,19 @@ const jsx =
/>
### Differences with React -JSX in Mithril has some subtle but important differences compared to React's JSX. +JSX in Mithril.js has some subtle but important differences compared to React's JSX. #### Attribute and style property case conventions -React requires you use the camel-cased DOM property names instead of HTML attribute names for all attributes other than `data-*` and `aria-*` attributes. For example using `className` instead of `class` and `htmlFor` instead of `for`. In Mithril, it's more idiomatic to use the lowercase HTML attribute names instead. Mithril always falls back to setting attributes if a property doesn't exist which aligns more intuitively with HTML. Note that in most cases, the DOM property and HTML attribute names are either the same or very similar. For example `value`/`checked` for inputs and the `tabindex` global attribute vs the `elem.tabIndex` property on HTML elements. Very rarely do they differ beyond case: the `elem.className` property for the `class` attribute or the `elem.htmlFor` property for the `for` attribute are among the few exceptions. +React requires you use the camel-cased DOM property names instead of HTML attribute names for all attributes other than `data-*` and `aria-*` attributes. For example using `className` instead of `class` and `htmlFor` instead of `for`. In Mithril.js, it's more idiomatic to use the lowercase HTML attribute names instead. Mithril.js always falls back to setting attributes if a property doesn't exist which aligns more intuitively with HTML. Note that in most cases, the DOM property and HTML attribute names are either the same or very similar. For example `value`/`checked` for inputs and the `tabindex` global attribute vs the `elem.tabIndex` property on HTML elements. Very rarely do they differ beyond case: the `elem.className` property for the `class` attribute or the `elem.htmlFor` property for the `for` attribute are among the few exceptions. -Similarly, React always uses the camel-cased style property names exposed in the DOM via properties of `elem.style` (like `cssHeight` and `backgroundColor`). Mithril supports both that and the kebab-cased CSS property names (like `height` and `background-color`) and idiomatically prefers the latter. Only `cssHeight`, `cssFloat`, and vendor-prefixed properties differ in more than case. +Similarly, React always uses the camel-cased style property names exposed in the DOM via properties of `elem.style` (like `cssHeight` and `backgroundColor`). Mithril.js supports both that and the kebab-cased CSS property names (like `height` and `background-color`) and idiomatically prefers the latter. Only `cssHeight`, `cssFloat`, and vendor-prefixed properties differ in more than case. #### DOM events -React upper-cases the first character of all event handlers: `onClick` listens for `click` events and `onSubmit` for `submit` events. Some are further altered as they're multiple words concatenated together. For instance, `onMouseMove` listens for `mousemove` events. Mithril does not do this case mapping but instead just prepends `on` to the native event, so you'd add listeners for `onclick` and `onmousemove` to listen to those two events respectively. This corresponds much more closely to HTML's naming scheme and is much more intuitive if you come from an HTML or vanilla DOM background. +React upper-cases the first character of all event handlers: `onClick` listens for `click` events and `onSubmit` for `submit` events. Some are further altered as they're multiple words concatenated together. For instance, `onMouseMove` listens for `mousemove` events. Mithril.js does not do this case mapping but instead just prepends `on` to the native event, so you'd add listeners for `onclick` and `onmousemove` to listen to those two events respectively. This corresponds much more closely to HTML's naming scheme and is much more intuitive if you come from an HTML or vanilla DOM background. -React supports scheduling event listeners during the capture phase (in the first pass, out to in, as opposed to the default bubble phase going in to out in the second pass) by appending `Capture` to that event. Mithril currently lacks such functionality, but it could gain this in the future. If this is necessary you can manually add and remove your own listeners in [lifecycle hooks](lifecycle-methods.md). +React supports scheduling event listeners during the capture phase (in the first pass, out to in, as opposed to the default bubble phase going in to out in the second pass) by appending `Capture` to that event. Mithril.js currently lacks such functionality, but it could gain this in the future. If this is necessary you can manually add and remove your own listeners in [lifecycle hooks](lifecycle-methods.md). --- From cfb5f3403388e5a93ee6fac95564e3ec1fea68d3 Mon Sep 17 00:00:00 2001 From: JodliDev Date: Sat, 9 Aug 2025 12:03:50 +0200 Subject: [PATCH 09/23] Improve code example for closure component --- docs/jsx.md | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/docs/jsx.md b/docs/jsx.md index 839bcbad..b757da0b 100644 --- a/docs/jsx.md +++ b/docs/jsx.md @@ -293,17 +293,17 @@ TS2786: LoadingSpinner cannot be used as a JSX component. ``` There are several options to circumvent that problem: -1) Instead of `
`, use `
{m(ChildComponent, {greet: "Hello World"})}
` instead. -2) Use [Class Components](components.md#class-component-state) instead. Class Components will not show any errors. But TypeScript will not be able to autocomplete or inspect attributes (in this example `greet` would be unknown when used in `ParentComponent`). -3) Create a "translation function" (see `TranslatedComponent()` in the example below) to trick TypeScript. +1. Instead of `
`, use `
{m(ChildComponent, {greet: "Hello World"})}
` instead. +2. Use [class components](components.md#class-component-state) instead. Class components will not show any errors. But TypeScript will not be able to autocomplete or inspect attributes (in this example `greet` would be unknown when used in `ParentComponent`). +3. Create a "translation function" (see `TsClosureComponent()` in the example below) to trick TypeScript. The following code will work without errors: ```typescript jsx /** - * Use TranslatedComponent to create Closure Components that can be inspected by TypeScript. + * Use TsClosureComponent to create closure components that can be inspected by TypeScript. */ -export function TranslatedComponent(create: m.ClosureComponent) { +export function TsClosureComponent(create: Mithril.ClosureComponent) { return create as any as ( (attrs: T & Mithril.CommonAttributes) => JSX.Element ) @@ -313,16 +313,16 @@ export function TranslatedComponent(create: m.ClosureComponent) { interface Attributes { greet: string } -//We slightly altered the definition of ChildComponent by using TranslatedComponent() -const ChildComponent = TranslationdComponent(vNode => { +// We slightly altered the definition of `ChildComponent` by using `TsClosureComponent` +const ChildComponent = TsClosureComponent(vNode => { return { - view:
{vNode.attrs.greet}
+ view: () =>
{vNode.attrs.greet}
}; }) function ParentComponent() { return { - view:
+ view: () =>
}; } @@ -332,12 +332,14 @@ When you need generics for your closure component, you can use the following def ```typescript jsx function ChildComponentImpl() { - ... + // ... } -const ChildComponent = TranslatedComponent(ChildComponentImpl); //for TranslatedComponent, see above +const ChildComponent = TsClosureComponent(ChildComponentImpl); //for TsClosureComponent, see above -const jsx =
/>
+const jsx =
+ /> +
``` From 3fc4e237960d700f6dba96e7c81d291578721511 Mon Sep 17 00:00:00 2001 From: JodliDev Date: Sat, 9 Aug 2025 12:34:25 +0200 Subject: [PATCH 10/23] Improve wording in closure component introduction --- docs/jsx.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/jsx.md b/docs/jsx.md index b757da0b..05a4270c 100644 --- a/docs/jsx.md +++ b/docs/jsx.md @@ -262,9 +262,10 @@ Add `jsx` and `jsxFactory` to `compilerOptions` in your `tsconfig.json`: This setup should be enough to get most JSX functionality working. #### Using closure components in TSX -Because of https://github.com/microsoft/TypeScript/issues/21699, we advise against using [closure components](components.md#closure-component-state) in TypeScript. Either use [class components](components.md#class-component-state) or Hyperscript instead (see a list of alternatives below). -If you really want to use them, you have to add a simple hack to trick the TypeScript error checking to resolve faulty errors. -When using closure components in JSX, TypeScript only expects an attribute object as a parameter. But Mithril.js provides a `Vnode` object instead. This leads to the editor showing faulty parameters even though the JSX would compile correctly. +>Because of https://github.com/microsoft/TypeScript/issues/21699, we advise against using [closure components](components.md#closure-component-state) in TypeScript for now. Either use [class components](components.md#class-component-state) without attribute inspection or Hyperscript instead (see the list of alternatives below the code example). + +TypeScript only expects an attribute object as a parameter. But Mithril.js provides a `Vnode` object instead. This leads to the editor showing faulty parameters even though the JSX would compile correctly. If you want to use closure components in TypeScript, you need to trick the TypeScript error checking. + For example, if you try to compile this code: @@ -280,7 +281,7 @@ function ChildComponent(vNode: Vnode): m.Component { function ParentComponent() { return { - view:
//This line will compile correctly but shows the errors above + view:
//This line will compile correctly but shows the errors bellow }; } ``` From dd5a169397eb63cd0156419edd0e8042d424de5c Mon Sep 17 00:00:00 2001 From: JodliDev Date: Wed, 13 Aug 2025 08:39:37 +0200 Subject: [PATCH 11/23] Grammar nit Co-authored-by: Claudia Meadows --- docs/jsx.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/jsx.md b/docs/jsx.md index 05a4270c..348cfa48 100644 --- a/docs/jsx.md +++ b/docs/jsx.md @@ -62,7 +62,7 @@ m.render(document.body, ) ### Setup JSX -When using JavaScript, the simplest way to use JSX is via a [Babel](https://babeljs.io/) plugin. (For TypeScript follow the [instructions below](#setup-tsx-jsx-in-typescript)). +When using JavaScript, the simplest way to use JSX is via a [Babel](https://babeljs.io/) plugin. (For TypeScript, follow the [instructions below](#setup-tsx-jsx-in-typescript).) Babel requires npm, which is automatically installed when you install [Node.js](https://nodejs.org/en/). Once npm is installed, create a project folder and run this command: From 07ea4f3301725141df3917a1a3e0c3e3818ba88d Mon Sep 17 00:00:00 2001 From: JodliDev Date: Wed, 13 Aug 2025 08:51:43 +0200 Subject: [PATCH 12/23] Rewording Co-authored-by: Claudia Meadows --- docs/jsx.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/jsx.md b/docs/jsx.md index 348cfa48..d7c1a09f 100644 --- a/docs/jsx.md +++ b/docs/jsx.md @@ -243,9 +243,9 @@ See [the Webpack docs](https://webpack.js.org/plugins/provide-plugin/) for more --- -### Setup TSX (JSX in TypeScript) +### Setup for TypeScript -When using [TypeScript](https://www.typescriptlang.org/), all you need to do is tell TypeScript how to handle JSX code correctly. Since TypeScript can transpile JSX and TSX on its own, you don't need any other tools like Babel to do it for you. (More information can be found about JSX in TypeScript [here](https://www.typescriptlang.org/docs/handbook/jsx.html).) +When using [TypeScript](https://www.typescriptlang.org/), all you need to do is tell TypeScript how to handle JSX code correctly. Since TypeScript can transpile JSX on its own, you don't need any other tools like Babel to do it for you. (More information can be found [here](https://www.typescriptlang.org/docs/handbook/jsx.html).) Add `jsx` and `jsxFactory` to `compilerOptions` in your `tsconfig.json`: From bef2403b530b439bbd81cc365ab0e792c6e12976 Mon Sep 17 00:00:00 2001 From: JodliDev Date: Wed, 13 Aug 2025 08:52:25 +0200 Subject: [PATCH 13/23] hyperscript clarification Co-authored-by: Claudia Meadows --- docs/jsx.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/jsx.md b/docs/jsx.md index d7c1a09f..8dd4b1ff 100644 --- a/docs/jsx.md +++ b/docs/jsx.md @@ -294,7 +294,7 @@ TS2786: LoadingSpinner cannot be used as a JSX component. ``` There are several options to circumvent that problem: -1. Instead of `
`, use `
{m(ChildComponent, {greet: "Hello World"})}
` instead. +1. Instead of `
`, use [hyperscript](hyperscript.md) instead: `
{m(ChildComponent, {greet: "Hello World"})}
`. 2. Use [class components](components.md#class-component-state) instead. Class components will not show any errors. But TypeScript will not be able to autocomplete or inspect attributes (in this example `greet` would be unknown when used in `ParentComponent`). 3. Create a "translation function" (see `TsClosureComponent()` in the example below) to trick TypeScript. From d77bb6454f8bfb911935bc68bc4e7ad8093dfa0f Mon Sep 17 00:00:00 2001 From: JodliDev Date: Wed, 13 Aug 2025 08:54:43 +0200 Subject: [PATCH 14/23] Improve wording in React section Co-authored-by: Claudia Meadows --- docs/jsx.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/jsx.md b/docs/jsx.md index 8dd4b1ff..c284c4b4 100644 --- a/docs/jsx.md +++ b/docs/jsx.md @@ -350,7 +350,7 @@ JSX in Mithril.js has some subtle but important differences compared to React's #### Attribute and style property case conventions -React requires you use the camel-cased DOM property names instead of HTML attribute names for all attributes other than `data-*` and `aria-*` attributes. For example using `className` instead of `class` and `htmlFor` instead of `for`. In Mithril.js, it's more idiomatic to use the lowercase HTML attribute names instead. Mithril.js always falls back to setting attributes if a property doesn't exist which aligns more intuitively with HTML. Note that in most cases, the DOM property and HTML attribute names are either the same or very similar. For example `value`/`checked` for inputs and the `tabindex` global attribute vs the `elem.tabIndex` property on HTML elements. Very rarely do they differ beyond case: the `elem.className` property for the `class` attribute or the `elem.htmlFor` property for the `for` attribute are among the few exceptions. +React requires you use the camel-cased DOM property names instead of HTML attribute names for all attributes other than `data-*` and `aria-*` attributes. For example, with React, you have to use `className` instead of `class` and `htmlFor` instead of `for`. In Mithril.js, it's more idiomatic to use the lowercase HTML attribute names instead. Mithril.js always falls back to `setAttribute` if a property doesn't exist, letting you just always use HTML attributes. Note that in most cases, the DOM property and HTML attribute names are either the same or very similar. For example, the property names for `value` and `checked` for inputs are the same as the attribute names for them, and the property name for the global `tabindex` HTML attribute is just `tabIndex` property. There's only a few exceptions, like the `className` property for the global `class` attribute and the `htmlFor` property for the `for` HTML form control attribute. Similarly, React always uses the camel-cased style property names exposed in the DOM via properties of `elem.style` (like `cssHeight` and `backgroundColor`). Mithril.js supports both that and the kebab-cased CSS property names (like `height` and `background-color`) and idiomatically prefers the latter. Only `cssHeight`, `cssFloat`, and vendor-prefixed properties differ in more than case. From 18c8d030b6b1a2b5491f946771d248eeb77cb2d9 Mon Sep 17 00:00:00 2001 From: JodliDev Date: Wed, 13 Aug 2025 08:55:08 +0200 Subject: [PATCH 15/23] Fix faulty syntax Co-authored-by: Claudia Meadows --- docs/jsx.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/jsx.md b/docs/jsx.md index c284c4b4..1b371541 100644 --- a/docs/jsx.md +++ b/docs/jsx.md @@ -275,7 +275,7 @@ interface Attributes { } function ChildComponent(vNode: Vnode): m.Component { return { - view:
{vNode.attrs.greet}
+ view: () =>
{vNode.attrs.greet}
}; } From 1b349479e3fc6aa293b0ea88dbdc614bce7d3fa6 Mon Sep 17 00:00:00 2001 From: JodliDev Date: Wed, 13 Aug 2025 08:55:47 +0200 Subject: [PATCH 16/23] Improve readability in code example Co-authored-by: Claudia Meadows --- docs/jsx.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/jsx.md b/docs/jsx.md index 1b371541..40afda57 100644 --- a/docs/jsx.md +++ b/docs/jsx.md @@ -323,7 +323,9 @@ const ChildComponent = TsClosureComponent(vNode => { function ParentComponent() { return { - view: () =>
+ view: () =>
+ +
}; } From b26bd4a681335d973681323c09c64ffe96f634c6 Mon Sep 17 00:00:00 2001 From: JodliDev Date: Wed, 13 Aug 2025 08:56:13 +0200 Subject: [PATCH 17/23] Fix error message example Co-authored-by: Claudia Meadows --- docs/jsx.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/jsx.md b/docs/jsx.md index 40afda57..42a4b8d8 100644 --- a/docs/jsx.md +++ b/docs/jsx.md @@ -290,7 +290,7 @@ TypeScript will report this error: ``` TS2739: Type { greet: string; } is missing the following properties from type Vnode<{}, {}>: tag, attrs, state -TS2786: LoadingSpinner cannot be used as a JSX component. +TS2786: ChildComponent cannot be used as a JSX component. ``` There are several options to circumvent that problem: From be7319517a9fd6eb2bc7c2a8eead9d10bd1c7a37 Mon Sep 17 00:00:00 2001 From: JodliDev Date: Wed, 13 Aug 2025 08:57:38 +0200 Subject: [PATCH 18/23] Shorten code format Co-authored-by: Claudia Meadows --- docs/jsx.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/jsx.md b/docs/jsx.md index 42a4b8d8..a25d8f97 100644 --- a/docs/jsx.md +++ b/docs/jsx.md @@ -269,7 +269,7 @@ TypeScript only expects an attribute object as a parameter. But Mithril.js provi For example, if you try to compile this code: -```typescript jsx +```tsx interface Attributes { greet: string } From 84b6c46be83b78fa2eb9d222114fbc6f30acb49f Mon Sep 17 00:00:00 2001 From: JodliDev Date: Wed, 13 Aug 2025 08:57:50 +0200 Subject: [PATCH 19/23] Shorten code format Co-authored-by: Claudia Meadows --- docs/jsx.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/jsx.md b/docs/jsx.md index a25d8f97..a40538d3 100644 --- a/docs/jsx.md +++ b/docs/jsx.md @@ -300,7 +300,7 @@ There are several options to circumvent that problem: The following code will work without errors: -```typescript jsx +```tsx /** * Use TsClosureComponent to create closure components that can be inspected by TypeScript. */ From 039d85fad87389b9300800a2fea58e92b82a2505 Mon Sep 17 00:00:00 2001 From: JodliDev Date: Wed, 13 Aug 2025 08:59:03 +0200 Subject: [PATCH 20/23] Fix wording in React section Co-authored-by: Claudia Meadows --- docs/jsx.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/jsx.md b/docs/jsx.md index a40538d3..f6ab7738 100644 --- a/docs/jsx.md +++ b/docs/jsx.md @@ -354,7 +354,7 @@ JSX in Mithril.js has some subtle but important differences compared to React's React requires you use the camel-cased DOM property names instead of HTML attribute names for all attributes other than `data-*` and `aria-*` attributes. For example, with React, you have to use `className` instead of `class` and `htmlFor` instead of `for`. In Mithril.js, it's more idiomatic to use the lowercase HTML attribute names instead. Mithril.js always falls back to `setAttribute` if a property doesn't exist, letting you just always use HTML attributes. Note that in most cases, the DOM property and HTML attribute names are either the same or very similar. For example, the property names for `value` and `checked` for inputs are the same as the attribute names for them, and the property name for the global `tabindex` HTML attribute is just `tabIndex` property. There's only a few exceptions, like the `className` property for the global `class` attribute and the `htmlFor` property for the `for` HTML form control attribute. -Similarly, React always uses the camel-cased style property names exposed in the DOM via properties of `elem.style` (like `cssHeight` and `backgroundColor`). Mithril.js supports both that and the kebab-cased CSS property names (like `height` and `background-color`) and idiomatically prefers the latter. Only `cssHeight`, `cssFloat`, and vendor-prefixed properties differ in more than case. +Similarly, React always uses the camel-cased style property names exposed in the DOM via properties of `elem.style` (like `cssHeight` and `backgroundColor`). Mithril.js supports both that and the kebab-cased CSS property names (like `height` and `background-color`), and the hyphenated CSS names are the preferred idiom. Only `cssHeight`, `cssFloat`, and some vendor-prefixed properties differ in more than case. #### DOM events From 3c5e9e845726152f0dee7e2ef41c0f10d2a60da4 Mon Sep 17 00:00:00 2001 From: JodliDev Date: Wed, 13 Aug 2025 09:26:30 +0200 Subject: [PATCH 21/23] Fix wording in React section 2 Co-authored-by: Claudia Meadows --- docs/jsx.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/jsx.md b/docs/jsx.md index f6ab7738..ede2f38e 100644 --- a/docs/jsx.md +++ b/docs/jsx.md @@ -348,19 +348,19 @@ const jsx =
### Differences with React -JSX in Mithril.js has some subtle but important differences compared to React's JSX. +JSX in Mithril.js has some subtle but important differences compared to JSX in React. #### Attribute and style property case conventions -React requires you use the camel-cased DOM property names instead of HTML attribute names for all attributes other than `data-*` and `aria-*` attributes. For example, with React, you have to use `className` instead of `class` and `htmlFor` instead of `for`. In Mithril.js, it's more idiomatic to use the lowercase HTML attribute names instead. Mithril.js always falls back to `setAttribute` if a property doesn't exist, letting you just always use HTML attributes. Note that in most cases, the DOM property and HTML attribute names are either the same or very similar. For example, the property names for `value` and `checked` for inputs are the same as the attribute names for them, and the property name for the global `tabindex` HTML attribute is just `tabIndex` property. There's only a few exceptions, like the `className` property for the global `class` attribute and the `htmlFor` property for the `for` HTML form control attribute. +React requires you use the camel-cased DOM property names instead of HTML attribute names for all attributes other than `data-*` and `aria-*` attributes. For example, with React, you have to use `className` instead of `class` and `htmlFor` instead of `for`. In Mithril.js, it's more idiomatic to use the lowercase HTML attribute names instead. Mithril.js always falls back to `setAttribute` if a property doesn't exist, letting you just always use HTML attributes. Note that in most cases, the DOM property and HTML attribute names are either the same or very similar. For example, the property names for `value` and `checked` for inputs are the same as the attribute names for them, and the property name for the global `tabindex` HTML attribute is just `tabIndex` property. There's only a few exceptions, like the `className` property for the global `class` attribute and the `htmlFor` property for the `for` HTML form control attribute. Similarly, React always uses the camel-cased style property names exposed in the DOM via properties of `elem.style` (like `cssHeight` and `backgroundColor`). Mithril.js supports both that and the kebab-cased CSS property names (like `height` and `background-color`), and the hyphenated CSS names are the preferred idiom. Only `cssHeight`, `cssFloat`, and some vendor-prefixed properties differ in more than case. #### DOM events -React upper-cases the first character of all event handlers: `onClick` listens for `click` events and `onSubmit` for `submit` events. Some are further altered as they're multiple words concatenated together. For instance, `onMouseMove` listens for `mousemove` events. Mithril.js does not do this case mapping but instead just prepends `on` to the native event, so you'd add listeners for `onclick` and `onmousemove` to listen to those two events respectively. This corresponds much more closely to HTML's naming scheme and is much more intuitive if you come from an HTML or vanilla DOM background. +React upper-cases the first character of all event handlers: `onClick` listens for `click` events and `onSubmit` for `submit` events. Some are further altered as their multiple words concatenated together. For instance, `onMouseMove` listens for `mousemove` events. Mithril.js does not do this case mapping but instead just prepends `on` to the native event, so you'd add listeners for `onclick` and `onmousemove` to listen to those two events respectively. This corresponds much more closely to HTML's naming scheme and is much more intuitive if you come from an HTML or vanilla DOM background. -React supports scheduling event listeners during the capture phase (in the first pass, out to in, as opposed to the default bubble phase going in to out in the second pass) by appending `Capture` to that event. Mithril.js currently lacks such functionality, but it could gain this in the future. If this is necessary you can manually add and remove your own listeners in [lifecycle hooks](lifecycle-methods.md). +React supports scheduling event listeners during the capture phase, as events first descend from the top to their target (as opposed to the bubble phase, when events ascend back out to the top), by appending `Capture` to that event. Mithril.js currently has no equivalent for this. If you need such event listeners, you can manually add and remove your own listeners in [lifecycle hooks](lifecycle-methods.md). --- From 1f0e45d4580333c2f565b3eed3f8e0d576b7a42d Mon Sep 17 00:00:00 2001 From: JodliDev Date: Wed, 13 Aug 2025 09:29:21 +0200 Subject: [PATCH 22/23] Fix issues in TSX section. Co-authored-by: Claudia Meadows --- docs/jsx.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/jsx.md b/docs/jsx.md index ede2f38e..8ed8a9c2 100644 --- a/docs/jsx.md +++ b/docs/jsx.md @@ -281,7 +281,9 @@ function ChildComponent(vNode: Vnode): m.Component { function ParentComponent() { return { - view:
//This line will compile correctly but shows the errors bellow + view: () =>
+ +
}; } ``` @@ -293,17 +295,15 @@ TS2739: Type { greet: string; } is missing the following properties from type Vn TS2786: ChildComponent cannot be used as a JSX component. ``` -There are several options to circumvent that problem: +There are a few options to circumvent that problem: 1. Instead of `
`, use [hyperscript](hyperscript.md) instead: `
{m(ChildComponent, {greet: "Hello World"})}
`. 2. Use [class components](components.md#class-component-state) instead. Class components will not show any errors. But TypeScript will not be able to autocomplete or inspect attributes (in this example `greet` would be unknown when used in `ParentComponent`). -3. Create a "translation function" (see `TsClosureComponent()` in the example below) to trick TypeScript. +3. Create a "translation function" (like `TsClosureComponent()` in the example below) to trick TypeScript. The following code will work without errors: ```tsx -/** - * Use TsClosureComponent to create closure components that can be inspected by TypeScript. - */ +// Use this helper to force TypeScript to treat closure components as valid JSX components export function TsClosureComponent(create: Mithril.ClosureComponent) { return create as any as ( (attrs: T & Mithril.CommonAttributes) => JSX.Element @@ -331,14 +331,14 @@ function ParentComponent() { ``` -When you need generics for your closure component, you can use the following definition style: +This also works with generics, as long as you define the generic as part of the wrapped component: ```typescript jsx function ChildComponentImpl() { // ... } -const ChildComponent = TsClosureComponent(ChildComponentImpl); //for TsClosureComponent, see above +const ChildComponent = TsClosureComponent(ChildComponentImpl); const jsx =
/> From db934ea58e95db5fcd1f86ca8e0dcbd603299c25 Mon Sep 17 00:00:00 2001 From: JodliDev Date: Fri, 29 Aug 2025 10:35:49 +0200 Subject: [PATCH 23/23] Fix usage of TSX in headers. Co-authored-by: Claudia Meadows --- docs/jsx.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/jsx.md b/docs/jsx.md index 8ed8a9c2..83c542ab 100644 --- a/docs/jsx.md +++ b/docs/jsx.md @@ -5,11 +5,11 @@ Explanation, examples, and build notes on how to use JSX in your Mithril.js-base # JSX - [Description](#description) -- [Setup JSX](#setup-jsx) +- [Setup for JavaScript](#setup-for-javascript) - [Production build](#production-build) - [Using Babel with Webpack](#using-babel-with-webpack) -- [Setup TSX](#setup-tsx-jsx-in-typescript) -- [Using Closure Components in TSX](#using-closure-components-in-tsx) +- [Setup for TypeScript](#setup-for-typescript) +- [Using Closure Components in TypeScript with JSX](#using-closure-components-in-typescript-with-jsx) - [Differences with React](#differences-with-react) - [JSX vs hyperscript](#jsx-vs-hyperscript) - [Tips and Tricks](#tips-and-tricks) @@ -60,7 +60,7 @@ m.render(document.body, ) --- -### Setup JSX +### Setup for JavaScript When using JavaScript, the simplest way to use JSX is via a [Babel](https://babeljs.io/) plugin. (For TypeScript, follow the [instructions below](#setup-tsx-jsx-in-typescript).) @@ -261,7 +261,7 @@ Add `jsx` and `jsxFactory` to `compilerOptions` in your `tsconfig.json`: This setup should be enough to get most JSX functionality working. -#### Using closure components in TSX +#### Using closure components in TypeScript with JSX >Because of https://github.com/microsoft/TypeScript/issues/21699, we advise against using [closure components](components.md#closure-component-state) in TypeScript for now. Either use [class components](components.md#class-component-state) without attribute inspection or Hyperscript instead (see the list of alternatives below the code example). TypeScript only expects an attribute object as a parameter. But Mithril.js provides a `Vnode` object instead. This leads to the editor showing faulty parameters even though the JSX would compile correctly. If you want to use closure components in TypeScript, you need to trick the TypeScript error checking.