htmx-router 1.0.0-alpha.5 → 1.0.0-alpha.6

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.
Files changed (94) hide show
  1. package/{bin/util/css.js → css.js} +1 -1
  2. package/{bin/util/dynamic.d.ts → dynamic.d.ts} +2 -5
  3. package/{bin/util/dynamic.js → dynamic.js} +7 -5
  4. package/{bin/util/endpoint.d.ts → endpoint.d.ts} +2 -2
  5. package/{bin/util/endpoint.js → endpoint.js} +3 -1
  6. package/event-source.d.ts +26 -0
  7. package/event-source.js +123 -0
  8. package/example/eventdim-react/package.json +67 -0
  9. package/example/eventdim-react/server.js +90 -0
  10. package/example/island-react/global.d.ts +8 -0
  11. package/example/island-react/package.json +38 -0
  12. package/example/island-react/server.js +58 -0
  13. package/global.d.ts +7 -0
  14. package/index.d.ts +19 -0
  15. package/index.js +2 -0
  16. package/internal/cli/config.d.ts +13 -0
  17. package/internal/cli/config.js +11 -0
  18. package/internal/cli/index.js +15 -0
  19. package/internal/client.d.ts +1 -0
  20. package/{bin/client/entry.js → internal/client.js} +3 -1
  21. package/internal/compile/manifest.d.ts +1 -0
  22. package/{bin/client/index.js → internal/compile/manifest.js} +111 -65
  23. package/internal/compile/router.d.ts +1 -0
  24. package/internal/compile/router.js +51 -0
  25. package/internal/component/dynamic.d.ts +4 -0
  26. package/internal/component/dynamic.js +18 -0
  27. package/internal/component/head.d.ts +5 -0
  28. package/internal/component/head.js +22 -0
  29. package/internal/component/scripts.d.ts +4 -0
  30. package/internal/component/scripts.js +23 -0
  31. package/{bin/client → internal}/mount.js +9 -44
  32. package/internal/request/http.d.ts +10 -0
  33. package/internal/request/http.js +61 -0
  34. package/{bin → internal}/request/index.d.ts +3 -3
  35. package/internal/request/index.js +8 -0
  36. package/{bin → internal}/request/native.d.ts +2 -2
  37. package/internal/request/native.js +48 -0
  38. package/{bin/helper.d.ts → internal/util.d.ts} +2 -0
  39. package/{bin/helper.js → internal/util.js} +15 -0
  40. package/package.json +9 -5
  41. package/readme.md +2 -214
  42. package/{bin/request → request}/http.d.ts +1 -1
  43. package/{bin/request → request}/http.js +22 -4
  44. package/request/index.d.ts +13 -0
  45. package/request/index.js +3 -0
  46. package/request/native.d.ts +9 -0
  47. package/{bin/request → request}/native.js +2 -2
  48. package/{bin/util/response.d.ts → response.d.ts} +3 -1
  49. package/{bin/util/response.js → response.js} +5 -5
  50. package/{bin/router.d.ts → router.d.ts} +12 -10
  51. package/{bin/router.js → router.js} +61 -47
  52. package/{bin/util/shell.js → shell.js} +3 -1
  53. package/{bin/util → util}/parameters.d.ts +0 -3
  54. package/{bin/util → util}/parameters.js +0 -3
  55. package/{bin/util → util}/path-builder.js +2 -0
  56. package/util/route.d.ts +2 -0
  57. package/util/route.js +58 -0
  58. package/vite/bundle-splitter.d.ts +4 -0
  59. package/vite/bundle-splitter.js +26 -0
  60. package/vite/client-island.d.ts +4 -0
  61. package/vite/client-island.js +14 -0
  62. package/vite/code-splitting.d.ts +4 -0
  63. package/vite/code-splitting.js +14 -0
  64. package/vite/index.d.ts +3 -0
  65. package/vite/index.js +3 -0
  66. package/vite/router.d.ts +2 -0
  67. package/vite/router.js +29 -0
  68. package/bin/cli/config.d.ts +0 -10
  69. package/bin/cli/config.js +0 -4
  70. package/bin/cli/index.js +0 -66
  71. package/bin/client/entry.d.ts +0 -1
  72. package/bin/client/index.d.ts +0 -7
  73. package/bin/client/watch.d.ts +0 -1
  74. package/bin/client/watch.js +0 -11
  75. package/bin/index.d.ts +0 -11
  76. package/bin/index.js +0 -18
  77. package/bin/request/index.js +0 -6
  78. package/bin/response.d.ts +0 -9
  79. package/bin/response.js +0 -46
  80. package/bin/types.d.ts +0 -10
  81. package/bin/types.js +0 -1
  82. package/bin/util/event-source.d.ts +0 -16
  83. package/bin/util/event-source.js +0 -85
  84. package/bin/util/index.d.ts +0 -1
  85. package/bin/util/index.js +0 -7
  86. /package/{bin/util/cookies.d.ts → cookies.d.ts} +0 -0
  87. /package/{bin/util/cookies.js → cookies.js} +0 -0
  88. /package/{bin/util/css.d.ts → css.d.ts} +0 -0
  89. /package/{bin → internal}/cli/index.d.ts +0 -0
  90. /package/{bin/util → internal}/hash.d.ts +0 -0
  91. /package/{bin/util → internal}/hash.js +0 -0
  92. /package/{bin/client → internal}/mount.d.ts +0 -0
  93. /package/{bin/util/shell.d.ts → shell.d.ts} +0 -0
  94. /package/{bin/util → util}/path-builder.d.ts +0 -0
@@ -0,0 +1,26 @@
1
+ import { ServerOnlyWarning } from "../internal/util.js";
2
+ ServerOnlyWarning("bundle-splitter");
3
+ const serverPattern = /\.server\.[tj]s(x)?/;
4
+ const clientPattern = /\.client\.[tj]s(x)?/;
5
+ const BLANK_MODULE = "export {};";
6
+ export function BundleSplitter() {
7
+ return {
8
+ name: "htmx-bundle-splitter",
9
+ enforce: "pre",
10
+ transform: (code, id, options) => {
11
+ const ssr = options?.ssr || false;
12
+ const pattern = ssr ? clientPattern : serverPattern;
13
+ if (pattern.test(id))
14
+ return BLANK_MODULE;
15
+ if (ssr) {
16
+ if (code.startsWith('"use client"'))
17
+ return BLANK_MODULE;
18
+ }
19
+ else {
20
+ if (code.startsWith('"use server"'))
21
+ return BLANK_MODULE;
22
+ }
23
+ return code;
24
+ }
25
+ };
26
+ }
@@ -0,0 +1,4 @@
1
+ import type { Plugin } from "vite";
2
+ type SupportedFramework = "react";
3
+ export declare function ClientIsland(framework: SupportedFramework): Plugin;
4
+ export {};
@@ -0,0 +1,14 @@
1
+ import { resolve } from "path";
2
+ import { CompileManifest } from "../internal/compile/manifest.js";
3
+ export function ClientIsland(framework) {
4
+ const file = resolve("./app/manifest.tsx").replaceAll("\\", "/");
5
+ return {
6
+ name: "vite-plugin-htmx-client-island",
7
+ enforce: "pre",
8
+ transform: (code, id, options) => {
9
+ if (id !== file)
10
+ return code;
11
+ return CompileManifest(framework, code, options?.ssr || false);
12
+ }
13
+ };
14
+ }
@@ -0,0 +1,4 @@
1
+ import type { UserConfig } from "vite";
2
+ type Plugin = NonNullable<UserConfig["plugins"]>[number];
3
+ export declare function BundleSplitting(): Plugin;
4
+ export {};
@@ -0,0 +1,14 @@
1
+ const serverPattern = /\.server\.[tj]s(x)?/;
2
+ const clientPattern = /\.client\.[tj]s(x)?/;
3
+ export function BundleSplitting() {
4
+ return {
5
+ name: "htmx-bundle-splitter",
6
+ enforce: "pre",
7
+ transform: (code, id, options) => {
8
+ const pattern = options?.ssr ? clientPattern : serverPattern;
9
+ if (pattern.test(id))
10
+ return "export {};";
11
+ return code;
12
+ }
13
+ };
14
+ }
@@ -0,0 +1,3 @@
1
+ import { BundleSplitter } from "./bundle-splitter.js";
2
+ import { ClientIsland } from "./client-island.js";
3
+ export { BundleSplitter, ClientIsland };
package/vite/index.js ADDED
@@ -0,0 +1,3 @@
1
+ import { BundleSplitter } from "./bundle-splitter.js";
2
+ import { ClientIsland } from "./client-island.js";
3
+ export { BundleSplitter, ClientIsland };
@@ -0,0 +1,2 @@
1
+ import type { Plugin } from "vite";
2
+ export declare function Router(): Plugin;
package/vite/router.js ADDED
@@ -0,0 +1,29 @@
1
+ export function Router() {
2
+ const virtualModuleId = "virtual:htmx-router/dynamic.tsx";
3
+ const resolvedVirtualModuleId = '\0' + virtualModuleId;
4
+ return {
5
+ name: "vite-plugin-htmx-router",
6
+ resolveId(id) {
7
+ if (id === virtualModuleId)
8
+ return resolvedVirtualModuleId;
9
+ },
10
+ load(id) {
11
+ if (id !== resolvedVirtualModuleId)
12
+ return;
13
+ return source;
14
+ }
15
+ };
16
+ }
17
+ const source = `export function Scripts() {
18
+ if (headCache) return headCache;
19
+
20
+ const res = <>
21
+ <link href={GetSheetUrl()} rel="stylesheet"></link>
22
+ { isProduction ? "" : <script type="module" src="/@vite/client"></script> }
23
+ <script type="module" src={clientEntry}></script>
24
+ <script src={GetMountUrl()}></script>
25
+ </>;
26
+
27
+ if (isProduction) headCache = res;
28
+ return res;
29
+ }`;
@@ -1,10 +0,0 @@
1
- export declare function ReadConfig(): Promise<{
2
- client?: {
3
- adapter: string;
4
- source: string;
5
- };
6
- router: {
7
- folder: string;
8
- output: string;
9
- };
10
- }>;
package/bin/cli/config.js DELETED
@@ -1,4 +0,0 @@
1
- import { readFile } from "fs/promises";
2
- export async function ReadConfig() {
3
- return JSON.parse(await readFile(process.argv[2] || "./htmx-config.json", "utf-8"));
4
- }
package/bin/cli/index.js DELETED
@@ -1,66 +0,0 @@
1
- #!/usr/bin/env node
2
- "use strict";
3
- import { writeFile } from "fs/promises";
4
- import { relative } from "path";
5
- import { GenerateClient } from "../client/index.js";
6
- import { ReadConfig } from "../cli/config.js";
7
- const config = await ReadConfig();
8
- console.info("Building router");
9
- const routes = relative(config.router.output, config.router.folder).replaceAll("\\", "/").slice(1);
10
- await writeFile(config.router.output, `/*------------------------------------------
11
- * Generated by htmx-router *
12
- * Warn: Any changes will be overwritten *
13
- -------------------------------------------*/
14
- /* eslint-disable @typescript-eslint/no-explicit-any */
15
-
16
- import { GenericContext, RouteTree } from "htmx-router/bin/router";
17
- import { GetClientEntryURL } from 'htmx-router/bin/client/entry';
18
- import { DynamicReference } from "htmx-router/bin/util/dynamic";
19
- import { GetMountUrl } from 'htmx-router/bin/client/mount';
20
- import { GetSheetUrl } from 'htmx-router/bin/util/css';
21
- import { RouteModule } from "htmx-router";
22
- import { resolve } from "path";
23
-
24
- (globalThis as any).HTMX_ROUTER_ROOT = resolve('${config.router.folder.replaceAll("\\", "/")}');
25
- const modules = import.meta.glob('${routes}/**/*.{ts,tsx}', { eager: true });
26
-
27
- export const tree = new RouteTree();
28
- for (const path in modules) {
29
- const tail = path.lastIndexOf(".");
30
- const url = path.slice(${routes.length + 1}, tail);
31
- tree.ingest(url, modules[path] as RouteModule<any>);
32
- }
33
-
34
- export function Dynamic<T extends Record<string, string>>(props: {
35
- params?: T,
36
- loader: (ctx: GenericContext, params?: T) => Promise<JSX.Element>
37
- children?: JSX.Element
38
- }): JSX.Element {
39
- return <div
40
- hx-get={DynamicReference(props.loader, props.params)}
41
- hx-trigger="load"
42
- hx-swap="outerHTML transition:true"
43
- style={{ display: "contents" }}
44
- >{props.children ? props.children : ""}</div>
45
- }
46
-
47
- let headCache: JSX.Element | null = null;
48
- const isProduction = process.env.NODE_ENV === "production";
49
- const clientEntry = await GetClientEntryURL();
50
- export function Scripts() {
51
- if (headCache) return headCache;
52
-
53
- const res = <>
54
- <link href={GetSheetUrl()} rel="stylesheet"></link>
55
- { isProduction ? "" : <script type="module" src="/@vite/client"></script> }
56
- <script type="module" src={clientEntry}></script>
57
- <script src={GetMountUrl()}></script>
58
- </>;
59
-
60
- if (isProduction) headCache = res;
61
- return res;
62
- }`);
63
- if (config.client) {
64
- console.info("Building client islands");
65
- await GenerateClient(config.client, true);
66
- }
@@ -1 +0,0 @@
1
- export declare function GetClientEntryURL(): Promise<any>;
@@ -1,7 +0,0 @@
1
- /**
2
- * Builds the SSR and client side mounter for client components
3
- */
4
- export declare function GenerateClient(config: {
5
- adapter: string;
6
- source: string;
7
- }, force?: boolean): Promise<void>;
@@ -1 +0,0 @@
1
- export declare function __RebuildClient__(): Promise<void>;
@@ -1,11 +0,0 @@
1
- import { GenerateClient } from "../client/index.js";
2
- import { ReadConfig } from "../cli/config.js";
3
- export async function __RebuildClient__() {
4
- if (process.env.NODE_ENV === "production")
5
- return;
6
- const config = await ReadConfig();
7
- const client = config.client;
8
- if (!client)
9
- return;
10
- GenerateClient(client, false).catch(console.error);
11
- }
package/bin/index.d.ts DELETED
@@ -1,11 +0,0 @@
1
- import { RouteModule, CatchFunction, RenderFunction } from './types.js';
2
- import { RouteContext, GenericContext } from "./router.js";
3
- import { createRequestHandler } from './request/index.js';
4
- import { MetaDescriptor, RenderMetaDescriptor, ShellOptions, ApplyMetaDescriptorDefaults, LdJsonObject, OpenGraph, OpenGraphImage, OpenGraphVideo, OpenGraphAudio, InferShellOptions } from './util/shell.js';
5
- import { redirect, refresh, revalidate, text, json } from './util/response.js';
6
- import { Cookies, CookieOptions } from "./util/cookies.js";
7
- import { EventSourceConnection } from "./util/event-source.js";
8
- import { DynamicReference } from './util/dynamic.js';
9
- import { StyleClass } from './util/css.js';
10
- import { Endpoint } from './util/endpoint.js';
11
- export { createRequestHandler, CatchFunction, RenderFunction, RouteContext, RouteModule, GenericContext, Cookies, CookieOptions, Endpoint, DynamicReference, StyleClass, EventSourceConnection, redirect, refresh, revalidate, text, json, MetaDescriptor, RenderMetaDescriptor, ShellOptions, ApplyMetaDescriptorDefaults, LdJsonObject, OpenGraph, OpenGraphImage, OpenGraphVideo, OpenGraphAudio, InferShellOptions };
package/bin/index.js DELETED
@@ -1,18 +0,0 @@
1
- import { RouteContext, GenericContext } from "./router.js";
2
- import { createRequestHandler } from './request/index.js';
3
- import { RenderMetaDescriptor, ApplyMetaDescriptorDefaults } from './util/shell.js';
4
- import { redirect, refresh, revalidate, text, json } from './util/response.js';
5
- import { Cookies } from "./util/cookies.js";
6
- import { EventSourceConnection } from "./util/event-source.js";
7
- import { DynamicReference } from './util/dynamic.js';
8
- import { StyleClass } from './util/css.js';
9
- import { Endpoint } from './util/endpoint.js';
10
- export { createRequestHandler, RouteContext, GenericContext,
11
- // Request helpers
12
- Cookies, Endpoint, DynamicReference,
13
- // CSS Helper
14
- StyleClass,
15
- // SSE helper
16
- EventSourceConnection,
17
- // Response helpers
18
- redirect, refresh, revalidate, text, json, RenderMetaDescriptor, ApplyMetaDescriptorDefaults };
@@ -1,6 +0,0 @@
1
- import * as native from "../request/native.js";
2
- import * as http from "../request/http.js";
3
- export const createRequestHandler = {
4
- http: http.createRequestHandler,
5
- native: native.createRequestHandler
6
- };
package/bin/response.d.ts DELETED
@@ -1,9 +0,0 @@
1
- export declare function text(text: string, init?: ResponseInit): Response;
2
- export declare function json(data: unknown, init?: ResponseInit): Response;
3
- export declare function redirect(url: string, init?: ResponseInit & {
4
- client?: boolean;
5
- }): Response;
6
- export declare function revalidate(init?: ResponseInit): Response;
7
- export declare function refresh(init?: ResponseInit & {
8
- client?: boolean;
9
- }): Response;
package/bin/response.js DELETED
@@ -1,46 +0,0 @@
1
- export function text(text, init) {
2
- init ||= {};
3
- init.statusText ||= "ok";
4
- init.status = 200;
5
- const res = new Response(text, init);
6
- res.headers.set("Content-Type", "text/plain");
7
- res.headers.set("X-Caught", "true");
8
- return res;
9
- }
10
- export function json(data, init) {
11
- init ||= {};
12
- init.statusText ||= "ok";
13
- init.status = 200;
14
- const res = new Response(JSON.stringify(data), init);
15
- res.headers.set("Content-Type", "application/json");
16
- res.headers.set("X-Caught", "true");
17
- return res;
18
- }
19
- export function redirect(url, init) {
20
- init ||= {};
21
- init.statusText ||= "Temporary Redirect";
22
- init.status = 307;
23
- const res = new Response("", init);
24
- if (!init?.client)
25
- res.headers.set("Location", url);
26
- res.headers.set("HX-Location", url); // use hx-boost if applicable
27
- return res;
28
- }
29
- export function revalidate(init) {
30
- init ||= {};
31
- init.statusText ||= "ok";
32
- init.status = 200;
33
- const res = new Response("", init);
34
- res.headers.set("HX-Location", "");
35
- return res;
36
- }
37
- export function refresh(init) {
38
- init ||= {};
39
- init.statusText ||= "ok";
40
- init.status = 200;
41
- const res = new Response("", init);
42
- if (!init?.client)
43
- res.headers.set("Refresh", "0"); // fallback
44
- res.headers.set("HX-Refresh", "true");
45
- return res;
46
- }
package/bin/types.d.ts DELETED
@@ -1,10 +0,0 @@
1
- import { ParameterShaper } from "./util/parameters.js";
2
- import { RouteContext } from "./router.js";
3
- export type CatchFunction<T> = (args: T, err: unknown) => Promise<Response | JSX.Element>;
4
- export type RenderFunction<T> = (args: T) => Promise<Response | JSX.Element | null>;
5
- export type RouteModule<T extends ParameterShaper> = {
6
- parameters?: T;
7
- loader?: RenderFunction<RouteContext<T>>;
8
- action?: RenderFunction<RouteContext<T>>;
9
- error?: CatchFunction<RouteContext<T>>;
10
- };
package/bin/types.js DELETED
@@ -1 +0,0 @@
1
- export {};
@@ -1,16 +0,0 @@
1
- /**
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
- */
5
- export declare class EventSourceConnection {
6
- private controller;
7
- private stream;
8
- private timer;
9
- readonly createdAt: number;
10
- constructor(request: Request, keepAlive?: number);
11
- response(): Response;
12
- private keepAlive;
13
- send(type: string, data: string, timeStamp: number): boolean;
14
- isClosed(): boolean;
15
- close(unlink?: boolean): boolean;
16
- }
@@ -1,85 +0,0 @@
1
- /**
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
- */
5
- export class EventSourceConnection {
6
- controller;
7
- stream;
8
- timer;
9
- createdAt; // unix time
10
- constructor(request, keepAlive = 30_000) {
11
- this.createdAt = Date.now();
12
- this.controller = null;
13
- this.stream = new ReadableStream({
14
- start: (c) => { this.controller = c; },
15
- cancel: () => { this.close(); }
16
- });
17
- request.signal.addEventListener('abort', () => this.close());
18
- this.timer = setInterval(() => this.keepAlive(), keepAlive);
19
- register.push(this);
20
- }
21
- response() {
22
- const headers = new Headers();
23
- headers.set("Content-Type", "text/event-stream");
24
- headers.set("Transfer-Encoding", "chunked");
25
- headers.set("Connection", "keep-alive");
26
- headers.set("Keep-Alive", `timeout=120`);
27
- return new Response(this.stream, { headers });
28
- }
29
- keepAlive() {
30
- if (!this.controller)
31
- return;
32
- try {
33
- this.controller.enqueue("\n\n");
34
- }
35
- catch (e) {
36
- console.error(e);
37
- this.close(); // unbind on failure
38
- }
39
- }
40
- send(type, data, timeStamp) {
41
- if (!this.controller)
42
- return false;
43
- try {
44
- this.controller.enqueue(`event: ${type}\ndata: [${data},${timeStamp}]\n\n`);
45
- return true;
46
- }
47
- catch (e) {
48
- console.error(e);
49
- this.close(); // unbind on failure
50
- return false;
51
- }
52
- }
53
- isClosed() {
54
- return this.controller === null;
55
- }
56
- close(unlink = true) {
57
- clearInterval(this.timer);
58
- if (!this.controller)
59
- return false;
60
- if (unlink) {
61
- const i = register.indexOf(this);
62
- if (i !== -1)
63
- register.splice(i, 1);
64
- }
65
- try {
66
- this.controller?.close();
67
- }
68
- catch (e) {
69
- console.error(e);
70
- this.controller = null;
71
- return false;
72
- }
73
- this.controller = null;
74
- return true;
75
- }
76
- }
77
- // Auto close all SSE streams when shutdown requested
78
- // Without this graceful shutdowns will hang indefinitely
79
- const register = new Array();
80
- function CloseAll() {
81
- for (const connection of register)
82
- connection.close(false); // don't waste time unregistering
83
- }
84
- process.on('SIGTERM', CloseAll);
85
- process.on('SIGHUP', CloseAll);
@@ -1 +0,0 @@
1
- export declare function MergeHeaders(base: Headers, extension: Headers, override: boolean): void;
package/bin/util/index.js DELETED
@@ -1,7 +0,0 @@
1
- export function MergeHeaders(base, extension, override) {
2
- extension.forEach((val, key) => {
3
- if (!override && base.has(key))
4
- return;
5
- base.set(key, val);
6
- });
7
- }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes