Installation
Install ilha and start from official templates for Vite SPA and Oxide SPA.
Install ilha with your package manager. For a new project, start from a template so routing and mounting are already wired.
Install
npm install ilha effectpnpm add ilha effectyarn add ilha effectbun add ilha effectnub add ilha effectaube add ilha effecteffect is a peer dependency. Ilha atoms and streams sit on Effect.
Templates
| Template | Command | Sandbox |
|---|---|---|
| Vite SPA | npx giget@latest gh:ilhajs/ilha/templates/vite-spa |
Open |
| Oxide SPA | npx giget@latest gh:ilhajs/ilha/templates/oxide-spa |
Open |
Vite SPA is a client app with file routes. Oxide SPA adds server pages, frames, and action from oxidejs.
Requirements
- ESM modules.
- TypeScript for the best editor support.
- A browser for
mount(). - JSON-serializable atom snapshots when you hydrate.
Import
import { const atom: AtomFnatom, const mount: (el: Element, fn: Component, opts?: MountOptions) => (() => void)mount, const renderToString: (fn: Component, opts?: RenderToStringOptions) => Promise<string>renderToString } from "ilha";
Named exports you will use most:
import { const atom: AtomFnatom, const when: <A, E = never, R = never>(stream: Stream<A, E, R>, body: (value: A) => Generator<Yielded, View | undefined, View>) => Instruction<undefined, E>when, const mount: (el: Element, fn: Component, opts?: MountOptions) => (() => void)mount, const renderToString: (fn: Component, opts?: RenderToStringOptions) => Promise<string>renderToString, const h: (type: string | Fragment | ComponentFn | JsxComponent, props: PropBag | null | undefined, ...rest: View[]) => VNodeh, type Fragment = typeof Fragment
const Fragment: typeof Fragment
Fragment } from "ilha";
Custom elements come from ilha/define:
import { const define: (name: string, component: Component) => voiddefine } from "ilha/define";
Minimal example
import { const atom: AtomFnatom } from "ilha";
const const Greeting: () => ViewGreeting = () => <JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p>Hello, ilha!</JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p>;
const const Counter: () => ViewCounter = () => {
const const count: AtomHandle<number>count = atom<number>(init: number | Atom<number> | Effect<number, unknown, AtomRegistry> | Stream<number, unknown, AtomRegistry>, options?: AtomOptions<number> | undefined): AtomHandle<number>atom(0);
return (
<JSX.IntrinsicElements.button: ButtonHTMLAttributesbutton type?: string | undefinedtype="button" EventProps<HTMLButtonElement>.onclick?: ((event: Targeted<HTMLButtonElement, MouseEvent>) => void) | undefinedonclick={() => const count: AtomHandle<number>count.AtomHandle<number>.update: (f: (current: number) => number) => voidupdate((n: numbern) => n: numbern + 1)}>
Count: {const count: AtomHandle<number>count}
</JSX.IntrinsicElements.button: ButtonHTMLAttributesbutton>
);
};
Server-side rendering
import { const atom: AtomFnatom, const renderToString: (fn: Component, opts?: RenderToStringOptions) => Promise<string>renderToString } from "ilha";
const const Counter: () => ViewCounter = () => {
const const count: AtomHandle<number>count = atom<number>(init: number | Atom<number> | Effect<number, unknown, AtomRegistry> | Stream<number, unknown, AtomRegistry>, options?: AtomOptions<number> | undefined): AtomHandle<number>atom(0);
return (
<JSX.IntrinsicElements.button: ButtonHTMLAttributesbutton type?: string | undefinedtype="button" EventProps<HTMLButtonElement>.onclick?: ((event: Targeted<HTMLButtonElement, MouseEvent>) => void) | undefinedonclick={() => const count: AtomHandle<number>count.AtomHandle<number>.update: (f: (current: number) => number) => voidupdate((n: numbern) => n: numbern + 1)}>
Count: {const count: AtomHandle<number>count}
</JSX.IntrinsicElements.button: ButtonHTMLAttributesbutton>
);
};
const const html: stringhtml = await function renderToString(fn: Component, opts?: RenderToStringOptions): Promise<string>renderToString(const Counter: () => ViewCounter);
renderToString waits until in-flight work is idle, then serializes. Pass { timeout: ms } to cut off early.
Client-side mounting
import { const mount: (el: Element, fn: Component, opts?: MountOptions) => (() => void)mount } from "ilha";
const const Counter: () => ViewCounter = () => <JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p>Count</JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p>;
const const root: HTMLElement | nullroot = var document: Document**`window.document`** returns a reference to the document contained in the window.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/document)document.Document.getElementById(elementId: string): HTMLElement | nullThe **`getElementById()`** method of the Document interface returns an Element object representing the element whose id property matches the specified string. Since element IDs are required to be unique if specified, they're a useful way to get access to a specific element quickly.getElementById("app");
if (const root: HTMLElement | nullroot) {
const const unmount: () => voidunmount = function mount(el: Element, fn: Component, opts?: MountOptions): (() => void)mount(const root: HTMLElementroot, const Counter: () => ViewCounter);
}
The returned function stops listeners and in-flight work. Call it when you remove the host.