Docs · 11 pages
Docs / Start here
Runtime
The lifecycle substrate every scripted component sits on, and the API you can use for your own scripts.
Astro bundles a component’s <script> once per page and never re-runs it
after a <ClientRouter /> navigation. On pages without the router the
astro:* events do not exist at all. On Astro 5.0 to 5.4, astro:page-load
could be skipped on a first visit or doubled on back/forward
(withastro/astro#12858,
fixed in 5.5.0). And it can be lost for a whole page view: the router fires it
from the promise that waits for the new page’s scripts, so a navigation
interrupted after its swap, or one script request that stalls, leaves the page
on screen with nothing bound.
Every scripted component in this library goes through one module that owns
that problem, and you can use it for your own code. It binds on the swap as
well as on the load event, and one setup that throws is reported to the console
without stopping the others.
import { onMount, onPage, prefersReducedMotion, refresh } from '@moonarc/core/runtime';onMount(selector, setup, options?)
Binds setup to every element matching selector, now and again after every
navigation. Each element is bound exactly once, however many times events fire.
onMount<HTMLElement>('[data-tilt]', (el, { signal, reducedMotion }) => {
if (reducedMotion) return;
el.addEventListener('pointermove', track, { signal }); // removed at teardown
return () => el.style.removeProperty('--rx'); // runs on astro:before-swap
});signalis anAbortSignalaborted at teardown. Pass it toaddEventListenerand the listener is removed for you.- The returned function runs before the DOM is swapped.
options.rootscopes the query (defaultdocument).- Returns an unregister function.
onPage(setup)
One run per page view, with the same lifecycle. For things without an element: a ticker, a GSAP context, a resize observer.
onPage(({ signal }) => {
const id = setInterval(tick, 1000);
return () => clearInterval(id);
});prefersReducedMotion()
Reads the media query once and keeps it current. Prefer shortening motion to near-zero over skipping the code path, so end states still apply.
refresh()
Binds elements you injected yourself after load, and releases bound elements you took out of the page: their listeners go, and their cleanup runs.
Where to register
Every page <script> is its own module. Two pages with the same script are two
registrations, and both would bind the same elements. Put onMount calls in
the component that owns the elements, or in a layout.
Runtime cost
About 1.9 kB raw, 1.0 kB gzip, once per site: it is one shared chunk no matter how many components use it. Measured in CI; see performance.