Using JSX with Vue.js and TypeScript

Hajime Yamasaki Vukelic
4 min readMay 18, 2017

--

How and why of using JSX with Vue.js

I know there are at least some Vue.js fans that would hate the notion of putting HTML in JavaScript. The .vue files that puts JavaScript in <script> tags “where it belongs” is the closest they would ever go towards bundling template markup and code. This article is also for them. I will try explain why their concerns are kind of missing the target. Then I will explain how to get started with JSX and TypeScript on Vue.js.

No, not really, it’s not HTML. It’s a different syntax for making the same call to h().

What JSX really is

If you already know what JSX is, feel free to skip to the next section where I talk about how the set-up is done.

JSX is not HTML. Period. Trying to compare JSX to HTML is kinda like trying to compare JavaScript to Java. They might look similar but they are completely different beasts.

In order to understand JSX, you first need to understand Vue.js’ render function.

As you already know, Vue.js can either use compiled templates or render functions. Compiled templates are easy to reason about (if you know HTML). They are what they are: HTML markup in string form. They compile to JavaScript. Render functions are, as the name suggests, already code, so there is no compilation (runtime or otherwise). They generate virtual DOM trees.

Typically, you would write render functions like so:

@Component
class Foo extends Vue {
name = "foo";
public render(h: CreateElement): VNode {
return h(
"div",
{
"class": "foo",
attribs: {"data-id": "12"}
},
[this.name]
);
}
}

This renders into <div class="foo" data-id="12">foo</div>. It’s not so difficult to type, but it does require some thought before you get what it’s doing. I myself was able to write it after 15 minutes of practice. I don’t think it’s such a bad way to write it, but it doesn’t look like HTML, and I can appreciate that someone may feel like it might have come out of someone’s posterior.

This API is low-level. It is meant to stay underneath the sugar-coating of the compiled templates, and not be used directly (at least that’s my guess; I can’t imagine why anyone would make this an API for day-to-day usage).

Just snap your finger. The ugly code above, turns into something like this:

@Component
class Foo extends Vue {
name = "foo";
public render(h: CreateElement): VNode {
return (
<div class="foo" attribs={{"data-id": "12"}}>
{this.name}
</div>
);
}
}

It’s kinda like HTML, right? If you squint very hard. And look the other way. No, not really, it’s not HTML. It’s a different syntax for making the same call to h(). It’s no more and no less: strictly just syntax. Under the hood, you are still calling the functions, and you have to know the function signature in order to successfully call it. You cannot, for example, do something like <div class="foo" data-id="12">. That will not work.

As a side note, React has a much nicer syntax for writing JSX (they invented it, so it figures).

If you are able to wrap your head around it, though, you will realize that it has some major advantages. Unlike a template language, which is basically a DSL for making templates, you have the full power of JavaScript.

How to make JSX work with TypeScript

Now that we are in the clear regarding what JSX is in Vue.js context, let’s talk about how to get it working with TypeScript.

It is generally quite straightforward, but you do need to watch out for a few things that were sort of hard to find.

Step 1: Configure the TypeScript compiler

The tsconfig.json file must contain the following compiler options:

{
"compilerOptions": {
....
"jsx": "react",
"jsxFactory": "h"
}
}

The jsx option turns on JSX. The second option, jsxFactory, tells TypeScript to use h() calls for JSX tags. By default it is React.createElement(), which, you’ve probably guessed it, only works with React.

Step 2: Add the JSX type definitions

Somewhere in your project, you need to include this type definition:

import Vue, { VNode } from "vue";declare global {
namespace JSX {
interface Element extends VNode {}
interface ElementClass extends Vue {}
interface IntrinsicElements {
[elem: string]: any;
}
}
}

The way I add this to my project is, I normally have a file called app.d.ts in the root of the project’s sources, and I add a line like this in there:

///<reference path="./types/jsx.d.ts"/>

Alternatively, you can add a typesRoot key to your tsconfig.json and point it to a directory where you will keep the jsx.d.ts file:

{
"compilerOptions": {
...
"typesRoot": ["./node_modules/@types", "./types"]
}
}

Step 3: Rename the TypeScript module to .tsx

The modules that contain JSX must have a .tsx extension. Don’t ask why.

Step 4: Read up on the API

The API is not easy. In fact, it’s quite ugly. But you have to know it in order to be effective writing JSX.

How to make it better

This is a thought I’m playing with in my head right now. The whole experience can be greatly improved if the h() API were a bit nicer to start with.

For example, if it could intelligently differentiate between HTML attributes, props, bindings, styles, and classes, we would not have to nest them. And so on. Since h() is just a function in the local scope, I imagine we could get pretty creative with it.

That’s just some homework or a pet project.

Conclusion

Hopefully, you now at least understand what JSX is and whether you want to use it or not. If you are sold on the idea, I hopefully helped you figure out how to integrate it into TypeScript.

If you found this article useful, don’t forget to recommend it and share it.

--

--

Hajime Yamasaki Vukelic

Helping build an inclusive and accessible web. Web developer and writer. Sometimes annoying, but mostly just looking to share knowledge.