miqro · @miqro/core · @miqro/parser · @miqro/query · @miqro/jsx · @miqro/jsx-dom · @miqro/jsx-node · @miqro/request · @miqro/runner · @miqro/test · @miqro/test-http
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.
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();
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="[{"title":"hello"}]"><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.
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.
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.
addPathListener, removePathListener, pushPath are no-ops — navigation is handled at the server levelquerySelector only supports #id selectorsuseEffect, useRefresh and event handlers run unless you turn them off. pass
runtimeOptions to createContainer, as the example above does, or use
JSXResponseHandler, which disables all four for the SSR passcreateNodeRuntime() call creates an isolated runtime instance — custom elements,
elements by id and the location are all its own, so it is safe to call once per request