@xmachines/play-svelte-spa-router
API / @xmachines/play-svelte-spa-router
Svelte SPA Router adapter for the XMachines Play architecture. It connects hash-based routing to a
Routableactor, so the state machine owns the navigation.
Installation
pnpm add @xmachines/play-svelte-spa-routerPeer dependencies. Install them with the adapter:
pnpm add svelte@^5.0.0 svelte-spa-router@^5.0.0 xstate@^5.31.0Usage
1. Define a routable XState machine
Declare the route of each state in its meta.route field:
import { setup } from "xstate";import { formatPlayRouteTransitions } from "@xmachines/play-xstate";import type { PlayRouteEvent } from "@xmachines/play-svelte-spa-router";
const machine = setup({ types: { events: {} as PlayRouteEvent },}).createMachine( formatPlayRouteTransitions({ id: "app", initial: "home", states: { home: { meta: { route: "/home" } }, dashboard: { meta: { route: "/dashboard" } }, settings: { meta: { route: "/settings/:section?" } }, }, }),);2. Create a player and connect the router
Use createRouteMap to build a bidirectional route map from the machine. Then call connectRouter one time, usually in your root Svelte component or in a dedicated runtime module:
import { definePlayer } from "@xmachines/play-xstate";import { connectRouter, createRouteMap } from "@xmachines/play-svelte-spa-router";import { machine } from "./machine.js"; // defined in the previous example
const createPlayer = definePlayer({ machine });export const actor = createPlayer();actor.start();
const routeMap = createRouteMap(machine);export const disconnectRouter = connectRouter({ actor, routeMap });3. Clean up on destroy
Give the cleanup function of connectRouter to onDestroy, or to the equivalent lifecycle hook:
<script lang="ts"> import { onDestroy } from "svelte"; import { disconnectRouter } from "./runtime.js"; // the module from the previous example
onDestroy(() => disconnectRouter());</script>API Summary
connectRouter(options): () => void
Connects svelte-spa-router to a Routable actor. Returns a cleanup (disconnect) function.
interface ConnectRouterOptions { readonly actor: RoutableActor; readonly routeMap: RouteMap; /** * Window-like object for `hashchange` subscriptions. * Defaults to the global `window`. Pass a mock in tests * or a no-op in SSR environments. */ readonly window?: WindowLike;}Behavior:
- On
connect: it reads the current hash URL fromrouter.loc.location, sets the actor state with aplay.routeevent, and then listens for ahashchangeevent. - On a change of the actor
currentRoute: it callspush(path)from svelte-spa-router to update the URL. - On
hashchange: it reads the new location fromrouter.locand sendsplay.routeto the actor. - On disconnect: it removes the
hashchangelistener and cancels the subscriptions to the actor signals.
createRouteMap(machine): RouteMap
This function comes from @xmachines/play-router. It reads every meta.route entry of an XState machine. Then it builds a bidirectional route map with URLPattern matching, which includes a parameter segment and an optional segment.
RouteMap
This type comes from @xmachines/play-router. It is the bidirectional map between the state IDs and the URL paths.
| Method | Description |
|---|---|
getStateIdByPath(path) | Returns the state ID for a URL path, or null |
getPathByStateId(stateId) | Returns the URL path for a state ID, or null |
Exported types
| Type | Description |
|---|---|
ConnectRouterOptions | The options that connectRouter accepts |
RoutableActor | Minimal actor interface — AbstractActor from @xmachines/play-actor combined with the Routable mixin |
PlayRouteEvent | The play.route event type that the bridge sends to the actor |
RouterBridge | Interface that all router bridges must satisfy |
RouteMapping | Route-entry shape within a RouteMap |
RouteMapOptions | Options for createRouteMap |
WindowLike | Minimal window interface for testability |
How it works
SvelteSpaRouterBridge extends RouterBridgeBase from @xmachines/play-router and implements three abstract methods:
| Method | svelte-spa-router equivalent |
|---|---|
navigateRouter(path) | push(path) |
watchRouterChanges() | win.addEventListener("hashchange", …) |
unwatchRouterChanges() | win.removeEventListener("hashchange", …) |
The bridge reads the initial path from router.loc.location, and the initial search string from router.loc.querystring. It inherits the restore-or-deeplink detection, the guard-redirect flow, and the isProcessingNavigation debounce from RouterBridgeBase.
SvelteSpaRouterBridge is exported from the package — prefer connectRouter for the common case, and instantiate the bridge directly (new SvelteSpaRouterBridge(actor, routeMap) followed by connect()/disconnect()) when you need fine-grained control over the connection lifecycle.
Testing
Run the package tests in isolation:
pnpm --filter @xmachines/play-svelte-spa-router testOr from inside this package directory:
pnpm testCoverage thresholds: 80% lines / functions / branches / statements.
License
MIT © XMachines Contributors. See LICENSE.