htmx-router 1.0.0-alpha.1 → 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 +16 -3
- package/bin/client/entry.d.ts +1 -0
- package/bin/client/entry.js +12 -0
- package/bin/client/index.d.ts +3 -0
- package/bin/client/index.js +76 -51
- package/bin/client/mount.js +2 -1
- 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/request/http.js +0 -5
- package/bin/request/native.js +1 -11
- package/bin/util/cookies.d.ts +3 -0
- package/bin/util/cookies.js +3 -0
- package/bin/util/css.d.ts +4 -0
- package/bin/util/css.js +15 -7
- package/bin/util/dynamic.d.ts +3 -0
- package/bin/util/dynamic.js +3 -0
- package/bin/util/endpoint.d.ts +4 -0
- package/bin/util/endpoint.js +4 -0
- package/bin/util/event-source.d.ts +2 -2
- package/bin/util/event-source.js +2 -2
- package/bin/util/hash.d.ts +3 -0
- package/bin/util/hash.js +3 -0
- package/bin/util/parameters.d.ts +3 -0
- package/bin/util/parameters.js +3 -0
- package/bin/util/path-builder.d.ts +1 -0
- package/bin/util/path-builder.js +43 -0
- package/bin/util/shell.d.ts +9 -9
- package/bin/util/shell.js +7 -0
- package/package.json +2 -1
package/bin/cli/index.js
CHANGED
|
@@ -14,10 +14,13 @@ await writeFile(config.router.output, `/*---------------------------------------
|
|
|
14
14
|
|
|
15
15
|
import { GenericContext, RouteTree } from "htmx-router/bin/router";
|
|
16
16
|
import { RegisterDynamic } from "htmx-router/bin/util/dynamic";
|
|
17
|
+
import { GetClientEntryURL } from 'htmx-router/bin/client/entry';
|
|
17
18
|
import { GetMountUrl } from 'htmx-router/bin/client/mount';
|
|
18
19
|
import { GetSheetUrl } from 'htmx-router/bin/util/css';
|
|
19
20
|
import { RouteModule } from "htmx-router";
|
|
21
|
+
import { resolve } from "path";
|
|
20
22
|
|
|
23
|
+
(globalThis as any).HTMX_ROUTER_ROOT = resolve('${config.router.folder.replaceAll("\\", "/")}');
|
|
21
24
|
const modules = import.meta.glob('${routes}/**/*.{ts,tsx}', { eager: true });
|
|
22
25
|
|
|
23
26
|
export const tree = new RouteTree();
|
|
@@ -46,11 +49,21 @@ export function Dynamic<T extends Record<string, string>>(props: {
|
|
|
46
49
|
>{props.children ? props.children : ""}</div>
|
|
47
50
|
}
|
|
48
51
|
|
|
49
|
-
|
|
50
|
-
|
|
52
|
+
let headCache: JSX.Element | null = null;
|
|
53
|
+
const isProduction = process.env.NODE_ENV === "production";
|
|
54
|
+
const clientEntry = await GetClientEntryURL();
|
|
55
|
+
export function Scripts() {
|
|
56
|
+
if (headCache) return headCache;
|
|
57
|
+
|
|
58
|
+
const res = <>
|
|
51
59
|
<link href={GetSheetUrl()} rel="stylesheet"></link>
|
|
60
|
+
{ isProduction ? "" : <script type="module" src="/@vite/client"></script> }
|
|
61
|
+
<script type="module" src={clientEntry}></script>
|
|
52
62
|
<script src={GetMountUrl()}></script>
|
|
53
|
-
|
|
63
|
+
</>;
|
|
64
|
+
|
|
65
|
+
if (isProduction) headCache = res;
|
|
66
|
+
return res;
|
|
54
67
|
}`);
|
|
55
68
|
if (config.client) {
|
|
56
69
|
console.info("Building client islands");
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function GetClientEntryURL(): Promise<any>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { readFile } from "fs/promises";
|
|
2
|
+
export async function GetClientEntryURL() {
|
|
3
|
+
if (process.env.NODE_ENV !== "production")
|
|
4
|
+
return "/app/entry.client.ts";
|
|
5
|
+
const config = JSON.parse(await readFile("./dist/client/.vite/manifest.json", "utf8"));
|
|
6
|
+
for (const key in config) {
|
|
7
|
+
const def = config[key];
|
|
8
|
+
if (!def.isEntry)
|
|
9
|
+
continue;
|
|
10
|
+
return def.file;
|
|
11
|
+
}
|
|
12
|
+
}
|
package/bin/client/index.d.ts
CHANGED
package/bin/client/index.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds the SSR and client side mounter for client components
|
|
3
|
+
*/
|
|
1
4
|
import { readFile, writeFile } from "fs/promises";
|
|
2
5
|
import { init, parse } from "es-module-lexer";
|
|
3
6
|
import { QuickHash } from "../util/hash.js";
|
|
@@ -10,29 +13,52 @@ export async function GenerateClient(config, force = false) {
|
|
|
10
13
|
if (!force && ExtractHash(history) === hash)
|
|
11
14
|
return;
|
|
12
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) {
|
|
13
26
|
const parsed = parse(source)[0];
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
if (imp.a !== -1)
|
|
27
|
+
const out = [];
|
|
28
|
+
for (const imported of parsed) {
|
|
29
|
+
if (imported.a !== -1)
|
|
18
30
|
continue;
|
|
19
|
-
if (
|
|
31
|
+
if (imported.t !== 1)
|
|
20
32
|
continue;
|
|
21
|
-
|
|
22
|
-
|
|
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 });
|
|
23
44
|
}
|
|
24
|
-
|
|
25
|
-
+ pivot
|
|
26
|
-
+ `// hash: ${hash}\n`
|
|
27
|
-
+ BuildClientServer(names));
|
|
28
|
-
await writeFile(CutString(config.source, ".", -1)[0] + ".manifest.tsx", BuildClientManifest(config.adapter, names, imports));
|
|
45
|
+
return out;
|
|
29
46
|
}
|
|
30
|
-
function BuildClientServer(
|
|
31
|
-
|
|
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"
|
|
32
58
|
+ "function mount(name: string, data: string, ssr?: JSX.Element) {\n"
|
|
33
59
|
+ "\treturn (<>\n"
|
|
34
|
-
+ `\t\t<div
|
|
35
|
-
+ "\t\t<script>{`Router.mountAboveWith(
|
|
60
|
+
+ `\t\t<div className={island}>{ssr}</div>\n`
|
|
61
|
+
+ "\t\t<script>{`Router.mountAboveWith('${name}', ${data})`}</script>\n"
|
|
36
62
|
+ "\t</>);\n"
|
|
37
63
|
+ "}\n"
|
|
38
64
|
+ "\n"
|
|
@@ -44,52 +70,51 @@ function BuildClientServer(names) {
|
|
|
44
70
|
+ `\t},\n`;
|
|
45
71
|
}
|
|
46
72
|
out += "}\nexport default Client;\n\n"
|
|
47
|
-
+ `
|
|
48
|
-
+
|
|
49
|
-
+ `}`;
|
|
73
|
+
+ `import { __RebuildClient__ } from "htmx-router/bin/client/watch.js";\n`
|
|
74
|
+
+ `__RebuildClient__();`;
|
|
50
75
|
return out;
|
|
51
76
|
}
|
|
52
|
-
|
|
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) {
|
|
53
82
|
let out = "/*------------------------------------------\n"
|
|
54
83
|
+ " * Generated by htmx-router *\n"
|
|
55
84
|
+ " * Warn: Any changes will be overwritten *\n"
|
|
56
|
-
+ "-------------------------------------------*/\n"
|
|
57
|
-
+
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
default:
|
|
63
|
-
console.error(`Unsupported client adapter ${type}`);
|
|
64
|
-
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);
|
|
65
91
|
}
|
|
66
|
-
|
|
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"
|
|
67
109
|
+ "(window as any).CLIENT = client;";
|
|
68
110
|
return out;
|
|
69
111
|
}
|
|
70
|
-
function BuildReactClientManifest(names) {
|
|
71
|
-
let out = `import ReactDOM from "react-dom/client";\n\n`
|
|
72
|
-
+ "const client = {\n";
|
|
73
|
-
for (const name of names)
|
|
74
|
-
out += `\t${name}: (element: HTMLElement, props: any) => ReactDOM.createRoot(element).render(<${name} {...props} />),\n`;
|
|
75
|
-
out += "};\n";
|
|
76
|
-
return out;
|
|
77
|
-
}
|
|
78
|
-
function ExtractNames(str) {
|
|
79
|
-
const start = str.indexOf("{");
|
|
80
|
-
if (start === -1) {
|
|
81
|
-
const middle = CutString(CutString(str, "import")[1], "from", -1)[0];
|
|
82
|
-
return [ExtractName(middle)];
|
|
83
|
-
}
|
|
84
|
-
const end = str.lastIndexOf("}");
|
|
85
|
-
const segments = str.slice(start + 1, end).split(",");
|
|
86
|
-
return segments.map(ExtractName);
|
|
87
|
-
}
|
|
88
112
|
function ExtractName(str) {
|
|
89
113
|
const parts = CutString(str, "as");
|
|
90
|
-
if (parts[1])
|
|
91
|
-
return parts[1].trim();
|
|
92
|
-
|
|
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 };
|
|
93
118
|
}
|
|
94
119
|
function ExtractHash(source) {
|
|
95
120
|
const regex = /\/\/\s+hash\s*:\s*(\w+)/;
|
package/bin/client/mount.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { QuickHash } from "../util/hash.js";
|
|
2
2
|
import { CutString } from "../helper.js";
|
|
3
|
+
// this function simply exists so it can be stringified and written into the client js bundle
|
|
3
4
|
function ClientMounter() {
|
|
4
5
|
const theme = {
|
|
5
6
|
infer: () => {
|
|
@@ -36,8 +37,8 @@ function ClientMounter() {
|
|
|
36
37
|
return;
|
|
37
38
|
if (!global.CLIENT)
|
|
38
39
|
throw new Error("Client manifest missing");
|
|
39
|
-
console.info("hydrating...");
|
|
40
40
|
for (const [funcName, element, json] of mountRequests) {
|
|
41
|
+
console.info("hydrating", funcName, "into", element);
|
|
41
42
|
const func = global.CLIENT[funcName];
|
|
42
43
|
if (!func)
|
|
43
44
|
throw new Error(`Component ${funcName} is missing from client manifest`);
|
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).catch(console.error);
|
|
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/request/http.js
CHANGED
|
@@ -7,11 +7,6 @@ export function createRequestHandler(config) {
|
|
|
7
7
|
let { response, headers } = await Resolve(request, mod.tree, config);
|
|
8
8
|
res.writeHead(response.status, headers);
|
|
9
9
|
let rendered = await response.text();
|
|
10
|
-
if (config.viteDevServer) {
|
|
11
|
-
if (!headers["x-partial"] && response.headers.get("content-type")?.startsWith("text/html")) {
|
|
12
|
-
rendered = await config.viteDevServer.transformIndexHtml(req.url || "", rendered);
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
10
|
res.end(rendered);
|
|
16
11
|
}
|
|
17
12
|
catch (e) {
|
package/bin/request/native.js
CHANGED
|
@@ -3,17 +3,7 @@ export function createRequestHandler(config) {
|
|
|
3
3
|
return async (req) => {
|
|
4
4
|
try {
|
|
5
5
|
const mod = typeof config.build === "function" ? await config.build() : await config.build;
|
|
6
|
-
let { response
|
|
7
|
-
if (config.viteDevServer) {
|
|
8
|
-
if (!headers["x-partial"] && response.headers.get("content-type")?.startsWith("text/html")) {
|
|
9
|
-
const rendered = await config.viteDevServer.transformIndexHtml(req.url || "", await response.text());
|
|
10
|
-
return new Response(rendered, {
|
|
11
|
-
status: response.status,
|
|
12
|
-
statusText: response.statusText,
|
|
13
|
-
headers: response.headers,
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
}
|
|
6
|
+
let { response } = await Resolve(req, mod.tree, config);
|
|
17
7
|
return response;
|
|
18
8
|
}
|
|
19
9
|
catch (e) {
|
package/bin/util/cookies.d.ts
CHANGED
|
@@ -9,6 +9,9 @@ export interface CookieOptions {
|
|
|
9
9
|
sameSite?: "lax" | "strict" | "none";
|
|
10
10
|
secure?: boolean;
|
|
11
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Helper provided in the Generic and RouteContext which provides reading and updating cookies
|
|
14
|
+
*/
|
|
12
15
|
export declare class Cookies {
|
|
13
16
|
private map;
|
|
14
17
|
private config;
|
package/bin/util/cookies.js
CHANGED
package/bin/util/css.d.ts
CHANGED
package/bin/util/css.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
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
|
+
/**
|
|
6
|
+
* Create a new css class to be included in the sheet
|
|
7
|
+
* Use .this as your class name in the source, and it will be replaced with a unique name
|
|
8
|
+
*/
|
|
5
9
|
export class StyleClass {
|
|
6
|
-
name;
|
|
7
|
-
style;
|
|
10
|
+
name; // unique name generated based on the original name and hash of the style
|
|
11
|
+
style; // the mutated source
|
|
8
12
|
hash;
|
|
9
13
|
constructor(name, style) {
|
|
10
14
|
if (!name.match(classNamePattern))
|
|
@@ -13,7 +17,7 @@ export class StyleClass {
|
|
|
13
17
|
this.name = `${name}-${this.hash}`;
|
|
14
18
|
style = style.replaceAll(".this", "." + this.name);
|
|
15
19
|
this.style = style;
|
|
16
|
-
registry.
|
|
20
|
+
registry.set(this.name, this);
|
|
17
21
|
cache = null;
|
|
18
22
|
}
|
|
19
23
|
toString() {
|
|
@@ -39,9 +43,13 @@ export function _resolve(fragments) {
|
|
|
39
43
|
return new Response(build.sheet, { headers });
|
|
40
44
|
}
|
|
41
45
|
function BuildSheet() {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
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);
|
|
45
53
|
cache = { hash, sheet };
|
|
46
54
|
return cache;
|
|
47
55
|
}
|
package/bin/util/dynamic.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This whole file is only for internal use but the generated router for the <Dynamic> component
|
|
3
|
+
*/
|
|
1
4
|
import { GenericContext } from "../router.js";
|
|
2
5
|
export declare function RegisterDynamic<T>(load: Loader<T>): string;
|
|
3
6
|
type Loader<T> = (params: T, ctx: GenericContext) => Promise<JSX.Element>;
|
package/bin/util/dynamic.js
CHANGED
package/bin/util/endpoint.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { RenderFunction } from "../types.js";
|
|
2
2
|
import { GenericContext } from "../router.js";
|
|
3
|
+
/**
|
|
4
|
+
* Create a route-less endpoint
|
|
5
|
+
* The name is optional and will be inferred from the function if not given (helpful for network waterfalls)
|
|
6
|
+
*/
|
|
3
7
|
export declare class Endpoint {
|
|
4
8
|
readonly render: RenderFunction<GenericContext>;
|
|
5
9
|
readonly name: string;
|
package/bin/util/endpoint.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { QuickHash } from "../util/hash.js";
|
|
2
2
|
const registry = new Map();
|
|
3
|
+
/**
|
|
4
|
+
* Create a route-less endpoint
|
|
5
|
+
* The name is optional and will be inferred from the function if not given (helpful for network waterfalls)
|
|
6
|
+
*/
|
|
3
7
|
export class Endpoint {
|
|
4
8
|
render;
|
|
5
9
|
name;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* Includes
|
|
2
|
+
* Helper for Server-Sent-Events, with auto close on SIGTERM and SIGHUP messages
|
|
3
|
+
* Includes a keep alive empty packet sent every 30sec (because Chrome implodes at 120sec, and can be unreliable at 60sec)
|
|
4
4
|
*/
|
|
5
5
|
export declare class EventSourceConnection {
|
|
6
6
|
private controller;
|
package/bin/util/event-source.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* Includes
|
|
2
|
+
* Helper for Server-Sent-Events, with auto close on SIGTERM and SIGHUP messages
|
|
3
|
+
* Includes a keep alive empty packet sent every 30sec (because Chrome implodes at 120sec, and can be unreliable at 60sec)
|
|
4
4
|
*/
|
|
5
5
|
export class EventSourceConnection {
|
|
6
6
|
controller;
|
package/bin/util/hash.d.ts
CHANGED
package/bin/util/hash.js
CHANGED
package/bin/util/parameters.d.ts
CHANGED
package/bin/util/parameters.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { relative } from "path";
|
|
2
|
+
/*
|
|
3
|
+
// This feature is disabled because vite doesn't compile import.meta.url to the original url when making the SSR build
|
|
4
|
+
// Even though it's used statically in the routes
|
|
5
|
+
|
|
6
|
+
const parameters = {
|
|
7
|
+
userID: Number
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const urlPath = RoutePathBuilder<typeof parameters>(import.meta.url);
|
|
11
|
+
|
|
12
|
+
export function loader({ params }: RouteContext<typeof parameters>) {
|
|
13
|
+
return <div>You are currently at {urlPath({ userID: params.userID.toString() })}</div>
|
|
14
|
+
}
|
|
15
|
+
*/
|
|
16
|
+
// This feature isn't particularly helpful for a router to refer to it's own URL
|
|
17
|
+
// But instead is useful because you could export this function
|
|
18
|
+
// Allowing other routes to use it, and thus if you move the file your imports will auto update by your LSP
|
|
19
|
+
// And thus so would your SSR rendered urls
|
|
20
|
+
function RoutePathBuilder(importUrl) {
|
|
21
|
+
// file runs all imports before actually executing the route.tsx
|
|
22
|
+
// so I have to do this BS instead of pre-compiling
|
|
23
|
+
let compiled = null;
|
|
24
|
+
return (params) => {
|
|
25
|
+
if (!compiled) {
|
|
26
|
+
const root = globalThis.HTMX_ROUTER_ROOT;
|
|
27
|
+
let path = importUrl.slice("file:///".length);
|
|
28
|
+
const raw = relative(root, path).replaceAll("\\", "/");
|
|
29
|
+
const i = raw.lastIndexOf(".");
|
|
30
|
+
compiled = raw.slice(0, i).split("/");
|
|
31
|
+
}
|
|
32
|
+
let path = "";
|
|
33
|
+
for (const frag of compiled) {
|
|
34
|
+
path += "/";
|
|
35
|
+
if (frag[0] != "$") {
|
|
36
|
+
path += frag;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
path += params[frag.slice(1)] || "_";
|
|
40
|
+
}
|
|
41
|
+
return path;
|
|
42
|
+
};
|
|
43
|
+
}
|
package/bin/util/shell.d.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* These types are just helpers which could be useful
|
|
3
|
+
* But the goal is to add a feature in the future to help will shells merging meta data
|
|
4
|
+
* Currently I want more experience using the slug-shell pattern before I build it out
|
|
5
|
+
*/
|
|
1
6
|
export type MetaDescriptor = {
|
|
2
7
|
charSet: "utf-8";
|
|
3
8
|
} | {
|
|
@@ -19,14 +24,9 @@ export type MetaDescriptor = {
|
|
|
19
24
|
} | {
|
|
20
25
|
[name: string]: unknown;
|
|
21
26
|
};
|
|
22
|
-
type LdJsonObject = {
|
|
27
|
+
export type LdJsonObject = {
|
|
23
28
|
[Key in string]?: LdJsonValue | undefined;
|
|
24
29
|
};
|
|
25
|
-
type LdJsonArray = LdJsonValue[] | readonly LdJsonValue[];
|
|
26
|
-
type LdJsonPrimitive = string | number | boolean | null;
|
|
27
|
-
type LdJsonValue = LdJsonPrimitive | LdJsonObject | LdJsonArray;
|
|
28
|
-
export type ShellOptions = {
|
|
29
|
-
meta?: Array<MetaDescriptor>;
|
|
30
|
-
} | undefined;
|
|
31
|
-
export type ShellProps<T> = T & ShellOptions;
|
|
32
|
-
export {};
|
|
30
|
+
export type LdJsonArray = LdJsonValue[] | readonly LdJsonValue[];
|
|
31
|
+
export type LdJsonPrimitive = string | number | boolean | null;
|
|
32
|
+
export type LdJsonValue = LdJsonPrimitive | LdJsonObject | LdJsonArray;
|
package/bin/util/shell.js
CHANGED
|
@@ -1 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* These types are just helpers which could be useful
|
|
3
|
+
* But the goal is to add a feature in the future to help will shells merging meta data
|
|
4
|
+
* Currently I want more experience using the slug-shell pattern before I build it out
|
|
5
|
+
*/
|
|
1
6
|
export {};
|
|
7
|
+
// export type ShellOptions = { meta?: Array<MetaDescriptor> } | undefined;
|
|
8
|
+
// export type ShellProps<T> = T & ShellOptions;
|
package/package.json
CHANGED