Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 118 additions & 3 deletions docs/jsx.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -58,9 +61,9 @@ m.render(document.body, <MyComponent />)

---

### 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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's move the "when using TypeScript" to the next section.

Suggested change
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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I shortened it. But I do think its wise to link to TSX in this secion. A lot of times the term "JSX" is used for "TSX" interchangably (so is "JavaScript" for "TypeScript") and TypeScript users might stumble over this section and follow the wrong instructions. Tell me if you find that unnecessary and I should fully remove it


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:

Expand Down Expand Up @@ -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<Attributes>): m.Component<Attributes> {
return {
view: <div>{vNode.attrs.greet}</div>
};
}

function ParentComponent() {
return {
view: <div><ChildComponent greet="Hello World"/></div> //This line will compile correctly but shows the errors above
};
}
```

There are several options to circumvent that problem:
1) Instead of `<div><ChildComponent greet="Hello World"/></div>`, use `<div>{m(ChildComponent, {greet: "Hello World"})}</div>` 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<T>(create: m.ClosureComponent<T>) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you come up with a shorter name for this helper? Maybe something like component could work.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that I chose a poor name for that helper function. But I think component would not be a good idea since it already exists in Mithril.js and also does not really make it clear what it does. I changed it to TsClosureComponent which hopefully is a bit more clear. It is not really shorter so I could also change it to TsClosure for example. But I find TsClosureComponent more descriptive.
Let me know what you think

return (attrs: T) => {
const vNode = attrs as m.Vnode<T>;
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<Attributes>(vNode => {
return {
view: <div>{vNode.attrs.greet}</div>
};
})

function ParentComponent() {
return {
view: <div><ChildComponent greet="Hello World"/></div>
};
}

```

When you need generics for your closure component, you can use the following definition style:

```typescript jsx
function ChildComponentImpl<T>() {
...
}

const ChildComponent = TranslatedComponent(ChildComponentImpl); //for TranslatedComponent, see above

const jsx = <div><ChildComponent<SomeClass>/></div>
```


### Differences with React

JSX in Mithril has some subtle but important differences compared to React's JSX.
Expand Down