If you are using this already, consider changes soon due the discussion around current ESX proposal.
Feel free to keep an eye on udomsay as that will be the implementation reference for consumers.
This is a TC39 proposal and there is a Babel transformer for it too.
There is also a zero-tooling solution based on template litearl tags in this repository, which solves almost all pain-points around having ESX as syntax.
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, general purpose, DSL.
This document describes all the moving parts of ESX in a way that:
- there is no DOM at all involved, only new primitives introduced by ESX
- 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 (noReact.createElement
orReact.Fragment
needed, norudomsay.interpolation
) - all classes can be used just as types to infer, as oppsite of being really classes ... no clashing in the logic can happen neither. The only global class needed out there is
ESXToken
which carries types and the prototypal inheritance for brand check. - 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
// base class for instanceof operations
class ESXToken {
static ATTRIBUTE: number;
static INTERPOLATION: number;
static STATIC: number;
static FRAGMENT: number;
static ELEMENT: number;
static COMPONENT: number;
}
// possible `attributes` entry
interface ESXAttribute {
type = ESXToken.ATTRIBUTE;
dynamic: boolean;
name: string;
value: unknown;
}
// possible `attributes` or `children` entry
interface ESXInterpolation {
type = ESXToken.INTERPOLATION;
value: unknown;
}
// possible `children` entry
interface ESXStatic {
type = ESXToken.STATIC;
value: string;
}
// always the same reference when this is used as outer template
interface ESXNode extends ESXToken {
id: object?
children: (ESXStatic | ESXInterpolation | ESXNode)[];
}
// <></>
class ESXFragment extends ESXNode {
type = ESXToken.FRAGMENT;
}
// <any-element />
class ESXElement extends ESXNode {
type = ESXToken.ELEMENT;
name: string;
value: string;
attributes: (ESXAttribute | ESXInterpolation)[];
}
// <AnyComponent />
class ESXComponent extends ESXNode {
type = ESXToken.COMPONENT;
name: string;
value: function;
attributes: (ESXAttribute | ESXInterpolation)[];
get properties(): object?;
}
To better understand what the transformer currently produce, please see this innput and compare it with this output.
F.A.Q.
What are ESX benefits?
template.id
as WeakMap key, or the template can be bypassed by simply accessing itsvalue
type
property to check in placeinstanceof ESXToken
brand check is performed on childrenvalue
, as these can still be primitives or non tokens and can be handled accordinglyReact.createElement
callback defined, arbitrarily overwritten by transpilers via proprietary directives such as/* @jsxPragma h */
or/* @jsxFragment f */
. This proposal has no such dependencies in its representation, hence it never need a different declaration at different transpilers level, it simply provides out of the box every single detail those callbacks need as arguments instead, through the previously mentioned uniformed structure. Bring any render logic you like in place, and forget about the need for transpilers (if implemented in core, of course, otherwise transpilers that generate the expected ESX structure will be still needed AOT).What's the difference with JSX?
className
instead ofclass
, or any other required artifact in JSX is completely irrelevant for this proposal. Properties are properties of the node in the tree and none of these have any special meaning. Implementations can decide special meanings or simply recreate the tree as it is.How can ESX be serialized?
If ESX represents elements and fragments, without components and without callbacks as properties,
JSON.stringify(<basic-esx />)
would already produce an outcome that could be parsed in the future to represent the previous state of the tree.However, if components are used, some specialized serlializer and de-serializer might be used to snapshot the state.
This ESX feature, missing in JSX due usage of callbacks for everything (
createElement
), enables great opportunities in the SSR world and/or hydration.Why would anyone use ESX instead of template literal tags?
JSX has brought an extremely desired DX thanks to it being syntax, not just string, hence highlighted and with instant feedback if something is missing, such as quotes around static properties or no quotes around interpolations. These features are definitively superior to template literal tags (with or without instrumented highlight) but it lacks interpolations. With template literal tag based solutions though, interpolations can break the final outcome/layout/intent without warnings, because template literal tags based libraries abuse strings to represent tree structures.
ESX takes the best from both worlds granting details around interpolations and providing uniqueness of outer templates, being these inline, as in a list of item returning always the same JSX template, or well known as Components.
This combination results into better DX than both JSX or template literal tags, plus it brings the template literal tags unique template feature to allow possible optimizations.
How can I test ESX?
There is a Babel transformer that is being tested and showcased in this repository.
Comparison
@click
orv:directive
, etc)<${Component}>...</${Component}>
might be supported by some library but it requires convoluted logic and is not natural to write as markupcreateElement
callback but it's somehow possible. Classes as Components for custom elements are (usually) not supported.