diff --git a/src/middleware/jsx-renderer/index.ts b/src/middleware/jsx-renderer/index.ts index 2dacc819..071070f6 100644 --- a/src/middleware/jsx-renderer/index.ts +++ b/src/middleware/jsx-renderer/index.ts @@ -1,11 +1,39 @@ import type { Context, Renderer } from '../../context' -// eslint-disable-next-line @typescript-eslint/no-unused-vars import { html, raw } from '../../helper/html' import { jsx, createContext, useContext } from '../../jsx' import type { FC, JSXNode } from '../../jsx' import { renderToReadableStream } from '../../jsx/streaming' import type { Env, Input, MiddlewareHandler } from '../../types' +const SPA_CONTENT_REQUEST_QUERY = '__spa_content' +const SPA_ROOT_ID = '__root' +const SPA_CLIENT_SCRIPT = `async function mountContent(pathname) { + const res = await fetch(pathname + '?${SPA_CONTENT_REQUEST_QUERY}') + const content = await res.text() + const root = document.querySelector('#${SPA_ROOT_ID}') + root.innerHTML = content +} +window.addEventListener( + 'click', + (e) => { + if ((e.target).tagName !== 'A') { + return + } + if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) { + return + } + const href = (e.target).getAttribute('href') + if (!href.startsWith('/')) { + return + } + e.preventDefault() + window.history.pushState(null, null, href) + mountContent(href) + }, + true +) +` + export const RequestContext = createContext(null) type PropsForRenderer = [...Required>] extends [unknown, infer Props] @@ -15,11 +43,19 @@ type PropsForRenderer = [...Required>] extends [unknown, in type RendererOptions = { docType?: boolean | string stream?: boolean | Record + spa?: boolean } const createRenderer = (c: Context, component?: FC, options?: RendererOptions) => (children: JSXNode, props: PropsForRenderer) => { + if (options?.spa) { + if (c.req.query(SPA_CONTENT_REQUEST_QUERY) !== undefined) { + return c.html(children) + } + children = jsx('hono-spa', { id: SPA_ROOT_ID }, children) + } + const docType = typeof options?.docType === 'string' ? options.docType @@ -31,7 +67,7 @@ const createRenderer = RequestContext.Provider, { value: c }, (component ? component({ children, ...(props || {}) }) : children) as any - )}` + )}${options?.spa ? raw(``) : ''}` if (options?.stream) { return c.body(renderToReadableStream(body), {