@helloharusystem/s-spa
v0.2.0
Published
Tiny template-based SPA router: HTML <template> rendering + pushState routing, no framework.
Readme
s-spa
A tiny, framework-free SPA router built on native <template> elements and pushState. No virtual DOM, no bundler required to use it.
Install
npm install @helloharusystem/s-spaDevelopment
To work on the library itself:
npm install
npm run buildUsage
<div id="view-container"></div>
<template id="home"><h1>Home</h1></template>
<template id="settings"><h1>Settings</h1></template>
<template id="login"><h1>Login</h1></template>import { createRouter } from "@helloharusystem/s-spa";
function requireAuth(templateId) {
if (!isLoggedIn()) return "login";
}
const router = createRouter({
container: "#view-container",
guard: requireAuth, // runs before every navigation
});
router.route("home", "/");
router.route("settings", "/settings", { guard: requireAuth }); // stacks on top of the global guard
router.route("login", "/login");
router.registerPageHandler("settings", () => {
// wire up this page's DOM after it renders
});
router.start(); // renders whatever the current URL resolves toGuards: global vs. per-route
createRouter's guard option runs before every navigation — good for cross-cutting checks most of your pages share, like "must be logged in." router.route(id, path, { guard }) runs only for that route, after the global guard passes — good for the minority of pages that need something extra, like an admin-only check.
Both accept a single guard function or an array of guards, run in order, stopping at the first one that returns a redirect:
router.route("admin", "/admin", { guard: [requireAuth, requireAdmin] });A guard returns a template id to redirect there, or nothing to let the navigation proceed, and can be async — useful for e.g. silently refreshing a session and skipping the login page if it succeeds:
async function requireAuth(templateId) {
if (templateId === "login" && maybeHasSession()) {
const ok = await tryRefresh();
if (ok) return "welcome";
return;
}
if (PROTECTED_PAGES.has(templateId) && !isAuthenticated()) return "login";
}Use onGuardPending to show a spinner while an async guard is resolving.
API
createRouter(options): Routerrouter.route(templateId, path, { guard? })— register a route's URL path and optional route-specific guard(s)router.navigate(templateId, { push? })— navigate, running guards and updating historyrouter.showPage(templateId)— render directly, bypassing guardsrouter.registerPageHandler(templateId, handler)— runs each time that template renders; independent ofroute()call orderrouter.templateForPath(path)— reverse lookup, falls back tonotFoundIdrouter.start()— resolve and render the current URL; call once after registering routes and page handlers
See src/router.ts for the full RouterOptions/RouteOptions types.
