![Oracle Drags Its Feet in the JavaScript Trademark Dispute](https://cdn.sanity.io/images/cgdhsj6q/production/919c3b22c24f93884c548d60cbb338e819ff2435-1024x1024.webp?w=400&fit=max&auto=format)
Security News
Oracle Drags Its Feet in the JavaScript Trademark Dispute
Oracle seeks to dismiss fraud claims in the JavaScript trademark dispute, delaying the case and avoiding questions about its right to the name.
htmx-router
Advanced tools
A remix.js style file path router for htmX websites
This library attempts to be as unopinionated as possible allowing for multiple escape hatches in-case there are certain times you want a different style of behaviour.
This library does not rely on any heavy weight dependencies such as react, instead opting to be built on the lighter weight kitajs/html library for it's JSX rendering, and using csstype, just as a type interface to improve developer ergonomics.
You can also see an example site running this library here with source code as an extra helpful example. Please be mindful the slow loading of this site is actually due to Discord APIs, and the rendering is taking less than 2ms on a raspberry pi on my floor.
There are two requirements for this package behave correctly, you need a root.jsx
/.tsx
, in the same folder as a routes
sub-folder. Plus any given route must have use the route name
provided as the top element's id
- which will be explained more in later.
URLs are resolved based on the file structure of yur routes
folder, you can choose to use nested folders or .
s to have all of your files in a single folder if you choose - all examples will use the .
layout for simplicity.
If the url path /user/1234/history
is requested, the router will create an outlet chain to your file tree (this chain is actually just an array of modules, and the library works based on stack operations to reduce recursion and increase response times).
root.tsx
routes
├── _index.tsx
├── user.tsx
├── user.static-path.tsx
└── user.$userID.tsx
Given the file tree above, when the root function calls it's Outlet function, that will trigger user
to render, and when user calls it's Outlet function, that will trigger user.$userID.tsx
to render as this sub route didn't match with any of the static options available, so instead it matched with the wild card route.
Since there is no user.$userID.history.tsx
file, if user.$userID.tsx
calls Outlet it will trigger a 404 error, which is actually generated by an internal hidden route placed at the end of an Outlet chain when it is not able to match the rest of a given URL.
If we request /user
, the root route will render, and the user.tsx
route will render, with user.tsx
's Outlet function actually returning a blank string. If we instead want this request to throw an error we should add a user._index.tsx
file which on render always throws a ErrorResponse
.
The router will look for three functions when reading a module, Render, CatchError, and Auth. Any combination of all or none of these functions are allowed, with the only exception being your root.tsx
which must have a Render and a CatchError function.
export async function Auth({shared}: RenderArgs) {
if (!shared.auth?.isAdmin) throw new ErrorResponse(401, 'Unauthorised', "Unauthorised Access");
return;
}
This function is ran on all routes resolved by this file, and it's child routes - no matter if the route itself is masked or not rendered. This function must return nothing, and instead signals failure via throwing an error. With the successful case being nothing was thrown.
export async function Render(routeName: string, {}: RenderArgs): Promise<string>
The render function should follow the above function signature, the routeName string must be consumed if you're rendering a valid output, with the top most HTML element having the id assigned to this value. This helps the router dynamically insert new routes when using the Link component.
Note that a render function may not always run for a given request, and may sometimes be ommited when the router determines the client already has this information in their DOM, and instead only response with the new data and where it should be inserted.
For authorisation checks which must always run for a given URL, please use the Auth function
Optionally this function can also throw
, in this case the thrown value will boil up until it hits a CatchError, unless it throws certain types from this library such as Redirect
or Override
which will boil all the way to the top without triggering any CatchError functions.
This allows a given route to return an arbitrary response without being nested within it's parent routes.
export async function CatchError(rn: string, {}: RenderArgs, e: ErrorResponse): Promise<string>
This function behaves almost identically to Render in the way that it's results will be embed within it's parents unless an Override
is thrown, and it takes a routeName
which must be used in the root HTML
element as the id. And it is given RenderArgs.
However this function must not call the Outlet function within the RenderArgs, as that will attempt to consume a failed child route as it's result.
The router itself can be generated via two different ways through the CLI from this library, dynamically or statically.
npx htmx-router ./source/website --dynamic
When the command is ran it will generate a router based on the directory provided which should contain your root file and routes folder. This command will generate a router.ts
which we recommend you git ignore from your project.
esbuild
nodemon
.Once your router is generated you can simply import it and use it like the example below:
const url = new URL(req.url || "/", "http://localhost");
const out = await Router.render(req, res, url);
if (out instanceof Redirect) {
res.statusCode = 302;
res.setHeader('Location', out.location);
return res.end();
} else if (out instanceof Override) {
res.end(out.data);
} else {
res.setHeader('Content-Type', 'text/html; charset=UTF-8');
res.end("<!DOCTYPE html>"+out);
}
The Router.render
function may output three possible types Redirect, Override, or a simple string. These two non-string types are to allow the boil up of overrides within routes, and allows you do handle them specific to your server environment.
This class has been designed to work well with object unpacking for ease of use, typically it's used in a style like this for routes that only need information about the req
and res
objects.
export async function Render(rn: string, {req, res}: RenderArgs) {
return "Hello World";
}
However it also includes a bunch of functions to help with higher order features.
The outlet function will call the child route to render, this function is asynchronous and will always return a string, or else it will throw. If there is nothing left in the outlet chain, it will return an empty string.
This function will update the title that will be generated by the renderHeadHTML function, as well as the trigger value for the title updater when using the Link component.
You should consider when you call this function in conjunction to your Outlet function, because if you run setTitle, after the outlet has been called it will override the title set by the child.
export async function Render(rn: string, {setTitle, Outlet}: RenderArgs) {
setTitle("Admin Panel");
return <div id={rn}>
<h1><Link to="/admin" style="color: inherit">
Admin Panel
</Link></h1>
{await Outlet()}
</div>;
}
This function allows you to add meta tags which will be rendered by the renderHeadHTML function.
addMeta([
{ property: "og:title", content: `${guild.name} - Predictions` },
{ property: "og:image", content: banner }
], true);
If the second argument of this function is set to true
this function will override any meta tags currently set, replacing them with the inputted tags instead.
This function behaves identically to addMeta but instead designed for link tags.
This renders out the set meta and link tags for use in the root.tsx
module, it also includes an embed script for updating the title for dynamic loads from the Link component.
There is also a blank object attached to all RenderArgs for sharing information between routes.
This can be used for various purposes, but one example is to hold onto decoded cookie values so that each session doesn't need to recompute them if they already have.
Such an example would look like this
import type { IncomingMessage } from "node:http";
import * as cookie from "cookie";
export function GetCookies(req: IncomingMessage, shared: any): Record<string, string> {
if (shared.cookie) return shared.cookie;
shared.cookies = cookie.parse(req.headers.cookie || "");
return shared.cookies;
}
import type { GetCookies } from "../shared/cookie.ts";
export function GetCookies(rn: string, {shared}: RenderArgs) {
const cookies = GetCookies(shared);
// do stuff....
}
This class is a way of HTTP-ifying error objects, and other error states, if an error is thrown by a Render or an Auth function that isn't already wrapped by this type, the error will then become wrapped by this type.
export class ErrorResponse {
code : number;
status : string;
data : any;
}
If a render function throws a value of this type, it will boil all the way up to the original render call allowing it to bypass any parent manipulation.
export class Override {
data : string | Buffer | Uint8Array;
}
This type behaves identically to override by is intended for boiling up specifically http redirect responses.
export class Redirect {
location: string;
}
export function Link(props: {
to: string,
target?: string,
style?: string
}, contents: string[])
<Link to={`/user/${id}`}>View Profile</Link>
This component overrides a normal <a>
, adding extra headers telling the server route it is coming from, based on this information the server can determine the minimal route that needs to be rendered to send back to the client and calculates just that sub route.
Sending it back to the client telling htmX where to insert the new content.
This element will encode as a standard <a>
with some extra html attributes, meaning it won't affect SEO and bots attempting to scrape your website.
DEPRECATED: If you utilize
@kitajs/html
instead oftyped-html
this function is no longer needed
This is a helper function allowing you to give it a CSS.Properties type, and render it into a string for kitajs/html to use.
<div style={StyleCSS({
height: "100%",
width: "100%",
fontWeight: "bold",
fontSize: "5em",
})}>
I AM BIG
</div>
FAQs
A lightweight SSR framework with server+client islands
The npm package htmx-router receives a total of 109 weekly downloads. As such, htmx-router popularity was classified as not popular.
We found that htmx-router demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Oracle seeks to dismiss fraud claims in the JavaScript trademark dispute, delaying the case and avoiding questions about its right to the name.
Security News
The Linux Foundation is warning open source developers that compliance with global sanctions is mandatory, highlighting legal risks and restrictions on contributions.
Security News
Maven Central now validates Sigstore signatures, making it easier for developers to verify the provenance of Java packages.