htmx-router
Advanced tools
Comparing version 1.0.0-pre4 to 1.0.0-pre5
@@ -78,4 +78,4 @@ import { ServerOnlyWarning } from "./internal/util.js"; | ||
ctx.headers.set("X-Partial", "true"); | ||
return ctx.render(res); | ||
return ctx.render(res, ctx.headers); | ||
} | ||
export const action = loader; |
@@ -38,4 +38,4 @@ import { ServerOnlyWarning } from "./internal/util.js"; | ||
return res; | ||
return ctx.render(res); | ||
return ctx.render(res, ctx.headers); | ||
} | ||
export const action = loader; |
@@ -75,14 +75,16 @@ import { init, parse } from "es-module-lexer"; | ||
+ "type FirstArg<T> = T extends (arg: infer U, ...args: any[]) => any ? U : never;\n" | ||
+ "function mount(name: string, data: string, ssr?: JSX.Element) {\n" | ||
+ "\treturn (<>\n" | ||
+ `\t\t<div className={island}>{ssr}</div>\n` | ||
+ `\t\t${SafeScript(type, "`Router.mountAboveWith('${name}', ${data})`")}\n` | ||
+ "\t</>);\n" | ||
+ "function mount(name: string, json: string, ssr?: JSX.Element) {\n" | ||
+ "\treturn (<div className={island}>\n" | ||
+ `\t\t{ssr}\n` | ||
+ `\t\t${SafeScript(type, "`Router.mountParentWith('${name}', ${json})`")}\n` | ||
+ "\t</div>);\n" | ||
+ "}\n" | ||
+ "\n" | ||
+ "function Stringify(data: any) {\n" | ||
+ "\treturn JSON.stringify(data).replaceAll('<', '\\x3C');\n" | ||
+ "}\n\n" | ||
+ "const Client = {\n"; | ||
for (const name of names) { | ||
out += `\t${name}: function(props: FirstArg<typeof ${name}> & { children?: JSX.Element }) {\n` | ||
+ `\t\tconst { children, ...rest } = props;\n` | ||
+ `\t\treturn mount("${name}", JSON.stringify(rest), children);\n` | ||
+ `\t\tconst { children, ...data } = props;\n` | ||
+ `\t\treturn mount("${name}", Stringify(data), children);\n` | ||
+ `\t},\n`; | ||
@@ -89,0 +91,0 @@ } |
@@ -36,26 +36,64 @@ import { ServerOnlyWarning } from "./util.js"; | ||
function RequestMount(funcName, json) { | ||
const elm = document.currentScript.previousElementSibling; | ||
const elm = document.currentScript.parentElement; | ||
if (elm.hasAttribute("mounted")) | ||
return; | ||
if (!document.body.contains(elm)) | ||
return; | ||
mountRequests.push([funcName, elm, json]); | ||
} | ||
function Mount() { | ||
function Mount([funcName, element, json]) { | ||
console.info("hydrating", funcName, "into", element); | ||
const func = global.CLIENT[funcName]; | ||
if (!func) | ||
throw new Error(`Component ${funcName} is missing from client manifest`); | ||
func(element, json); | ||
element.setAttribute("mounted", "yes"); | ||
} | ||
function MountAll() { | ||
if (!global.CLIENT) | ||
throw new Error("Client manifest missing"); | ||
if (mountRequests.length < 1) | ||
return; | ||
if (!global.CLIENT) | ||
throw new Error("Client manifest missing"); | ||
for (const [funcName, element, json] of mountRequests) { | ||
console.info("hydrating", funcName, "into", element); | ||
const func = global.CLIENT[funcName]; | ||
if (!func) | ||
throw new Error(`Component ${funcName} is missing from client manifest`); | ||
func(element, json); | ||
element.setAttribute("mounted", "yes"); | ||
for (const request of mountRequests) { | ||
if (!document.body.contains(request[1])) | ||
continue; | ||
Mount(request); | ||
} | ||
mountRequests.length = 0; | ||
} | ||
document.addEventListener("DOMContentLoaded", Mount); | ||
document.addEventListener("htmx:load", Mount); | ||
function MountStep() { | ||
let request = mountRequests.shift(); | ||
while (request && !document.body.contains(request[1])) | ||
request = mountRequests.shift(); | ||
if (!request) { | ||
console.warn("No more pending mount requests"); | ||
return; | ||
} | ||
Mount(request); | ||
} | ||
function Freeze() { | ||
localStorage.setItem(freezeKey, "frozen"); | ||
window.location.reload(); | ||
} | ||
function Unfreeze() { | ||
localStorage.removeItem(freezeKey); | ||
window.location.reload(); | ||
} | ||
const freezeKey = "htmx-mount-freeze"; | ||
if (localStorage.getItem(freezeKey)) { | ||
const ok = confirm("Client mounting is frozen, do you want to unfreeze"); | ||
if (ok) | ||
Unfreeze(); | ||
} | ||
else { | ||
document.addEventListener("DOMContentLoaded", MountAll); | ||
document.addEventListener("htmx:load", MountAll); | ||
} | ||
return { | ||
mountAboveWith: RequestMount, | ||
mountParentWith: RequestMount, | ||
mount: { | ||
freeze: Freeze, | ||
unfreeze: Unfreeze, | ||
step: MountStep | ||
}, | ||
theme | ||
@@ -62,0 +100,0 @@ }; |
@@ -12,5 +12,5 @@ import { ParameterShaper } from '../util/parameters.js'; | ||
url: URL; | ||
render: (res: JSX.Element) => Response; | ||
render: (res: JSX.Element, headers: Headers) => Promise<Response> | Response; | ||
constructor(request: GenericContext["request"], url: GenericContext["url"], renderer: GenericContext["render"]); | ||
shape<T extends ParameterShaper>(shape: T): RouteContext<T>; | ||
} |
{ | ||
"name": "htmx-router", | ||
"version": "1.0.0-pre4", | ||
"version": "1.0.0-pre5", | ||
"description": "A lightweight SSR framework with server+client islands", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
@@ -15,3 +15,3 @@ import type { GenericContext } from "./internal/router.js"; | ||
readonly url: URL; | ||
render: (res: JSX.Element) => Response; | ||
render: GenericContext["render"]; | ||
constructor(base: GenericContext | RouteContext, params: ParameterPrelude<T>, shape: T); | ||
@@ -18,0 +18,0 @@ } |
@@ -176,3 +176,3 @@ import { ServerOnlyWarning } from "./internal/util.js"; | ||
return res; | ||
return ctx.render(res); | ||
return await ctx.render(res, ctx.headers); | ||
} | ||
@@ -185,3 +185,3 @@ async error(ctx, e) { | ||
return res; | ||
return ctx.render(res); | ||
return await ctx.render(res, ctx.headers); | ||
} | ||
@@ -188,0 +188,0 @@ async renderWrapper(ctx) { |
import { ServerOnlyWarning } from "../internal/util.js"; | ||
ServerOnlyWarning("bundle-splitter"); | ||
import { init, parse } from "es-module-lexer"; | ||
await init; | ||
const serverPattern = /\.server\.[tj]s(x)?/; | ||
const clientPattern = /\.client\.[tj]s(x)?/; | ||
const BLANK_MODULE = "export {};"; | ||
export function BundleSplitter() { | ||
@@ -14,10 +15,10 @@ return { | ||
if (pattern.test(id)) | ||
return BLANK_MODULE; | ||
return StubExports(code); | ||
if (ssr) { | ||
if (code.startsWith('"use client"')) | ||
return BLANK_MODULE; | ||
return StubExports(code); | ||
} | ||
else { | ||
if (code.startsWith('"use server"')) | ||
return BLANK_MODULE; | ||
return StubExports(code); | ||
} | ||
@@ -28,1 +29,12 @@ return code; | ||
} | ||
// A server only module may be imported into client code, | ||
// But as long as it isn't used this shouldn't break the program. | ||
// However JS will crash the import if the export name it's looking for isn't present. | ||
// Even if it's never used. | ||
// So we must place some stubs just in case | ||
function StubExports(code) { | ||
const exports = parse(code)[1]; | ||
return exports.map(x => x.n === "default" | ||
? "export default undefined;" | ||
: `export const ${x.n} = undefined;`).join("\n"); | ||
} |
75387
65
2152