Skip to content

Instantly share code, notes, and snippets.

@phpnode
Forked from WebReflection/esx.md
Created November 8, 2022 12:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save phpnode/42f367225d0650d097af0ca18e8b1799 to your computer and use it in GitHub Desktop.
Save phpnode/42f367225d0650d097af0ca18e8b1799 to your computer and use it in GitHub Desktop.
Proposal: an ESX for JS implementation

About

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 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 or jsxFragment 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

ESX

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

Basic expectations

// ESX
const div = <div />;
// internal representation
const templateReference1 = {};
const div = new ESXTemplate({
  id: templateReference1,
  value: new ESXEntry(
    {
      type: ESXEntry.Element,
      value: 'div'
    },
    null
  )
});

Nested elements + props

// 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'
        })
      )
    })
  )
});

All together

// 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
        })
      ]
    })
  )
});

Please help me out!

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 🙏

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