DOCUMENTATION Docs Powered by Fuzor Docs

HTML module scripts

Native HTML, vanilla DOM behavior, compiled resumable handlers and Fuzor UI Web Components belong to Precompiled Resumable UI. A Framework Island is reserved for a third-party UI runtime such as React, Solid or Vue.

HTML module scripts

Write native client logic in an existing <script type="module"> tag. Fuzor removes it from the HTML and collects its client code into one entry module per route while leaving the surrounding HTML precompiled. The entry records DOM locations for each instance, so no placeholder <template> elements or script IDs are added to the rendered markup. JavaScript is the default; lang="ts" enables TypeScript syntax.

In a reusable components/*.html file, scripts are optional. When present, they must be top-level tags before any template markup. The compiler rejects scripts nested inside the markup or placed after it. Whitespace and comments may precede the script.

<script type="module" lang="ts">
  import { formatCount } from "./counter-format.ts"

  let count: number = 0
  const button = host.querySelector("button")!
  button.addEventListener("click", () => {
    button.textContent = formatCount(++count)
  }, { signal })
</script>

<section>
  <button type="button">Count: 0</button>
</section>

A referenced script uses the same per-instance semantics and placement:

<script type="module" src="./editor.ts"></script>

<section>
  <div data-editor></div>
</section>

In editor.ts, import your editor library and write initialization statements directly. Imports stay at module scope and use the normal Vite ESM pipeline, including imported CSS and dynamic imports. Executable statements run when that HTML instance activates. Reusable exports belong in modules imported by the script, rather than in the page script itself.

The script receives three names:

  • host: in a component, the first markup element following its leading scripts, skipping scripts and styles. Use that root to scope DOM queries to the component instance. Consecutive leading scripts share the same root. In pages and layouts, scripts use their parent element; a top-level page script receives the route outlet.
  • signal: an AbortSignal aborted when the instance is disposed. Pass it to event listeners and fetch requests.
  • onCleanup(callback): register synchronous or asynchronous cleanup for editors, subscriptions, timers and other resources. Cleanup runs in reverse registration order, including after an activation failure.

Relative imports resolve from the HTML file containing the script, including layouts and reusable HTML components. An external script resolves its own imports from its own location. Use host.querySelector() for repeated components instead of global ID lookups. Top-level await runs inside the generated asynchronous activation function.

Each route entry coordinates loaders for its script instances. Shared dependencies may be factored into shared chunks, and explicit dynamic imports remain lazy. Independent hosts activate concurrently; nested hosts wait for their parent, and scripts sharing one host retain order. A script-free route needs no client entry. In SPA --inline builds, all route entries, shared code, dynamic imports, CSS and route templates are embedded in the single HTML file; changing routes does not fetch external JavaScript chunks. An embedded module map preserves ESM evaluation and local import failures.

Page scripts run in the browser only, in document order. They do not run during precompilation or server rendering. Scripts in inert <template> content are not activated. Ordinary JSON scripts remain untouched. Document-global module scripts belong in the index.html shell; data-fuzor-global excludes a page script from extraction but retains ordinary browser script semantics, including the browser's rule that scripts inserted with HTML do not execute.

Resumability boundary

For explicitly managed state and lazy handlers, use a defineNative({...}) declaration from fuzor/runtime/native; see the managed HTML counter. That path has a bounded compiler contract and does not replay handlers during restoration.

Page scripts are an explicit initialization path for imperative client libraries. Local variables and arbitrary closures in these scripts are not automatically serialized or resumed; their initialization runs for each new page instance. Existing compiled handlers, server-component identity and Atom-backed UI checkpoints retain their own resumable paths. A page script does not replace the surrounding DOM or deserialize/overwrite those checkpoints. Avoid initializing a second owner for a component already managed by Fuzor's resumable UI.

TypeScript syntax is compiled, but this does not yet provide inline TypeScript semantic checking or HTML editor completion. Volar integration is deferred.

Manual activation

fuzor/runtime/page-scripts exports mountPageScripts, mountPageScriptsEffect, PageScriptActivationError and the script context/module/registry types. A registry maps route template paths to loaders returning a RouteScriptModule with bindings, each pairing a DOM address with a mount function. These addresses are generated by the compiler and stored in JavaScript, not authored in HTML. mountPageScripts(root, registry, { route }) activates one route entry. For custom SPA hosts, createRouter(outlet, { scripts }) accepts a script registry. Normal Vite builds generate it automatically. The old native wrapper, folder discovery and separate behavior registry have been removed; native code uses scripts in ordinary HTML.

Built with Fuzor Back to top ↑
On this page Back to top ↑