Skip to content

@xmachines/play-vue-router-demo

Examples / @xmachines/play-vue-router-demo

Vue Router integration demo for the XMachines Play architecture using Vue Composition API.

What This Demonstrates

  • Shared auth machine reused without framework-specific business logic
  • PlayRouterProvider renderer-based integration with Vue Router
  • Shell-driven rendering via PlayRenderer with actor-authoritative navigation
  • Vue Composition API mapping to TC39 Signals lifecycle
  • Non-browser invariant tests plus browser E2E coverage

Running the Demo

From the repository root:

Terminal window
npm install
npm run dev -w @xmachines/play-vue-router-demo

Then open http://localhost:3001.

Step-by-Step Code Flow

Use this order to understand how the demo is wired:

  1. src/main.ts creates the Vue app, installs Vue Router, mounts, and wires HMR cleanup.
  2. src/runtime.ts creates the actor, starts it, builds the shared registryResult, and creates the routeMap.
  3. src/router.ts installs a single catch-all route; PlayRenderer selects the actual view from actor state.
  4. src/App.vue renders <PlayRouterProvider> with the shared actor, routeMap, router instance, and a renderShell renderer function.
  5. The renderer function creates the shared Shell component with actor, router, and registry as props.
  6. PlayRouterProvider waits for router.isReady() so direct URL loads are handled correctly.
  7. Browser tests in test/browser/ validate startup and auth route transitions.
// src/main.ts (shape)
const app = createApp(App);
app.use(router);
app.mount("#app");
// src/router.ts (shape)
export const routes = [{ path: "/:pathMatch(.*)*", name: "xmachines-play", component: RouteHost }];
<!-- src/App.vue (shape) -->
<script setup lang="ts">
import { actor, routeMap, registryResult } from "./runtime.js";
const renderShell = (currentActor, currentRouter) =>
h(SharedShell, { actor: currentActor, router: currentRouter, registryResult });
</script>
<template>
<PlayRouterProvider
:actor="actor"
:route-map="routeMap"
:router="router"
:renderer="renderShell"
/>
</template>
// src/runtime.ts (shape)
export const actor = createPlayer();
actor.start();
export const routeMap = createRouteMap(authMachine);
export const registryResult = defineRegistry(authCatalog, { components, actions });

Key Files

  • src/main.ts - Vue app bootstrap (mount + router install + HMR cleanup)
  • src/runtime.ts - actor startup, routeMap creation, and registry construction
  • src/router.ts - single catch-all route record
  • src/App.vue - PlayRouterProvider wiring and renderer composition using the shared runtime
  • test/reactivity.test.ts - reactive integration assertions
  • test/browser/shared-demo.browser.test.ts - browser startup and full auth flow coverage

State Machine & Architecture Details

The demo utilizes XMachines architectural invariants:

  1. Actor Authority: When a user navigates to a protected route via a link, Vue Router updates the location. The PlayRouterProvider intercepts this, translates it to a play.route event, and sends it to the actor. The actor evaluates guards (e.g. isAuthenticated) and transitions.
  2. Passive Infrastructure: The router does not execute Vue route guards for business logic. The actor dictates whether navigation is permitted. The Vue application only renders the state.
  3. Signal-Only Reactivity: The bridge leverages Vue’s watch and triggerRef internally to react precisely when signals update, without polluting the Vue component tree with reactive refs that hold business state.

Watcher Lifecycle and Cleanup Contract

This demo follows the canonical watcher lifecycle:

  1. notify
  2. queueMicrotask
  3. getPending()
  4. Read actor signals and sync Vue-local render state
  5. Re-arm with watch()/watch(...signals)

Notifications are one-shot, so re-arm is required. Teardown is explicit: provider/bridge cleanup must flow through disconnect and watcher unwatch, not GC-only assumptions. The PlayRouterProvider wires this seamlessly into the component’s onUnmounted hook.

Adapter Boundaries

Vue Router integration remains passive infrastructure. RouterBridgeBase owns shared synchronization policy and the Vue adapter is a thin framework port. Route extraction override strategies remain supported when teams need custom route-name mapping.

Available Scripts

These commands are defined in package.json:

CommandDescription
npm run dev -w @xmachines/play-vue-router-demoStart Vite dev server
npm run build -w @xmachines/play-vue-router-demoBuild production bundle
npm run preview -w @xmachines/play-vue-router-demoPreview built bundle
npm run test -w @xmachines/play-vue-router-demoRun Vitest test suite
npm run test:browser -w @xmachines/play-vue-router-demoRun browser-focused Vitest suite

Verification

Use these checks to validate README claims against the current demo implementation:

Terminal window
npm run test -w @xmachines/play-vue-router-demo
npm run test:browser -w @xmachines/play-vue-router-demo

Expected result: reactivity integration tests and the browser shared-demo suite both pass, confirming login/logout transitions update both view and URL correctly.

Learn More