JSX Everywhere

Today I want to talk about JSX. Some people love, some hate, some just do not understand it. Bigger problem of it is React. JSX is highly coupled to React and that is so hard to use it today without looking back to React. Even tough Facebook is trying to make JSX separate, their React is still one actual consumer of JSX.

There is number of other projects which are trying to utilize JSX, but most of the time it ends by transpiling it to React’s output format, just with changed pragma. Besides that there is no unified JSX output format. React’s format became de-facto standard which forces other consumers to follow it, even when that format dramatically changes. Hopefully here Babel commes in.

Intermediate Representation

Of course, writing transipilation plugin for Babel every time for every new consumer is not sufficient and is not that easy for those who is not working with AST and transpilation everyday. One thing which came to my mind is IR, an unified JSX output which is independent from its further consumer and particular implementation of that consumer. I think this is very simple idea — JS output without any dependencies and which can be utilized by everyone.

{
tag: ‘div’,
children: [],
props: {}
}

This is how it may looks like. Would not it be better to not care if, for example, React is in your scope or not? Or other library, or framework..

Recently, Google announced their Incremental DOM — an alternative to vDOM. As follows, people started thinking about using it with JSX, even started some babel-plugin implementations. As you can imagine, such approach assumes that all iDOM methods are in the scope. Would you include up to 7 method in every .jsx file? I think not.

Implementation

Taking all of this, I decided to start working on JSX-IR, an independent output format of JSX and its realization. I split it on different building blocks where each of it has their on responsibility:

  • Transpiler — is responsible for producing JSX-IR according to the spec.
  • Runtime — is responsible for providing common interface for consumers (Renderers).
  • Renderers — actual consumers of JSX-IR.

Runtime is a bridge between generated JSX-IR output and Renderers. This allows really easy way of implementing new consumers of JSX. For example, I was able to make Incremental DOM Renderer just in a few hours, because I had not to worry about tranpilation and its handling by the runtime. Since those two parts are already tested, I just had to do one thing — implement iDOM Renderer and test its calls.

As for now, I have implemented following Renderers, which I think are most important:

Testing

Testing with JSX-IR becomes pretty easy, since we are using simple JS objects as output, we do not need to write complex tests for output format, comparing strings or implementing mock objects/methods for generated output. We can just test it directly with objects:

assert.deepEqual(<div />, {
tag: ‘div’,
props: null,
children: null
});

Then to test Runtime we can write Renderer which returns back JSX-IR format and test it easily again:

assert.deepEqual(render(<div />), {
tag: ‘div’,
props: null,
children: null
});

Next, Renderers will just need to test their own implementation and generation.

TypeScript

As you may know, TypeScript recently added support of JSX which as I understand will be in stable with 1.6 version. This is really great news if you are TypeScript developer. One great thing is that they tried to support JSX in general, not only React’s JSX, but as you may guess that did not really worked. They have support of “jsx”: “preserve” option which tells to compiler to not touch JSX and instead leave it for the next compiler. This is exactly what we want and this is super cool to have it in TypeScript (many thanks for it).

However, as it turns out, TypeScript’s type system for JSX is actually for React. For example, you cannot have built-in tags which is not all-letters-lower-case because in React’s world all lower case tags are built-ins and something which start with uppercase is Class/Component. This also means that with TypeScript you cannot have custom lowercase tags. See this issue for more details.

Another thing which couples TypeScript to React is that for custom tags you should use Classes/Components and cannot use plain functions. For example, with JSX-IR you can do this:

var Custom = () => {
return <span>Custom</span>
};
<div>
<Custom />
</div>

Which will actually put <span> with text “Custom” into <div>.

Unfortunately this is not possible in TypeScript. Until all of that is fixed, I do not think JSX is quite useful with TypeScript… unless your are using React, of course.

Conclusion

I can honestly say that I love JSX. Some time ago I thought about similar thing, but had no chance to implement it. Many thanks to Facebook folks for making it and sharing with all of us. Hope, with JSX-IR anyone will be able to use JSX everywhere, with any framework/architecture of choice. Thanks for reading!

References