The official router plugin for Eleva.js - a lightweight, zero-dependency client-side routing solution with support for hash, query, and history modes.
Latest: v1.2.0-alpha with configurable view selectors, dynamic route parameters, enhanced error handling, and memory management.
- π Multiple routing modes: Hash (
#/page), Query (?page=name), History (/page) - π― Dynamic route parameters:
/users/:id,/files/:path*(catch-all) - π§ Zero configuration: Works out of the box with sensible defaults
- πΎ Memory efficient: Automatic cleanup and leak prevention
- π‘οΈ Error resilient: Built-in error handling and recovery
- π¦ Tiny footprint: Zero dependencies, minimal bundle size
npm install eleva-routerimport Eleva from "eleva";
import ElevaRouter from "eleva-router";
const app = new Eleva("MyApp");
// Define components
const Home = {
template: () => `<h1>Welcome Home!</h1>`,
};
const About = {
setup: ({ navigate }) => ({
goHome: () => navigate("/"),
}),
template: (ctx) => `
<div>
<h1>About Us</h1>
<button @click="goHome">Go Home</button>
</div>
`,
};
// Setup router
app.use(ElevaRouter, {
layout: document.getElementById("app"),
mode: "history", // "hash" | "query" | "history"
routes: [
{ path: "/", component: Home },
{ path: "/about", component: About },
],
});The router uses an app layout concept where you provide a layout element that contains a dedicated view element for mounting routed components. This allows you to maintain persistent layout elements (like navigation, headers, footers) while only the view content changes during navigation.
The router automatically looks for a view element within your layout using these selectors (in order of priority, based on selection speed):
#view- Element withviewid (fastest - ID selector).view- Element withviewclass (fast - class selector)<view>- Native<view>HTML element (medium - tag selector)[data-view]- Element withdata-viewattribute (slowest - attribute selector)- Falls back to the layout element itself if no view element is found
Note: The difference in selection speed between these selector types is negligible for most practical cases. This ordering is a micro-optimization that may provide minimal performance benefits in applications with very frequent route changes.
You can customize the view element selector by setting the viewSelector option:
// Using custom view selector
app.use(ElevaRouter, {
layout: document.getElementById("app"),
viewSelector: "router-view", // Custom selector name
routes: [...]
});This will look for elements like:
#router-view(ID).router-view(class)<router-view>(tag)data-router-view(attribute)
Example HTML Structure:
<div id="app">
<header>
<nav><a href="#/">Home</a> <a href="#/about">About</a></nav>
</header>
<main id="view"></main>
<!-- Router mounts components here -->
<footer>© 2024</footer>
</div>// Route with parameters
{ path: "/users/:id", component: UserProfile }
// Catch-all route
{ path: "/files/:path*", component: FileViewer }
// Access parameters in component
const UserProfile = {
setup: ({ route }) => ({
userId: route.params.id // "123" for "/users/123"
}),
template: (ctx) => `<h1>User: ${ctx.userId}</h1>`
};| Option | Type | Default | Description |
|---|---|---|---|
layout |
HTMLElement | required | App layout element. Router looks for a view element (#{viewSelector}, .{viewSelector}, <{viewSelector}>, or data-{viewSelector}) within this layout to mount components. Priority based on selection speed (micro-optimization). If no view element is found, the layout itself is used. |
mode |
string | "hash" |
Routing mode: "hash", "query", or "history" |
queryParam |
string | "page" |
Query parameter name for query mode (?page=about vs ?view=about) |
viewSelector |
string | "view" |
Selector name for the view element. Used to find elements like #router-view, .router-view, <router-view>, or data-router-view. |
routes |
array | [] |
Array of route objects |
defaultRoute |
object | null |
Fallback route for unmatched paths |
autoStart |
boolean | true |
Auto-start router after installation |
const MyComponent = {
setup: ({ navigate, route }) => ({
// Simple navigation
goToAbout: () => navigate("/about"),
// With parameters
goToUser: (id) => navigate("/users/:id", { id }),
// Current route info
currentPath: route.path,
routeParams: route.params,
queryParams: route.query,
}),
};// Navigate from anywhere
await app.router.navigate("/about");
await app.router.navigate("/users/:id", { id: 123 });
// Router control
await app.router.start(); // Manual start
await app.router.destroy(); // Cleanup// URLs: http://example.com/#/about
app.use(ElevaRouter, { mode: "hash", ... });// URLs: http://example.com/about
app.use(ElevaRouter, { mode: "history", ... });// Default: ?page=about
app.use(ElevaRouter, { mode: "query", ... });
// Custom: ?view=about
app.use(ElevaRouter, {
mode: "query",
queryParam: "view",
...
});Query Mode Customization
// E-commerce with custom parameter
app.use(ElevaRouter, {
layout: document.getElementById("app"),
mode: "query",
queryParam: "category", // ?category=electronics
routes: [
{ path: "/", component: Home },
{ path: "/electronics", component: Electronics },
{ path: "/books", component: Books },
],
});
// Admin panel
app.use(ElevaRouter, {
layout: document.getElementById("app"),
mode: "query",
queryParam: "section", // ?section=users
routes: [
{ path: "/", component: Dashboard },
{ path: "/users", component: UserManagement },
{ path: "/settings", component: Settings },
],
});Complete App Example
import Eleva from "eleva";
import ElevaRouter from "eleva-router";
const app = new Eleva("BlogApp");
const routes = [
{
path: "/",
component: {
template: () => `<h1>Blog Home</h1>`,
},
},
{
path: "/posts/:id",
component: {
setup: ({ route, navigate }) => ({
postId: route.params.id,
goHome: () => navigate("/"),
}),
template: (ctx) => `
<article>
<h1>Post #${ctx.postId}</h1>
<button @click="goHome">β Back</button>
</article>
`,
},
},
{
path: "/category/:name",
component: {
setup: ({ route }) => ({
category: route.params.name,
}),
template: (ctx) => `<h1>Category: ${ctx.category}</h1>`,
},
},
];
app.use(ElevaRouter, {
layout: document.getElementById("app"),
mode: "history",
routes,
defaultRoute: {
path: "/404",
component: {
template: () => `<h1>Page Not Found</h1>`,
},
},
});Manual Router Control
app.use(ElevaRouter, {
layout: document.getElementById("app"),
routes: [...],
autoStart: false // Don't start automatically
});
// Start when ready
document.addEventListener("DOMContentLoaded", async () => {
try {
await app.router.start();
console.log("Router started!");
} catch (error) {
console.error("Router failed:", error);
}
});
// Cleanup on exit
window.addEventListener("beforeunload", () => {
app.router.destroy();
});For comprehensive documentation, advanced features, and best practices:
π Full Documentation
- Complete API reference
- Advanced routing patterns
- Error handling strategies
- Performance optimization
- Migration guides
Common Issues:
- Routes not matching: Check path syntax and ensure layout exists
- Components not mounting: Verify component definitions and layout element
- Navigation not working: Use
navigate()from context orapp.router.navigate() - Memory issues: Call
app.router.destroy()during cleanup
Need Help?
- π¬ GitHub Discussions
- π Report Issues
- π Full Documentation
We welcome contributions! Please see our Contributing Guidelines for details.
MIT License - feel free to use in any project.
Made with π€ for the Eleva.js ecosystem