In a quest to explore improvements over common JSX transformers I have managed to find great performance able to compete with template literal based libraries.
In this document I would like to describe, via JS itself, and as PoC, how JSX could be both rebranded as ESX and improved.
Differently from E4X, but also differently from JSX, this proposal leaves developers provide their own "render" implementation, freeing ESX usage from any previous attempt to confine JSX or E4X into the DOM world, where it's been proven, in the JSX case, it's not really where it belongs, as it can be used as neutral DSL.
This document describes all the moving part of ESX in a way that:
- there is no DOM at all involved, only new primitives introduced by JSX
- all relevant details around each part of the transformation are described through simple objects, here represented as classes instances, but these easily work just as object literals (easy polyfills via transformers)
- no extra scope pollution is needed, hence no
jsxPragma
orjsxFragment
around is required at all - all classes can be used just as types to infer, as oppsite of being really classes ... no clashing in the logic can happen neither
- hints to "parse-once" through templates and/or Components are all over the place, making usignal like alternative implementations possible, but also any SSR related project can benefit from these
// unique identifier for the fragment
const ESXFragment = Symbol('ESX.Fragment');
// basic ESX value wrapper
class ESXValue {
static Static = 1 << 0;
static Mixed = 1 << 1;
static Runtime = 1 << 2;
/**
* @param {Object} details
* @param {number} details.type ESXValue.Static | ESXValue.Mixed | ESXValue.Runtime
* @param {any} details.value the reference carried through this instance
*/
constructor({type, value}) {
this.type = type;
this.value = value;
}
}
// specialized ESX property value
class ESXProperty extends ESXValue {
/**
* @param {Object} details
* @param {number} details.type ESXValue.Static | ESXValue.Mixed | ESXValue.Runtime
* @param {string} details.name the entry carried through this reference
* @param {any} details.value the entry carried through this reference
*/
constructor({type, name, value}) {
super({type, value});
this.name = name;
}
}
// Static, Mixed,or Runtime properties wrapper
class ESXProperties {
/**
* @param {Object} details
* @param {number} details.type ESXValue.Static | ESXValue.Mixed | ESXValue.Runtime
* @param {ESXProperty[]} details.values
*/
constructor({type, values}) {
this.type = type;
this.values = values;
}
}
// Element, Fragment, or Component wrapper
class ESXEntry {
static Element = 1 << 0;
static Fragment = 1 << 1;
static Component = 1 << 2;
/**
* @param {Object} entry
* @param {numer} entry.type ESXEntry.Element | ESXEntry.Fragment | ESXEntry.Component
* @param {string | symbol | Function} entry.value tag name | ESXFragment | Component
* @param {ESXProperties | null} properties
* @param {...ESXValue[]} children
*/
constructor({type, value}, properties, ...children) {
this.type = type;
this.value = value;
this.properties = properties;
this.children = children;
}
}
// Outer JSX template elements
class ESXTemplate {
/**
* @param {Object} template
* @param {object} template.id unique template reference
* @param {ESXEntry} template.value unique template reference
*/
constructor({id, value}) {
this.id = id;
this.value = value;
}
}
// ESX
const div = <div />;
// internal representation
const templateReference1 = {};
const div = new ESXTemplate({
id: templateReference1,
value: new ESXEntry(
{
type: ESXEntry.Element,
value: 'div'
},
null
)
});
// ESX
const div = <div a="a" b={"b"}><p>c</p></div>;
// internal representation
const templateReference2 = {};
const div = new ESXTemplate({
id: templateReference2,
value: new ESXEntry(
{
type: ESXEntry.Element,
value: 'div'
},
new ESXProperties({
// Static: all properties are static
// Mixed: properties can also be Static
// Runtime: all properties are runtime, such as
// <div even="though" {...spread} />
// case that can overwrite also static props
type: ESXProperty.Mixed,
values: [
new ESXProperty({
type: ESXProperty.Static,
name: 'a',
value: 'a'
}),
new ESXProperty({
type: ESXProperty.Runtime,
name: 'b',
value: 'b'
})
]
}),
new ESXValue({
type: ESXValue.Static,
value: new ESXEntry(
{
type: ESXEntry.Element,
value: 'p'
},
null,
new ESXValue({
type: ESXValue.Static,
value: 'c'
})
)
})
)
});
// ESX
function MyComponent(...args) {
return (
<>
{'A'},
{'B'}
</>
);
}
const props = {a: '', b: 'b'};
const component = <MyComponent a="a" {...props} />;
// internal representation
const templateReference3 = {};
const templateReference4 = {};
function MyComponent() {
return new ESXTemplate({
id: templateReference3,
value: new ESXEntry(
{
type: ESXEntry.Fragment,
value: ESXFragment
},
null,
// children as interpolations
new ESXValue({
type: ESXValue.Runtime,
value: 'A'
}),
//the static comma interpolations separator
new ESXValue({
type: ESXValue.Static,
value: ', '
}),
new ESXValue({
type: ESXValue.Runtime,
value: 'B'
})
)
});
}
const props = {a: '', b: 'b'};
const component = new ESXTemplate({
id: templateReference4,
value: new ESXEntry(
{
type: ESXEntry.Component,
value: MyComponent
},
new ESXProperties({
type: ESXProperty.Runtime,
values: [
new ESXProperty({
type: ESXProperty.Static,
name: 'a',
value: 'a'
}),
new ESXProperty({
type: ESXProperty.Runtime,
name: 'props',
value: props
})
]
})
)
});
If you have ideas around possible improvements, if you want to bring this to the TC39 attention as champion, if have any question related to this proposal, or if you'd like to know more about how udomsay became the fastest and smallest runtime using 90% of this proposal through a dedicated transformer, I will be more than happy if you could reach out, either here, in twitter, or in mastodon.
Thank you very much for your patience reading through this gist 🙏