htmxjs
Version:
htmxjs is the server-side js framework for HTMX
68 lines (62 loc) • 2.42 kB
text/typescript
import { Hono, Env } from 'hono';
import { html } from 'hono/html';
import { Context, Next } from 'hono';
import { HtmlEscapedString } from 'hono/utils/html';
export * from 'hono/html';
export * from 'hono/jsx';
export { handle } from 'hono/cloudflare-pages';
type EndpointResponse =
| Promise<HtmlEscapedString | HtmlEscapedString[]>
| HtmlEscapedString
| HtmlEscapedString[];
export type ViewFunction = (props: { context: Context }) => EndpointResponse;
export type LayoutFunction = (props: {
context: Context;
children: HtmlEscapedString;
}) => EndpointResponse;
type P = string;
export type HtmxJsContext = Context;
export const htmlRaw = String.raw;
export class HtmxJS<T extends Env> {
app: Hono<T>;
constructor(RootLayout: LayoutFunction) {
this.app = new Hono<T>();
this.app.use('*', this.layout(RootLayout, true));
}
view(viewToRender: ViewFunction) {
return async (c: Context) => {
const newBody = await viewToRender({ context: c });
const newBodyText = Array.isArray(newBody) ? newBody.join('\n') : newBody;
return c.html(newBodyText);
};
}
layout(layoutToApply: LayoutFunction, isRoot = false) {
return async (c: Context, next: Next) => {
await next();
const isHXBoosted = c.req.header('HX-Boosted') === 'true';
const isHXRequested = c.req.header('HX-Request') === 'true';
const isTargetBody =
c.req.header('HX-Target') === '#body' ||
c.req.header('HX-Target') === 'body';
// render all layouts including root if there's no HX-Request header
const renderRoot = !isHXRequested || isHXBoosted || isTargetBody;
const renderLayout =
!isRoot && isHXRequested && (isHXBoosted || isTargetBody);
console.log({ isRoot, renderRoot, isHXRequested, renderLayout });
if (renderRoot || renderLayout) {
const curBody = (await c.res.text()) as unknown as TemplateStringsArray;
// To overwrite res, set it to undefined before setting new value
// https://github.com/honojs/hono/pull/970 released in https://github.com/honojs/hono/releases/tag/v3.1.0
c.res = undefined;
const newBody = await layoutToApply({
context: c,
children: html(curBody),
});
const newBodyText = Array.isArray(newBody)
? newBody.join('\n')
: newBody;
c.res = c.html(newBodyText);
}
};
}
}