Skip to content

Instantly share code, notes, and snippets.

@WebReflection
Last active October 6, 2024 12:35
Show Gist options
  • Save WebReflection/2d64f34cf58daa812ec876242c91a97c to your computer and use it in GitHub Desktop.
Save WebReflection/2d64f34cf58daa812ec876242c91a97c to your computer and use it in GitHub Desktop.
Proposal: an ESX for JS implementation

About

Highly Experimental

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.

Goal

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 or jsxFragment around is required at all (no React.createElement or React.Fragment needed, nor udomsay.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

ESX

// 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?;
}

Code samples

To better understand what the transformer currently produce, please see this innput and compare it with this output.

@WebReflection
Copy link
Author

WebReflection commented Nov 9, 2022

Simplified implementation

Deprecated

The previous content doesn't matter anymore, because the current updated OP gist represent an already simplified implementation of ESX.

The class is defined in its polyfill repository.

@WebReflection
Copy link
Author

WebReflection commented Nov 9, 2022

F.A.Q.

What are ESX benefits?
  • safety: the current industry standards are either JSX or template literal tags base solution. JSX is superior to the latter because it has a well defined syntax that throws right away in your IDE or at building time, while template literal tags are digesting strings, where any malformed part of it will ship out of the box in a way or another. In this regard, JSX promotes cleaner outputs by default, without abusing the fact strings are strings, not DSL to represent non-strings related content (prone to SQL injection like accidents).
  • caching: similarly with template literals tags and their unique template reference as first argument, it is possible to chose to optimize, or completely ignore, well known templates. Optimizations can be done once per template.id as WeakMap key, or the template can be bypassed by simply accessing its value
  • uniformed structure: beside distinguishing between null properties or null value for fragment, as it's irrelevant when the type is fragment, the whole representation is fully trustable:
    • there is always a type property to check in place
    • properties, if present, have a uniformed structure too
    • children is always loop-able as array
    • no surprise on parsing can ever happen, as long as instanceof ESXToken brand check is performed on children value, as these can still be primitives or non tokens and can be handled accordingly
  • no callbacks needed: the current default JSX expectation is to have a global React.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).
  • portability: because no directive is needed, it is possible to test the exact same code through multiple libraries implementation, without asking anyone to install this or that transformer, as any render can simply deal, in an optimized or pre-transpiled way, or another, with the very same schema/structure this proposal provides. That results into having migration from library/FW X to library/FW Y a no brainer, helping developers picking the right tool for the job without needing to change the underlying code at all.
What's the difference with JSX?
  • it's not HTML: ESX represents just syntax and it's not coupled in any way with the HTML standard. In few words, conventions like className instead of class, 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.
  • interpolations: in JSX it's impossible to distinguish between interpolations: everything is either a property field or a children. This is the main reason vDOM is needed, to diff only moving parts among snapshots. With ESX, every static property or child can be fully ignored and no vDOM is strictly needed (although some caching system to avoid repeated tasks on the DOM might be used by implementations).
  • template: in JSX there's no way to understand if we are dealing with a static, well known, outer template definition, like it is instead with template literal tags. The latter enabled tons of one-off/JIT optimizations that are impossible to do with standard JSX transformer. Having a unique template reference, together with interpolations, helps even further getting fully rid of vDOM-like solutions.
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

Tagged Templates JSX ESX
provides a unique template identifier
identifies static parts from interpolations
allows object spread for props/attrs
out of the box static analisys + correctness
syntax highlight in most popular IDEs
it's standardized as syntax ⚠️ 1
it can be serialized 2
supports first-class Components ⚠️ 3
it's scope aware (as template's Components)
supports custom elements 4 ⚠️ 5 6
it's highly interoparable 7
  • 1 every library defines its own special syntax features within static parts of the template (e.g. @click or v:directive, etc)
  • 2 instrumented serializers might be needed for components, callbacks or special interpolations' values, but by default a tree can be represented as plain JSON and retrieved as valid ESX
  • 3 using <${Component}>...</${Component}> might be supported by some library but it requires convoluted logic and is not natural to write as markup
  • 4 custom elements are supported through the registry and (usually) as static markup only
  • 5 special care needs to be taken by the provided createElement callback but it's somehow possible. Classes as Components for custom elements are (usually) not supported.
  • 6 nothing strictly prevents 100% support of custom elements as either tag, builtin extend, or even as Class / Componment, as all details to operate accordingly are backed in ESX
  • 7 in tagged templates case, it's hard to be sure static parts are compatible, syntax speaking, with other libraries. JSX already demonstrated portability (React VS Preact) while ESX is still compatible with any JSX transformer out there and, if no transformer is needed, allows cross-library usage without changing anything around its template syntax.

@WebReflection
Copy link
Author

FYI I've written a TC39 Post and if you're registered there please either up vote the issue or rise your concerns around it, thank you!

@fusionstrings
Copy link

All the power to you

@WebReflection
Copy link
Author

WebReflection commented Nov 10, 2022

@fusionstrings I take that as a wish for success around this proposal, unfortunately it had underwhelming reactions out there (and so far) ... everyone wants JSX native in the language, and yet nobody wants it unless it's based on React namespace and mandatory tooling requirement.

@fusionstrings
Copy link

Yes, it’s a wishful thinking for success but in my eyes you’ve already succeeded and set the example on how to approach and do right thing.

The rest will be history. Someone is going to pick this up definitely, if not then still this is very brave.

@fusionstrings
Copy link

We shall not forget that for many, JSX is opaque and this may have very niche audience anyway, amongst them almost everyone is tied to one or other distro 🫣.

@bresnow
Copy link

bresnow commented Nov 27, 2022

This is interesting. I followed your repos for the GJS and ended up writing a gtk jsx factory/renderer semi independently. Saw this for the first time and think you have a powerful idea here.

@trusktr
Copy link

trusktr commented Mar 5, 2023

There's other ones too:

  • solid.js html
  • Lit html
  • etc.

The thing is, we can standardize syntax, but I don't think anyone is confident of which runtime implementation is the best yet.

Plus I personally don't think that this belongs in JavaScript spec. JavaScript is not specifically about making trees (namely DOM trees). I think a spec like this might make more sense as part of browser DOM API specs. I think it would be a lot more likely for a browser to standardize some sort of html template string language.

I just don't think that ECMAScript itself is likely to be the carrier of this feature, because it is very specific compared to other APIs (f.e. Math.whatever is an stdlib that is highly useful in many generic cases, not just tree building, and a language aims to be very generic).

Maybe WICG.io would be a better place to propose this (but I still think runtime semantics would currently hold it back). I like Solid.js html better than Lit html and esx, for example. I like the way that it works. But that's just my taste.

It will take a lot of time to see what's really the best runtime.

@WebReflection
Copy link
Author

I think it would be a lot more likely for a browser to standardize some sort of html template string language.

the whole premises of ESX is that it has nothing to do with HTML and it doesn't want to ... it's rather a XML compat DSL that translates into JS tokens.

Until people will couple JSX to HTML this standard will never happen. React JSX works in React native too, and good old E4X worked regardless of HTML.

There's no HTML in ESX, and that's no accident, as well as there's nothing DOM related too, to keep it general purpose, just like JS is ... which is why this could be an ECMAScript feature, not a WHATWG biased half powerful feature.

Trees are a structure that exist in every PL and since about ever, the fact Front End people keep thinking tress are only a DOM matter is, imho, a Front End people thinking limitation.

@WebReflection
Copy link
Author

P.S. please read the whole thing and see libraries already using ESX or transformers ... there's no limitation around what HTML can do, there's just a srializable JS friendly universal struct that can represent any kind of tree with attributes and holes ... every diversion from this representation will doom the proposal or the standard itself, and in the very same TC39 post people already said ESX is so cool, but I want it to do HTML things and that's where ESX will fail, if implemented outside pure ECMAScript syntax specification.

@WebReflection
Copy link
Author

P.S.2 this gist needs an update as the Token is now a single one, capable of everything described in the initial gist ... read the TC39 related post first and feel free to contribute there, not in this gist.

I can't unfortunately close gists but I am tempted to erase this one as it's not serving any purpose for the ESX cause.

@jogibear9988
Copy link

@WebReflection Your proposal was also Mentioned in this discord Thread: https://discord.com/channels/767813449048260658/1226165073022554172

Maybe you should look to also create a official proposal at https://github.com/tc39/proposals (described here: https://github.com/tc39/ecma262/blob/HEAD/CONTRIBUTING.md)

@o-t-w
Copy link

o-t-w commented Apr 8, 2024

Even though Signals is only a stage 0 proposal, it would be cool to see some examples of using signals in conjunction with ESX. Also would love to see some examples of using ESX in conjunction with custom elements.

@WebReflection
Copy link
Author

WebReflection commented Apr 8, 2024

@jogibear9988 I can't access that discord ... no idea, but I did write on TC39 channel and crickets happened

@o-t-w udomsay has it all: it's ESX with signals https://github.com/WebReflection/udomsay#readme ... it's also listed in js-frameworks-benchmark and it performs extremely well despite not being native.

@jogibear9988
Copy link

jogibear9988 commented Apr 8, 2024

@WebReflection

you can join here : https://discord.gg/VRzgBSxM (if you'd like to)

the comment in the discord was:

"I don't think something really registers as a proposal until it's put into GitHub based on the TC39 proposal template, and at least briefly presented before the commitee. I think there would be a lot of feedback, and the champion would have to be open to it."

@o-t-w
Copy link

o-t-w commented Apr 8, 2024

Standards is always a long tedious process. I wouldn’t call a thread with over 100 comments “crickets”.

@WebReflection
Copy link
Author

@o-t-w I suggested already TC39 standards that got implemented in ECMAScript, the crickets was around the fact I've no idea what else I should do to move that forward and as I need a champion to show interest it's time and effort to write down any proposal knowing nobody cares or showed interest ... that's crickets to me.

@shellscape
Copy link

dear lord yes. I've been futilely trying to develop TypeScript types for cross-framework JSX compat and this proposal is exactly what we need.

@shellscape
Copy link

@jogibear9988 that invite link is invalid

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment