miqro · @miqro/core · @miqro/parser · @miqro/query · @miqro/jsx · @miqro/jsx-dom · @miqro/jsx-node · @miqro/request · @miqro/runner · @miqro/test · @miqro/test-http

@miqro/jsx-node

Node.js SSR runtime for @miqro/jsx. implements the Runtime interface using a fake DOM that serializes to HTML.

used by miqro internally for .html.tsx handlers and --inflate.

createNodeRuntime

import { createNodeRuntime } from "@miqro/jsx-node";
import JSX, { Props } from "@miqro/jsx";

function MyComponent(props: Props) {
  return <p>{props.title}</p>;
}

const runtime = createNodeRuntime({
  url: {
    pathname: "/posts",
    searchParams: new URLSearchParams("page=1"),
    toString: () => "/posts?page=1"
  },
  basePath: null,
  logger: console
});

const root = runtime.createElement("div");
const container = runtime.createContainer(root, {
  shadowInit: false,
  runtimeOptions: {
    disableEffects: true,
    disableEvents: true,
    disableRefListener: true,
    disableRefresh: true
  }
});

container.render(<MyComponent title="hello" />);
console.log(root.innerHTML); // <p>hello</p>
container.disconnect();

define (server-side Web Components)

register a Web Component so that the tag renders its component's output server side instead of being written out empty for the browser to fill in.

import { createNodeRuntime } from "@miqro/jsx-node";
import JSX, { Props } from "@miqro/jsx";

function PostsList(props: Props) {
  const posts: { title: string }[] = JSON.parse(props.data);
  return <ul>{posts.map(p => <li key={p.title}>{p.title}</li>)}</ul>;
}

const runtime = createNodeRuntime();
runtime.define("posts-list", PostsList, { shadowInit: false });

const root = runtime.createElement("div");
const container = runtime.createContainer(root, { shadowInit: false });
container.render(<posts-list data='[{"title":"hello"}]' />);

console.log(root.innerHTML);
// <posts-list data="[{&quot;title&quot;:&quot;hello&quot;}]"><ul><li>hello</li></ul></posts-list>

the tag stays in the markup. it is what a browser upgrades, so it is what lets a client side define from @miqro/jsx-dom take the same component over once its bundle loads. what define adds server side is the content inside it.

shadowInit does not change the output. this runtime has no shadow DOM and writes a shadow root's content as ordinary children, so all four spellings serialize the same way. it only matches what a browser produces when the client uses shadowInit: false.

asFragment

drops the tag and writes only what the component rendered.

runtime.define("posts-list", PostsList, { shadowInit: false, asFragment: true });
container.render(<posts-list data='[{"title":"hello"}]' />);

console.log(root.innerHTML);
// <ul><li>hello</li></ul>

this is a one way door. with no tag in the markup there is nothing for a client side define to upgrade, so the output is final. use it for markup nothing will hydrate -- --inflate output, mail -- and for the places a custom element cannot go at all, like inside a <table>, where the HTML parser moves a foreign element out of the table.

attributes live on the tag, so they go with it: a class or an id written on <posts-list> is not in the output. put what the markup needs on an element the component itself renders.

@miqro/jsx-dom's define does not take this option. in a browser the custom element is a real DOM element and there is no tag to leave out.

hydrating what was rendered

a client side define re-renders from scratch on upgrade, and it reads whatever is already inside the tag as the children the author wrote. after SSR that content is the server's own output, and the two are indistinguishable:

so a component meant to be both server rendered and hydrated should not render children.

notes