htmx-router 1.0.0-pre2 → 1.0.0-pre5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/index.js +0 -1
- package/cookies.js +5 -3
- package/defer.js +1 -1
- package/endpoint.js +1 -1
- package/internal/compile/manifest.js +12 -10
- package/internal/component/defer.js +1 -1
- package/internal/mount.js +52 -14
- package/internal/router.d.ts +1 -1
- package/navigate.d.ts +3 -0
- package/navigate.js +31 -0
- package/package.json +2 -2
- package/router.d.ts +1 -1
- package/router.js +2 -2
- package/vite/bundle-splitter.js +16 -4
package/cli/index.js
CHANGED
|
@@ -6,7 +6,6 @@ import * as components from "../internal/component/index.js";
|
|
|
6
6
|
import { CompileManifest } from "../internal/compile/manifest.js";
|
|
7
7
|
import { ReadConfig } from "./config.js";
|
|
8
8
|
const config = await ReadConfig();
|
|
9
|
-
console.info("");
|
|
10
9
|
if (config.client) {
|
|
11
10
|
console.info(`Generating ${chalk.green("client island")} manifest`);
|
|
12
11
|
const source = await readFile(config.client.source, "utf8");
|
package/cookies.js
CHANGED
|
@@ -15,7 +15,9 @@ export class Cookies {
|
|
|
15
15
|
return;
|
|
16
16
|
const source = typeof this.source === "object" ? this.source.cookie : this.source;
|
|
17
17
|
for (const line of source.split("; ")) {
|
|
18
|
-
|
|
18
|
+
let [name, value] = line.split("=");
|
|
19
|
+
name = decodeURIComponent(name);
|
|
20
|
+
value = decodeURIComponent(value);
|
|
19
21
|
this.map[name] = value;
|
|
20
22
|
}
|
|
21
23
|
// keep source it document
|
|
@@ -46,7 +48,7 @@ export class Cookies {
|
|
|
46
48
|
this.config[name] = options;
|
|
47
49
|
this.map[name] = value;
|
|
48
50
|
if (typeof this.source === "object")
|
|
49
|
-
document.cookie = `${name}=${value}`;
|
|
51
|
+
document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
|
|
50
52
|
}
|
|
51
53
|
unset(name) {
|
|
52
54
|
this.parse();
|
|
@@ -72,7 +74,7 @@ export class Cookies {
|
|
|
72
74
|
value = value[0].toUpperCase() + value.slice(1);
|
|
73
75
|
config += `; ${prop}=${value}`;
|
|
74
76
|
}
|
|
75
|
-
const cookie = name + "=" + this.map[name] + config + ";";
|
|
77
|
+
const cookie = encodeURIComponent(name) + "=" + encodeURIComponent(this.map[name]) + config + ";";
|
|
76
78
|
headers.push(cookie);
|
|
77
79
|
}
|
|
78
80
|
return headers;
|
package/defer.js
CHANGED
package/endpoint.js
CHANGED
|
@@ -69,21 +69,23 @@ function BuildServerManifest(type, imported) {
|
|
|
69
69
|
}
|
|
70
70
|
out += `from "${imp.href}";\n`;
|
|
71
71
|
}
|
|
72
|
-
out += `\nimport {
|
|
73
|
-
+ `const island = new
|
|
72
|
+
out += `\nimport { Style } from "htmx-router/css";\n`
|
|
73
|
+
+ `const island = new Style("i", ".this{display:contents;}\\n").name;\n\n`
|
|
74
74
|
+ "type FirstArg<T> = T extends (arg: infer U, ...args: any[]) => any ? U : never;\n"
|
|
75
|
-
+ "function mount(name: string,
|
|
76
|
-
+ "\treturn (
|
|
77
|
-
+ `\t\t
|
|
78
|
-
+ `\t\t${SafeScript(type, "`Router.
|
|
79
|
-
+ "\t
|
|
75
|
+
+ "function mount(name: string, json: string, ssr?: JSX.Element) {\n"
|
|
76
|
+
+ "\treturn (<div className={island}>\n"
|
|
77
|
+
+ `\t\t{ssr}\n`
|
|
78
|
+
+ `\t\t${SafeScript(type, "`Router.mountParentWith('${name}', ${json})`")}\n`
|
|
79
|
+
+ "\t</div>);\n"
|
|
80
80
|
+ "}\n"
|
|
81
|
-
+ "\n"
|
|
81
|
+
+ "function Stringify(data: any) {\n"
|
|
82
|
+
+ "\treturn JSON.stringify(data).replaceAll('<', '\\x3C');\n"
|
|
83
|
+
+ "}\n\n"
|
|
82
84
|
+ "const Client = {\n";
|
|
83
85
|
for (const name of names) {
|
|
84
86
|
out += `\t${name}: function(props: FirstArg<typeof ${name}> & { children?: JSX.Element }) {\n`
|
|
85
|
-
+ `\t\tconst { children, ...
|
|
86
|
-
+ `\t\treturn mount("${name}",
|
|
87
|
+
+ `\t\tconst { children, ...data } = props;\n`
|
|
88
|
+
+ `\t\treturn mount("${name}", Stringify(data), children);\n`
|
|
87
89
|
+ `\t},\n`;
|
|
88
90
|
}
|
|
89
91
|
out += "}\nexport default Client;";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const generic = `import { Parameterized, ParameterShaper } from "htmx-router/util/parameters";
|
|
2
2
|
import { RenderFunction } from "htmx-router";
|
|
3
|
-
import { Deferral } from "htmx-router/
|
|
3
|
+
import { Deferral } from "htmx-router/defer";
|
|
4
4
|
|
|
5
5
|
export function Defer<T extends ParameterShaper>(props: {
|
|
6
6
|
params?: Parameterized<T>,
|
package/internal/mount.js
CHANGED
|
@@ -33,30 +33,68 @@ function ClientMounter() {
|
|
|
33
33
|
const global = window;
|
|
34
34
|
const mountRequests = new Array();
|
|
35
35
|
function RequestMount(funcName, json) {
|
|
36
|
-
const elm = document.currentScript.
|
|
36
|
+
const elm = document.currentScript.parentElement;
|
|
37
37
|
if (elm.hasAttribute("mounted"))
|
|
38
38
|
return;
|
|
39
|
+
if (!document.body.contains(elm))
|
|
40
|
+
return;
|
|
39
41
|
mountRequests.push([funcName, elm, json]);
|
|
40
42
|
}
|
|
41
|
-
function Mount() {
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
function Mount([funcName, element, json]) {
|
|
44
|
+
console.info("hydrating", funcName, "into", element);
|
|
45
|
+
const func = global.CLIENT[funcName];
|
|
46
|
+
if (!func)
|
|
47
|
+
throw new Error(`Component ${funcName} is missing from client manifest`);
|
|
48
|
+
func(element, json);
|
|
49
|
+
element.setAttribute("mounted", "yes");
|
|
50
|
+
}
|
|
51
|
+
function MountAll() {
|
|
44
52
|
if (!global.CLIENT)
|
|
45
53
|
throw new Error("Client manifest missing");
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
if (!
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
element.setAttribute("mounted", "yes");
|
|
54
|
+
if (mountRequests.length < 1)
|
|
55
|
+
return;
|
|
56
|
+
for (const request of mountRequests) {
|
|
57
|
+
if (!document.body.contains(request[1]))
|
|
58
|
+
continue;
|
|
59
|
+
Mount(request);
|
|
53
60
|
}
|
|
54
61
|
mountRequests.length = 0;
|
|
55
62
|
}
|
|
56
|
-
|
|
57
|
-
|
|
63
|
+
function MountStep() {
|
|
64
|
+
let request = mountRequests.shift();
|
|
65
|
+
while (request && !document.body.contains(request[1]))
|
|
66
|
+
request = mountRequests.shift();
|
|
67
|
+
if (!request) {
|
|
68
|
+
console.warn("No more pending mount requests");
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
Mount(request);
|
|
72
|
+
}
|
|
73
|
+
function Freeze() {
|
|
74
|
+
localStorage.setItem(freezeKey, "frozen");
|
|
75
|
+
window.location.reload();
|
|
76
|
+
}
|
|
77
|
+
function Unfreeze() {
|
|
78
|
+
localStorage.removeItem(freezeKey);
|
|
79
|
+
window.location.reload();
|
|
80
|
+
}
|
|
81
|
+
const freezeKey = "htmx-mount-freeze";
|
|
82
|
+
if (localStorage.getItem(freezeKey)) {
|
|
83
|
+
const ok = confirm("Client mounting is frozen, do you want to unfreeze");
|
|
84
|
+
if (ok)
|
|
85
|
+
Unfreeze();
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
document.addEventListener("DOMContentLoaded", MountAll);
|
|
89
|
+
document.addEventListener("htmx:load", MountAll);
|
|
90
|
+
}
|
|
58
91
|
return {
|
|
59
|
-
|
|
92
|
+
mountParentWith: RequestMount,
|
|
93
|
+
mount: {
|
|
94
|
+
freeze: Freeze,
|
|
95
|
+
unfreeze: Unfreeze,
|
|
96
|
+
step: MountStep
|
|
97
|
+
},
|
|
60
98
|
theme
|
|
61
99
|
};
|
|
62
100
|
}
|
package/internal/router.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export declare class GenericContext {
|
|
|
9
9
|
[key: string]: string;
|
|
10
10
|
};
|
|
11
11
|
url: URL;
|
|
12
|
-
render: (res: JSX.Element) => Response;
|
|
12
|
+
render: (res: JSX.Element, headers: Headers) => Promise<Response> | Response;
|
|
13
13
|
constructor(request: GenericContext["request"], url: GenericContext["url"], renderer: GenericContext["render"]);
|
|
14
14
|
shape<T extends ParameterShaper>(shape: T): RouteContext<T>;
|
|
15
15
|
}
|
package/navigate.d.ts
ADDED
package/navigate.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
function htmx() {
|
|
2
|
+
const htmx = window.htmx;
|
|
3
|
+
if (typeof htmx !== "object")
|
|
4
|
+
throw new Error("Missing htmx");
|
|
5
|
+
return htmx;
|
|
6
|
+
}
|
|
7
|
+
export async function navigate(href, pushUrl = true) {
|
|
8
|
+
if (typeof window !== "object")
|
|
9
|
+
return;
|
|
10
|
+
const url = new URL(href, window.location.href);
|
|
11
|
+
if (url.host !== window.location.host) {
|
|
12
|
+
window.location.assign(href);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
// Perform an HTMX GET request similar to hx-boost
|
|
16
|
+
await htmx().ajax("GET", href, {
|
|
17
|
+
target: 'body',
|
|
18
|
+
swap: 'outerHTML',
|
|
19
|
+
history: pushUrl
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
export function revalidate() {
|
|
23
|
+
return navigate("", false);
|
|
24
|
+
}
|
|
25
|
+
export async function htmxAppend(href, verb = "GET") {
|
|
26
|
+
await htmx().ajax(verb, href, {
|
|
27
|
+
target: 'body',
|
|
28
|
+
swap: 'beforeend',
|
|
29
|
+
history: false
|
|
30
|
+
});
|
|
31
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "htmx-router",
|
|
3
|
-
"version": "1.0.0-
|
|
3
|
+
"version": "1.0.0-pre5",
|
|
4
4
|
"description": "A lightweight SSR framework with server+client islands",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"htmx", "router", "client islands", "ssr", "vite"
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"bugs": {
|
|
23
23
|
"url": "https://github.com/AjaniBilby/htmx-router/issues"
|
|
24
24
|
},
|
|
25
|
-
"homepage": "https://
|
|
25
|
+
"homepage": "https://htmx-router.ajanibilby.com/",
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"es-module-lexer": "^1.5.4",
|
|
28
28
|
"vite": "^6.0.6"
|
package/router.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export declare class RouteContext<T extends ParameterShaper = {}> {
|
|
|
12
12
|
readonly cookie: Cookies;
|
|
13
13
|
readonly params: Parameterized<T>;
|
|
14
14
|
readonly url: URL;
|
|
15
|
-
render:
|
|
15
|
+
render: GenericContext["render"];
|
|
16
16
|
constructor(base: GenericContext | RouteContext, params: ParameterPrelude<T>, shape: T);
|
|
17
17
|
}
|
|
18
18
|
export declare class RouteTree {
|
package/router.js
CHANGED
|
@@ -173,7 +173,7 @@ class RouteLeaf {
|
|
|
173
173
|
return null;
|
|
174
174
|
if (res instanceof Response)
|
|
175
175
|
return res;
|
|
176
|
-
return ctx.render(res);
|
|
176
|
+
return await ctx.render(res, ctx.headers);
|
|
177
177
|
}
|
|
178
178
|
async error(ctx, e) {
|
|
179
179
|
if (!this.module.error)
|
|
@@ -181,7 +181,7 @@ class RouteLeaf {
|
|
|
181
181
|
const res = await this.module.error(ctx, e);
|
|
182
182
|
if (res instanceof Response)
|
|
183
183
|
return res;
|
|
184
|
-
return ctx.render(res);
|
|
184
|
+
return await ctx.render(res, ctx.headers);
|
|
185
185
|
}
|
|
186
186
|
async renderWrapper(ctx) {
|
|
187
187
|
try {
|
package/vite/bundle-splitter.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { ServerOnlyWarning } from "../internal/util.js";
|
|
2
2
|
ServerOnlyWarning("bundle-splitter");
|
|
3
|
+
import { init, parse } from "es-module-lexer";
|
|
4
|
+
await init;
|
|
3
5
|
const serverPattern = /\.server\.[tj]s(x)?/;
|
|
4
6
|
const clientPattern = /\.client\.[tj]s(x)?/;
|
|
5
|
-
const BLANK_MODULE = "export {};";
|
|
6
7
|
export function BundleSplitter() {
|
|
7
8
|
return {
|
|
8
9
|
name: "htmx-bundle-splitter",
|
|
@@ -11,16 +12,27 @@ export function BundleSplitter() {
|
|
|
11
12
|
const ssr = options?.ssr || false;
|
|
12
13
|
const pattern = ssr ? clientPattern : serverPattern;
|
|
13
14
|
if (pattern.test(id))
|
|
14
|
-
return
|
|
15
|
+
return StubExports(code);
|
|
15
16
|
if (ssr) {
|
|
16
17
|
if (code.startsWith('"use client"'))
|
|
17
|
-
return
|
|
18
|
+
return StubExports(code);
|
|
18
19
|
}
|
|
19
20
|
else {
|
|
20
21
|
if (code.startsWith('"use server"'))
|
|
21
|
-
return
|
|
22
|
+
return StubExports(code);
|
|
22
23
|
}
|
|
23
24
|
return code;
|
|
24
25
|
}
|
|
25
26
|
};
|
|
26
27
|
}
|
|
28
|
+
// A server only module may be imported into client code,
|
|
29
|
+
// But as long as it isn't used this shouldn't break the program.
|
|
30
|
+
// However JS will crash the import if the export name it's looking for isn't present.
|
|
31
|
+
// Even if it's never used.
|
|
32
|
+
// So we must place some stubs just in case
|
|
33
|
+
function StubExports(code) {
|
|
34
|
+
const exports = parse(code)[1];
|
|
35
|
+
return exports.map(x => x.n === "default"
|
|
36
|
+
? "export default undefined;"
|
|
37
|
+
: `export const ${x.n} = undefined;`).join("\n");
|
|
38
|
+
}
|