htmx-router 1.0.0-alpha.2 → 1.0.0-alpha.3
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/bin/cli/index.js +1 -1
- package/bin/client/index.js +73 -51
- package/bin/client/watch.d.ts +1 -1
- package/bin/client/watch.js +3 -11
- package/bin/helper.d.ts +1 -0
- package/bin/helper.js +7 -0
- package/bin/util/css.js +9 -5
- package/package.json +2 -1
package/bin/cli/index.js
CHANGED
|
@@ -56,9 +56,9 @@ export function Scripts() {
|
|
|
56
56
|
if (headCache) return headCache;
|
|
57
57
|
|
|
58
58
|
const res = <>
|
|
59
|
+
<link href={GetSheetUrl()} rel="stylesheet"></link>
|
|
59
60
|
{ isProduction ? "" : <script type="module" src="/@vite/client"></script> }
|
|
60
61
|
<script type="module" src={clientEntry}></script>
|
|
61
|
-
<link href={GetSheetUrl()} rel="stylesheet"></link>
|
|
62
62
|
<script src={GetMountUrl()}></script>
|
|
63
63
|
</>;
|
|
64
64
|
|
package/bin/client/index.js
CHANGED
|
@@ -13,29 +13,52 @@ export async function GenerateClient(config, force = false) {
|
|
|
13
13
|
if (!force && ExtractHash(history) === hash)
|
|
14
14
|
return;
|
|
15
15
|
await init;
|
|
16
|
+
const imported = ParseImports(source);
|
|
17
|
+
await Promise.all([
|
|
18
|
+
writeFile(config.source, source
|
|
19
|
+
+ pivot
|
|
20
|
+
+ `// hash: ${hash}\n`
|
|
21
|
+
+ BuildClientServer(imported)),
|
|
22
|
+
writeFile(CutString(config.source, ".", -1)[0] + ".manifest.tsx", BuildClientManifest(config.adapter, imported))
|
|
23
|
+
]);
|
|
24
|
+
}
|
|
25
|
+
function ParseImports(source) {
|
|
16
26
|
const parsed = parse(source)[0];
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
if (imp.a !== -1)
|
|
27
|
+
const out = [];
|
|
28
|
+
for (const imported of parsed) {
|
|
29
|
+
if (imported.a !== -1)
|
|
21
30
|
continue;
|
|
22
|
-
if (
|
|
31
|
+
if (imported.t !== 1)
|
|
23
32
|
continue;
|
|
24
|
-
|
|
25
|
-
|
|
33
|
+
const href = source.slice(imported.s, imported.e);
|
|
34
|
+
const front = source.slice(imported.ss, imported.s);
|
|
35
|
+
const start = front.indexOf("{");
|
|
36
|
+
if (start === -1) {
|
|
37
|
+
const middle = CutString(CutString(front, "import")[1], "from", -1)[0];
|
|
38
|
+
out.push({ mapping: ExtractName(middle), href });
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const end = front.lastIndexOf("}");
|
|
42
|
+
const segments = front.slice(start + 1, end).split(",");
|
|
43
|
+
out.push({ mapping: segments.map(ExtractName), href });
|
|
26
44
|
}
|
|
27
|
-
|
|
28
|
-
+ pivot
|
|
29
|
-
+ `// hash: ${hash}\n`
|
|
30
|
-
+ BuildClientServer(names));
|
|
31
|
-
await writeFile(CutString(config.source, ".", -1)[0] + ".manifest.tsx", BuildClientManifest(config.adapter, names, imports));
|
|
45
|
+
return out;
|
|
32
46
|
}
|
|
33
|
-
function BuildClientServer(
|
|
34
|
-
|
|
47
|
+
function BuildClientServer(imported) {
|
|
48
|
+
const names = new Array();
|
|
49
|
+
for (const imp of imported) {
|
|
50
|
+
if (Array.isArray(imp.mapping))
|
|
51
|
+
names.push(...imp.mapping.map(x => x.name));
|
|
52
|
+
else
|
|
53
|
+
names.push(imp.mapping.name);
|
|
54
|
+
}
|
|
55
|
+
let out = `import { StyleClass } from "htmx-router";\n`
|
|
56
|
+
+ `const island = new StyleClass("i", ".this{display:contents;}\\n").name;\n\n`
|
|
57
|
+
+ "type FirstArg<T> = T extends (arg: infer U, ...args: any[]) => any ? U : never;\n"
|
|
35
58
|
+ "function mount(name: string, data: string, ssr?: JSX.Element) {\n"
|
|
36
59
|
+ "\treturn (<>\n"
|
|
37
|
-
+ `\t\t<div
|
|
38
|
-
+ "\t\t<script>{`Router.mountAboveWith(
|
|
60
|
+
+ `\t\t<div className={island}>{ssr}</div>\n`
|
|
61
|
+
+ "\t\t<script>{`Router.mountAboveWith('${name}', ${data})`}</script>\n"
|
|
39
62
|
+ "\t</>);\n"
|
|
40
63
|
+ "}\n"
|
|
41
64
|
+ "\n"
|
|
@@ -47,52 +70,51 @@ function BuildClientServer(names) {
|
|
|
47
70
|
+ `\t},\n`;
|
|
48
71
|
}
|
|
49
72
|
out += "}\nexport default Client;\n\n"
|
|
50
|
-
+ `
|
|
51
|
-
+
|
|
52
|
-
+ `}`;
|
|
73
|
+
+ `import { __RebuildClient__ } from "htmx-router/bin/client/watch.js";\n`
|
|
74
|
+
+ `__RebuildClient__();`;
|
|
53
75
|
return out;
|
|
54
76
|
}
|
|
55
|
-
|
|
77
|
+
const renderer = {
|
|
78
|
+
react: '\t\tconst r = await import("react-dom/client");\n'
|
|
79
|
+
+ "\t\tr.createRoot(element).render(<C {...props} />);\n"
|
|
80
|
+
};
|
|
81
|
+
function BuildClientManifest(type, imports) {
|
|
56
82
|
let out = "/*------------------------------------------\n"
|
|
57
83
|
+ " * Generated by htmx-router *\n"
|
|
58
84
|
+ " * Warn: Any changes will be overwritten *\n"
|
|
59
|
-
+ "-------------------------------------------*/\n"
|
|
60
|
-
+
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
default:
|
|
66
|
-
console.error(`Unsupported client adapter ${type}`);
|
|
67
|
-
process.exit(1);
|
|
85
|
+
+ "-------------------------------------------*/\n\n"
|
|
86
|
+
+ "const client = {\n";
|
|
87
|
+
const render = renderer[type];
|
|
88
|
+
if (!render) {
|
|
89
|
+
console.error(`Unsupported client adapter ${type}`);
|
|
90
|
+
process.exit(1);
|
|
68
91
|
}
|
|
69
|
-
|
|
92
|
+
for (const imported of imports) {
|
|
93
|
+
if (Array.isArray(imported.mapping)) {
|
|
94
|
+
for (const map of imported.mapping) {
|
|
95
|
+
out += `\t${map.name}: async (element: HTMLElement, props: any) => {\n`
|
|
96
|
+
+ `\t\tconst C = (await import("${imported.href}")).${map.original};\n`
|
|
97
|
+
+ render
|
|
98
|
+
+ `\t},\n`;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
out += `\t${imported.mapping.name}: async (element: HTMLElement, props: any) => {\n`
|
|
103
|
+
+ `\t\tconst C = (await import("${imported.href}")).default;\n`
|
|
104
|
+
+ render
|
|
105
|
+
+ `\t},\n`;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
out += "}\nexport default client;\n"
|
|
70
109
|
+ "(window as any).CLIENT = client;";
|
|
71
110
|
return out;
|
|
72
111
|
}
|
|
73
|
-
function BuildReactClientManifest(names) {
|
|
74
|
-
let out = `import ReactDOM from "react-dom/client";\n\n`
|
|
75
|
-
+ "const client = {\n";
|
|
76
|
-
for (const name of names)
|
|
77
|
-
out += `\t${name}: (element: HTMLElement, props: any) => ReactDOM.createRoot(element).render(<${name} {...props} />),\n`;
|
|
78
|
-
out += "};\n";
|
|
79
|
-
return out;
|
|
80
|
-
}
|
|
81
|
-
function ExtractNames(str) {
|
|
82
|
-
const start = str.indexOf("{");
|
|
83
|
-
if (start === -1) {
|
|
84
|
-
const middle = CutString(CutString(str, "import")[1], "from", -1)[0];
|
|
85
|
-
return [ExtractName(middle)];
|
|
86
|
-
}
|
|
87
|
-
const end = str.lastIndexOf("}");
|
|
88
|
-
const segments = str.slice(start + 1, end).split(",");
|
|
89
|
-
return segments.map(ExtractName);
|
|
90
|
-
}
|
|
91
112
|
function ExtractName(str) {
|
|
92
113
|
const parts = CutString(str, "as");
|
|
93
|
-
if (parts[1])
|
|
94
|
-
return parts[1].trim();
|
|
95
|
-
|
|
114
|
+
if (parts[1].length !== 0)
|
|
115
|
+
return { name: parts[1].trim(), original: parts[0].trim() };
|
|
116
|
+
const name = parts[0].trim();
|
|
117
|
+
return { name, original: name };
|
|
96
118
|
}
|
|
97
119
|
function ExtractHash(source) {
|
|
98
120
|
const regex = /\/\/\s+hash\s*:\s*(\w+)/;
|
package/bin/client/watch.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function
|
|
1
|
+
export declare function __RebuildClient__(): Promise<void>;
|
package/bin/client/watch.js
CHANGED
|
@@ -1,19 +1,11 @@
|
|
|
1
|
-
import { watch } from "fs";
|
|
2
1
|
import { GenerateClient } from "../client/index.js";
|
|
3
2
|
import { ReadConfig } from "../cli/config.js";
|
|
4
|
-
export async function
|
|
5
|
-
if (process.env.NODE_ENV === "production")
|
|
6
|
-
console.warn("Watching client islands is disabled in production");
|
|
3
|
+
export async function __RebuildClient__() {
|
|
4
|
+
if (process.env.NODE_ENV === "production")
|
|
7
5
|
return;
|
|
8
|
-
}
|
|
9
6
|
const config = await ReadConfig();
|
|
10
7
|
const client = config.client;
|
|
11
8
|
if (!client)
|
|
12
9
|
return;
|
|
13
|
-
|
|
14
|
-
console.info("Building client");
|
|
15
|
-
GenerateClient(client, false).catch(console.error); // rebuild only if the hash has changed
|
|
16
|
-
};
|
|
17
|
-
watch(client.source, rebuild);
|
|
18
|
-
rebuild();
|
|
10
|
+
GenerateClient(client, false).catch(console.error);
|
|
19
11
|
}
|
package/bin/helper.d.ts
CHANGED
package/bin/helper.js
CHANGED
|
@@ -25,3 +25,10 @@ export function CutString(str, pivot, offset = 1) {
|
|
|
25
25
|
}
|
|
26
26
|
return [str, ""];
|
|
27
27
|
}
|
|
28
|
+
export function Singleton(name, cb) {
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
30
|
+
const g = globalThis;
|
|
31
|
+
g.__singletons ??= {};
|
|
32
|
+
g.__singletons[name] ??= cb();
|
|
33
|
+
return g.__singletons[name];
|
|
34
|
+
}
|
package/bin/util/css.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { QuickHash } from "../util/hash.js";
|
|
2
2
|
const classNamePattern = /^[a-zA-Z_][a-zA-Z0-9_-]*$/;
|
|
3
|
-
const registry = new
|
|
3
|
+
const registry = new Map();
|
|
4
4
|
let cache = null;
|
|
5
5
|
/**
|
|
6
6
|
* Create a new css class to be included in the sheet
|
|
@@ -17,7 +17,7 @@ export class StyleClass {
|
|
|
17
17
|
this.name = `${name}-${this.hash}`;
|
|
18
18
|
style = style.replaceAll(".this", "." + this.name);
|
|
19
19
|
this.style = style;
|
|
20
|
-
registry.
|
|
20
|
+
registry.set(this.name, this);
|
|
21
21
|
cache = null;
|
|
22
22
|
}
|
|
23
23
|
toString() {
|
|
@@ -43,9 +43,13 @@ export function _resolve(fragments) {
|
|
|
43
43
|
return new Response(build.sheet, { headers });
|
|
44
44
|
}
|
|
45
45
|
function BuildSheet() {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const
|
|
46
|
+
let composite = "";
|
|
47
|
+
let sheet = "";
|
|
48
|
+
for (const [key, def] of registry) {
|
|
49
|
+
composite += key;
|
|
50
|
+
sheet += def.style;
|
|
51
|
+
}
|
|
52
|
+
const hash = QuickHash(composite);
|
|
49
53
|
cache = { hash, sheet };
|
|
50
54
|
return cache;
|
|
51
55
|
}
|
package/package.json
CHANGED