@aduh95/async-jsx

JSX syntax for vanilla JS, and async

Usage no npm install needed!

<script type="module">
  import aduh95AsyncJsx from 'https://cdn.skypack.dev/@aduh95/async-jsx';
</script>

README

Async-JSX

This package aims to give you the opportunity to use the JSX syntax introduced by React, without using React at all.

Key differrences with React API:

  • No VirtualDOM (less overhead, but also less reactivity)
  • Everything is asynchronous
  • No Component life-cycle

Usage

Get Started

import { h } from "@aduh95/async-jsx";

const element = <div>Hello World</div>;

console.log(element instanceof Promise); // true

element.then(domElement => console.log(domElement instanceof HTMLElement)); // true

document.body.append(renderAsync(element));

It is recommanded to add this CSS to your page to avoid custom elements leaking into your design:

async-component,
conditional-element {
  display: contents;
}
dom-portal {
  display: none;
}

Build tools

With Babel

You can use the @babel/plugin-transform-react-jsx package:

{
  "plugins": [
    [
      "@babel/plugin-transform-react-jsx",
      { "pragma": "h", "pragmaFrag": "Fragment" }
    ]
  ]
}

With TypeScript

In the tsconfig.json:

{
  "compilerOptions": {
    "jsx": "react",
    "jsxFactory": "h"
  }
}

With dynamic imports

You can define a component in a separate file and lazy-load it:

/* Greetings.js */
import { h } from "@aduh95/async-jsx";

// You can use an arrow function instead of a class
export default props => (
  <div className={props.className}>Hello {props.name}</div>
);
/* App.js */
import {
  h,
  lazy,
  Component,
  Fragment,
  conditionalRendering,
} from "@aduh95/async-jsx";
const Greetings = lazy(() => import("./Greetings.js"));

export default class App extends Component {
  stateObservers = new Set();
  greetingName = "";

  _handleNewName = this.handleNewName.bind(this);

  handleNewName(ev) {
    const { target } = ev;
    this.greetingName = target.value;
    this.stateObservers.forEach(fn => fn("ready"));
  }

  render() {
    return (
      <>
        <h1>Welcome</h1>
        {conidtionalRendering(
          {
            ready: <Greetings name={this.greetingName} />,
            askForName: (
              <input
                autofocus
                onBlur={this._handleNewName}
                placeholder="What's your name"
              />
            ),
          },
          this.stateObservers,
          ready,
          console.error
        )}
      </>
    );
  }
}

StatfulComponent

I have added a React-compatible StatefulComponent, which can be used the same way as a React.Component. However, because of the lack of VirtualDOM, it can be highly inneficient to use it. For example, let's take the Clocl example from React docs:

import { h, StatefulComponent } from "@aduh95/async-jsx";
class Clock extends StatefulComponent {
  constructor(props) {
    super(props);
    this.state = { date: new Date() };
  }

  componentDidMount() {
    this.timerID = setInterval(() => this.tick(), 1000);
  }

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  tick() {
    this.setState({
      date: new Date(),
    });
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

document
  .getElementById("root")
  .append(renderAsync(<Clock />, null, console.error));

This will work apparently, but at each tick, 3 DOM elements are initiated. Perf difference may not be visible on that small example, but you can imagine that having a whole site re-render everytime something change will put a lot of pressure on small-CPU clients.

SVG Elements

Because SVG Elements cannot be created using Document.prototype.createElement, but by Document.prototype.createElementNS, they cannot be created by the usual h p(or createElement) function. pIf you want to create SVG elements using JSX, you can put them in a separate module:

// Logo.js
import { createSVGElement as h } from "../utils/jsx.js";

export default () => (
  <svg>
    <path />
  </svg>
);

Then you can import this module in another component:

import { Component, h } from "../utils/jsx.js";
import Logo from "./Logo.js";

export default class Header extends Component {
  render() {
    return (
      <header>
        <Logo />
        <h1>Title</h1>
      </header>
    );
  }
}

API

This repo is using TypeScript, you can use it to have access to the documentation during development. The API is defined by the .d.ts files.