Van Router
A small router middleware for vanilla-js.
Features
Easy to use
. you can use pure js everywhere or combine with other framework like Alpinejs, React etc.Middleware
. does your application have authentication? you can use middleware.Lazy-Load
. this router support laze-load js/controller.SSR
. support SSR / Deno / Nodejs.
Installation
Browser
<!-- non module -->
<script src="//unpkg.com/van-router"></script>
<!-- es module -->
<script type="module">
import { createRouter } from "https://esm.sh/van-router";
// code here
</script>
This library transpile to ES3.
Nodejs
npm i van-router
const { createRouter } = require("van-router");
Deno
import { createRouter } from "https://deno.land/x/van_router@0.6.5/mod.ts";
Usage
Example in the browser
...
<body>
<nav>
<a href="/home" van-link>Home</a>
<a href="/about" van-link>About</a>
</nav>
<div id="app"></div>
<script>
const { createRouter } = Van;
const router = createRouter({
render: (elem) => {
document.getElementById("app").innerHTML = elem;
}
});
router.add("/", () => {
return `<h1>Hello Home</h1>`;
});
router.add("/about", () => {
return `<h1>Hello About</h1>`;
});
addEventListener("load", () => {
router.resolve();
});
</script>
</body>
...
note : extension vscode for literal html lit-html.
router.add("/", ({ html }) => {
return html`<h1>Hello Home</h1>`;
});
With hash
...
// html
<nav>
<a href="#/home" van-link>Home</a>
<a href="#/about" van-link>About</a>
</nav>
...
// js
const router = createRouter({
// set hash to true
hash: true,
render: (elem) => {...}
});
...
Middleware
...
const foo_midd = (ctx, next) => {
ctx.foo = "foo";
return next();
}
const bar_midd = (ctx, next) => {
ctx.bar = "bar";
return next();
}
// global middleware
router.use(foo_midd);
// inline middleware
router.add("/", bar_midd, ({ foo, bar }) => {
return `<h1>${foo}${bar}</h1>`;
// => foobar
});
...
Lazy-load
/router.js
...
router.add("/", ({ lazy }) => {
return lazy("/controller/home.js");
});
...
/controller/home.js
function home() {
return `<h1>Hello Home</h1>`;
}
Route Paths
Route
router.add("/", (ctx) => {...});
// or
router.on("GET", "/", (ctx) => {...});
Example
// simple path
router.add("/", (ctx) => {...});
// with parameter
router.add("/user/:id/:name", (ctx) => {...});
// with optional parameter
router.add("/user/:id/:name?", (ctx) => {...});
// with ext
router.add("/image/:filename.(jpg|png)", (ctx) => {...});
// wildcard
router.add("*", (ctx) => {...});
router.add("/user/*", (ctx) => {...});
// with regex
router.add(/.*noop$/, (ctx) => {...});
Config
Config for VanRouter
// types
type Config = {
render?: (elem: any) => any;
base?: string;
hash?: boolean;
}
const router = createRouter(config);
Config.render
Render configs.
const render = (elem) => {
document.getElementById("app").innerHTML = elem;
// or with React
// ReactDOM.render(elem, document.getElementById("app"));
};
const router = createRouter({ render });
Config.base
Base path/url like <base href="/myapp" />
. default to undefined.
Config.hash
optional hash true/false. default to false.
Context (ctx)
Is an utility based on object.
router.add("/", (context) => {...})
Context.route
type CtxRoute = {
url: string;
path: string | RegExp;
pathname: string;
params: Record<string, any>;
go(url: string, type?: string): void;
};
Context.route.params
Object query parameter from path.
router.add("/user/:userId", ({ route }) => {
console.log(route.params);
// => { userId: "123" }
return ``;
});
Context.route.pathname
router.add("/user", ({ route }) => {
console.log(route.pathname);
// url => /user?name=john
// pathname => /user
return ``;
});
Context.route.url
router.add("/user", ({ route }) => {
console.log(route.url);
return ``;
});
Context.route.go
Go to pathname (client only).
router.add("/user", ({ route }) => {
route.go("/home");
return ``;
});
Context.route.path
router.add("/user/:userId/book/:bookId", ({ route }) => {
console.log(route.path);
// => /user/:userId/book/:bookId
return ``;
});
Context.useAfter
Turn on client-side effect with useAfter
.
why useAfter
? because execute code after rendering element/view.
...
useAfter(() => {
// code here
return () => {
// cleanup here
}
})
...
Example useAfter
router.add("/", ({ useAfter }) => {
useAfter(() => {
window.myClick = () => {
alert("Hello World");
};
return () => {
delete window.myClick;
};
});
return `<button onclick="myClick()">Click Me</button>`;
});
Context.lazy
Lazy load js/controller
/router.js
...
router.add("/", ({ lazy }) => {
return lazy("/controller/home.js");
});
...
/controller/home.js
function home() {
return `<h1>Hello Home</h1>`;
}
Context.html
for vscode literal html syntax highlight lit-html.
router.add("/", ({ html }) => {
return html`<h1>Hello World</h1>`;
});
Context.useData
support client-side and server-side.
router.add("/", ({ html, useData }) => {
const data = useData(() => {
return { name: "john" };
});
return html`<h1>${data.name}</h1>`;
});
Context.setHead
set head support client-side and server-side.
router.add("/", ({ html, setHead }) => {
setHead(html`<title>Name John</title>`);
return html`<h1>Name John</h1>`;
});
Context.getHandler
Call handler in handler. (server-side only).
getHandler(url: string, method?: string) => Promise<any>
.
note : requires return object directly on handler.
router.add("/api/user/:name", ({ html, route }) => {
return { name: route.params.name };
});
router.add("/", ({ html, getHandler }) => {
const data = await getHandler("/api/user/john");
return html`<h1>${data.name}</h1>`;
// => <h1>john</h1>
});
Other context
- Context.isServer
- Context.isHydrate
- Context.request (SSR only)
- Context.response (SSR only)
Handle error & not found
Handle 404 not found
router.add("*", () => {
return `<h1>404 not found</h1>`;
});
Handle error
router.add("/", (ctx) => {
ctx.noop();
return `<h1>Noop</h1>`;
});
router.onError((err, ctx) => {
console.log(err);
return `<h1>${err.message}</h1>`;
// message => ctx.noop is not a function
});
With React (jsx)
import React from "react";
import ReactDOM from "react-dom";
import { createRouter } from "van-router";
// example components
import Home from "./components/home";
import About from "./components/about";
const render = (component) => {
ReactDOM.render(component, document.getElementById("app"));
};
const router = createRouter({ render });
router.add("/", () => {
return <Home />;
});
router.add("/about", () => {
return <About />;
});
router.resolve();
Server-Rendered
// resolve in the server.
const res = router.resolve({ request, response });
// body / elem
const body = await res.out();
// initial data from useData.
const data = await res.data();
// head for seo from setHead.
const head = res.head();
With Nodejs (Server-Rendered)
const { createRouter } = require("van-router");
const http = require("http");
const port = 8080;
const router = createRouter();
router.add("/", ({ html, setHead }) => {
setHead(html`<title>Hello from node</title>`);
return html`<h1>Hello From Node</h1>`;
});
http.createServer(async (request, response) => {
const res = router.resolve({ request, response });
const body = await res.out();
const head = res.head();
if (typeof body === "string") {
response.setHeader("Content-Type", "text/html");
response.end(`
<html>
<head>
<link rel="icon" href="data:,">
${head}
</head>
<body>
${body}
</body>
</html>
`);
}
}).listen(port);
With Deno (Server-Rendered)
import { createRouter } from "https://deno.land/x/van_router@0.6.5/mod.ts";
import { serve } from "https://deno.land/std@0.140.0/http/server.ts";
const port = 8080;
const router = createRouter();
router.add("/", ({ html, setHead }) => {
setHead(html`<title>Hello from deno</title>`);
return html`<h1>Hello From Deno</h1>`;
});
await serve(async (request: Request) => {
const res = router.resolve({ request });
const body = await res.out();
const head = res.head();
if (body instanceof Response) return body;
return new Response(
`
<html>
<head>
<link rel="icon" href="data:,">
${head}
</head>
<body>
${body}
</body>
</html>
`,
{
headers: { "Content-Type": "text/html" },
},
);
}, { port });
It's Fun Project :).