Client

This section will be revisited soon to improve the API. What follows is how it works today.

Browser TypeScript for the bits HTML cannot do. Register at module scope with client.module or client.element. The server compiles client files and ships them only when a host rendered.

client.module

Register a client file that runs as a side effect. Call client.module at module scope, not inside a handler or component. Render the returned component on the page to attach the script.

Client files can import each other. The convention is to name them *_client.ts and keep them beside the file that registers them. Import browser APIs from dashi/client, not dashi.

The module runs on first page load, when a RouteSlot brings it in, or after soft navigation.

clock.tsx

import { client } from "dashi";

const Clock = client.module(new URL("./clock_client.ts", import.meta.url));

export function Page() {
  return (
    <main>
      <Clock />
      <p id="time" />
    </main>
  );
}

clock_client.ts

const el = document.getElementById("time");
if (el) {
  el.textContent = new Date().toLocaleTimeString();
}

client.element

Register a custom element. Pass a kebab-case tag and the client file URL. Use the returned component as the host in JSX and put declarative markup inside.

copy_button.tsx

import { client } from "dashi";

const CopyButton = client.element(
  "copy-button",
  new URL("./copy_button_client.ts", import.meta.url),
);

export function SnippetToolbar({ id }: { id: string }) {
  return (
    <CopyButton htmlFor={id}>
      <button type="button">copy</button>
    </CopyButton>
  );
}

copy_button_client.ts

class CopyButton extends HTMLElement {
  connectedCallback() {
    this.addEventListener("click", this.#copy);
  }

  #copy = () => {
    const id = this.getAttribute("for");
    const source = id === null ? null : document.getElementById(id);
    if (source === null) {
      return;
    }
    void navigator.clipboard.writeText(source.textContent ?? "");
  };
}

customElements.define("copy-button", CopyButton);

How it ships

dashi compiles registered client files to hashed URLs under /_dashi/client/. The document gets one import map for that graph. A <script type="module"> is added only when a host rendered on the page.

Build client hosts inside handler-rendered components. A tree built outside the handler can land in HTML without the matching module script.