Lightview Router
A lightweight, pipeline-based History API router with middleware support.
Overview
The Lightview Router provides a simple yet powerful way to handle client-side routing in your application. It supports standard routes, wildcards, named parameters, and middleware pipelines.
Features
- Pipeline-based routing (middleware style)
- History API integration
- Zero dependencies
- Route parameters
/user/:id - Wildcard support
/api/* - Async handler support
- Locale prefix handling
- Markdown rendering
Basic Usage
Initialize the router with a target element and define your routes.
import { LightviewRouter } from '/lightview-router.js';
// 1. Initialize
const appRouter = LightviewRouter.router({
contentEl: document.getElementById('app'), // Routes will render here automatically
// Optional: Lifecycle hooks
// onStart: (path) => console.log('Loading...'),
// notFound: (path) => console.log('404 Not Found')
});
// 2. Register routes
// Simple path mapping (fetches /index.html -> renders to #app)
appRouter.use('/', '/index.html');
// Self-mapping (fetches /about.html -> renders to #app)
appRouter.use('/about.html');
// Wildcards (fetches matching path -> renders to #app)
appRouter.use('/docs/*');
// 3. Start listening
appRouter.start();
Middleware & Pipelines
Lightview Router uses a "chain of responsibility" pattern. When you register a route, you can provide multiple handlers.
Automatic Rendering: If you provide contentEl and your route chain ends with a
string (or has no handlers),
the router automatically appends a handler that fetches the path and renders it to contentEl.
You can still define manual handlers for advanced logic:
➡️ Continue Chain
If a handler returns null, undefined, or a context object, the router continues to the next handler in the chain.
Use this for logging, auth checks, or transforming the path.
// Middleware
const logger = (ctx) => {
console.log('Visiting:', ctx.path);
// Returns undefined, so routing continues
};
🛑 Stop & Return
If a handler returns a Response object, the chain stops immediately, and the router considers the navigation complete.
Use this for custom API responses or redirects.
// Final Handler
const loadPage = async (ctx) => {
return await fetch(ctx.path);
// Returns Response, stops routing
};
Example Pipeline
appRouter.use('/admin/*',
// 1. Authentication Middleware
(ctx) => {
if (!user.isLoggedIn) {
router.navigate('/login');
return new Response('Redirecting...'); // Stop chain
}
// User is logged in, continue...
},
// 2. Logging Middleware
(ctx) => {
console.log('Admin access at', new Date());
// implicitly returns undefined, continues...
},
// 3. Implicit Fetch (if contentEl is set)
// The router automatically fetches /admin/* and renders it
);
Advanced Usage
Functional Arguments
The use(...) method accepts a variadic list of arguments. While standard usage involves strings
for
matching and replacement, you can pass functions for any argument to achieve fine-grained control.
Internally, all strings and regexes are converted to functions. Passing a function directly gives you access to the raw context pipeline.
Anatomy of a use() call
appRouter.use(Matcher, ...Middleware);
- Matcher (Arg 0): Determines if this chain runs. Returns a context object (continue) or null (skip).
- Middleware (Args 1...n): Logic that executes if the matcher succeeds.
appRouter.use(
// Argument 1: The Matcher
// Can be a string '/path', regex /path/, or function
(ctx) => {
// Custom matching logic
if (ctx.path.includes('secret') && user.isAdmin) {
return ctx; // Match!
}
return null; // No match, try next route
},
// Argument 2: Middleware / Logic
(ctx) => {
console.log('Secret route accessed');
// Modify the context for the next handler
return { ...ctx, path: '/admin/secret.html' };
}
// Note: If no implementation is provided and contentEl is set,
// the router automatically appends a fetch handler for the final path.
);
API Reference
LightviewRouter.router(options)
Creates a new router instance.
Options:
contentEl(HTMLElement): Element to automatically render content into.base(string): Base path for the router.onStart(function): Callback when navigation begins.onResponse(function): Callback when a handler returns a response.
Built-in Middleware
localeHandler(ctx)
Extracts locale prefixes (e.g., /en/about) from the path. It modifies the context
by stripping the prefix (/about) and adding a locale property.
Usage: Add it early in your chain so subsequent handlers see the clean path.
import { localeHandler } from '/middleware/locale.js';
appRouter.use('/*', localeHandler);
// If user visits /en/about:
// 1. localeHandler strips /en/, sets ctx.locale = 'en'
// 2. Next handlers see ctx.path = '/about'
markdownHandler(ctx)
Intercepts requests for .md files, fetches the content, parses it using marked.js
(loaded on demand), and renders the resulting HTML.
Returns: A Response object (stops the route chain).
import { markdownHandler } from '/middleware/markdown.js';
// Handle all markdown files
appRouter.use('/*.md', markdownHandler);
notFound(options)
A final middleware that handles 404 errors when no other route matches.
Usage: Add it as the very last route in your configuration.
import { notFound } from '/middleware/notFound.js';
appRouter.use(notFound({
// contentEl: inherited from router options,
html: '<h1>404 Not Found</h1>' // Optional custom HTML
}));
router.use(pattern, ...handlers)
Registers a route or middleware.
Pattern types:
- String literal:
'/about'- Exact match. - Wildcard:
'/api/*'- Matches any path starting with /api/. - Parameter:
'/user/:id'- Captures:idas a parameter.
router.navigate(path)
Programmatically navigate to a new path.
router.navigate('/profile/settings');
Context Object
Handlers receive a context object containing:
path: The current normalized path.params: Object containing named parameters (if any).wildcard: Content of the wildcard match (if any).contentEl: The main content element (allows middleware to render content or override the target).
Advanced Configuration
The router() constructor accepts an options object for lifecycle hooks and error handling.
const appRouter = LightviewRouter.router({
contentEl: document.getElementById('app'),
// Lifecycle: Called when navigation triggers (e.g., click or back button)
// Good for starting loading spinners
onStart: (path) => {
document.getElementById('spinner').style.display = 'block';
},
// Lifecycle: Called AFTER auto-render completes
// Good for post-render logic like analytics or scroll position
onResponse: (response, path) => {
document.getElementById('spinner').style.display = 'none';
window.scrollTo(0, 0);
// Run additional logic here (analytics, etc.)
}
});