htmx-router 1.0.0-alpha.4 → 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 (97) hide show
  1. package/{bin/util/css.js → css.js} +1 -1
  2. package/dynamic.d.ts +5 -0
  3. package/{bin/util/dynamic.js → dynamic.js} +20 -7
  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/internal/compile/manifest.js +178 -0
  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 +15 -9
  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/{bin → internal}/request/native.js +12 -14
  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/request/native.js +46 -0
  48. package/response.d.ts +13 -0
  49. package/{bin/response.js → response.js} +25 -12
  50. package/{bin/router.d.ts → router.d.ts} +12 -10
  51. package/{bin/router.js → router.js} +62 -48
  52. package/shell.d.ts +120 -0
  53. package/shell.js +253 -0
  54. package/{bin/util → util}/parameters.d.ts +0 -3
  55. package/{bin/util → util}/parameters.js +0 -3
  56. package/{bin/util → util}/path-builder.js +2 -0
  57. package/util/route.d.ts +2 -0
  58. package/util/route.js +58 -0
  59. package/vite/bundle-splitter.d.ts +4 -0
  60. package/vite/bundle-splitter.js +26 -0
  61. package/vite/client-island.d.ts +4 -0
  62. package/vite/client-island.js +14 -0
  63. package/vite/code-splitting.d.ts +4 -0
  64. package/vite/code-splitting.js +14 -0
  65. package/vite/index.d.ts +3 -0
  66. package/vite/index.js +3 -0
  67. package/vite/router.d.ts +2 -0
  68. package/vite/router.js +29 -0
  69. package/bin/cli/config.d.ts +0 -10
  70. package/bin/cli/config.js +0 -4
  71. package/bin/cli/index.js +0 -72
  72. package/bin/client/entry.d.ts +0 -1
  73. package/bin/client/index.d.ts +0 -7
  74. package/bin/client/index.js +0 -126
  75. package/bin/client/watch.d.ts +0 -1
  76. package/bin/client/watch.js +0 -11
  77. package/bin/index.d.ts +0 -9
  78. package/bin/index.js +0 -8
  79. package/bin/request/index.js +0 -6
  80. package/bin/response.d.ts +0 -4
  81. package/bin/types.d.ts +0 -10
  82. package/bin/types.js +0 -1
  83. package/bin/util/dynamic.d.ts +0 -8
  84. package/bin/util/event-source.d.ts +0 -16
  85. package/bin/util/event-source.js +0 -85
  86. package/bin/util/index.d.ts +0 -1
  87. package/bin/util/index.js +0 -7
  88. package/bin/util/shell.d.ts +0 -32
  89. package/bin/util/shell.js +0 -8
  90. /package/{bin/util/cookies.d.ts → cookies.d.ts} +0 -0
  91. /package/{bin/util/cookies.js → cookies.js} +0 -0
  92. /package/{bin/util/css.d.ts → css.d.ts} +0 -0
  93. /package/{bin → internal}/cli/index.d.ts +0 -0
  94. /package/{bin/util → internal}/hash.d.ts +0 -0
  95. /package/{bin/util → internal}/hash.js +0 -0
  96. /package/{bin/client → internal}/mount.d.ts +0 -0
  97. /package/{bin/util → util}/path-builder.d.ts +0 -0
package/shell.d.ts ADDED
@@ -0,0 +1,120 @@
1
+ export type ShellOptions<D = {}> = D & MetaDescriptor;
2
+ export declare function ApplyMetaDescriptorDefaults(options: ShellOptions, defaults: Readonly<Partial<ShellOptions>>): void;
3
+ export type InferShellOptions<F> = F extends (jsx: any, options: infer U) => any ? U : never;
4
+ export type MetaDescriptor = {
5
+ title?: string;
6
+ description?: string;
7
+ meta?: Record<string, string>;
8
+ og?: OpenGraph<string>;
9
+ jsonLD?: LdJsonObject[];
10
+ };
11
+ export declare function RenderMetaDescriptor<T>(options: ShellOptions<T>): string;
12
+ export type LdJsonObject = {
13
+ [Key in string]?: LdJsonValue | undefined;
14
+ };
15
+ type LdJsonArray = LdJsonValue[] | readonly LdJsonValue[];
16
+ type LdJsonPrimitive = string | number | boolean | null;
17
+ type LdJsonValue = LdJsonPrimitive | LdJsonObject | LdJsonArray;
18
+ export type OpenGraphType = "website" | "article" | "book" | "profile" | "music.song" | "music.album" | "music.playlist" | "music.radio_station" | "video.movie" | "video.episode" | "video.tv_show" | "video.other" | string;
19
+ export type OpenGraph<T extends OpenGraphType = string> = {
20
+ type?: T;
21
+ title?: string;
22
+ description?: string;
23
+ determiner?: string;
24
+ url?: string;
25
+ secure_url?: string;
26
+ locale?: string | {
27
+ base: string;
28
+ alternative: string[];
29
+ };
30
+ image?: OpenGraphImage[];
31
+ video?: OpenGraphVideo[];
32
+ audio?: OpenGraphAudio[];
33
+ } & (T extends "music.song" ? OpenGraphSong : T extends "music.album" ? OpenGraphAlbum : T extends "music.playlist" ? OpenGraphPlaylist : T extends "music.radio_station" ? OpenGraphRadioStation : T extends "video.movie" ? OpenGraphMovie : T extends "video.episode" ? OpenGraphEpisode : T extends "video.tv_show" ? OpenGraphTvShow : T extends "video.other" ? OpenGraphVideoOther : T extends "article" ? OpenGraphArticle : T extends "book" ? OpenGraphBook : T extends "profile" ? OpenGraphProfile : {});
34
+ export type OpenGraphImage = {
35
+ url: string;
36
+ secure_url?: string;
37
+ type?: string;
38
+ width?: number;
39
+ height?: number;
40
+ alt?: string;
41
+ };
42
+ export type OpenGraphVideo = {
43
+ url: string;
44
+ type?: string;
45
+ secure_url?: string;
46
+ width?: number;
47
+ height?: number;
48
+ alt?: string;
49
+ };
50
+ export type OpenGraphAudio = {
51
+ url: string;
52
+ type?: string;
53
+ secure_url?: string;
54
+ };
55
+ type OpenGraphSong = {
56
+ duration?: number;
57
+ album?: Array<string | {
58
+ url: string;
59
+ disc?: number;
60
+ track?: number;
61
+ }>;
62
+ musician?: string[];
63
+ };
64
+ type OpenGraphAlbum = {
65
+ songs?: Array<string | {
66
+ url: string;
67
+ disc?: number;
68
+ track?: number;
69
+ }>;
70
+ musician?: string[];
71
+ release_date?: Date;
72
+ };
73
+ type OpenGraphPlaylist = {
74
+ songs?: Array<string | {
75
+ url: string;
76
+ disc?: number;
77
+ track?: number;
78
+ }>;
79
+ creator?: string[];
80
+ };
81
+ type OpenGraphRadioStation = {
82
+ creator?: string[];
83
+ };
84
+ type OpenGraphMovie = {
85
+ actors?: Array<string | {
86
+ url: string;
87
+ role: string;
88
+ }>;
89
+ directors?: string[];
90
+ writers?: string[];
91
+ duration?: number;
92
+ release_date?: Date;
93
+ tag: string[];
94
+ };
95
+ type OpenGraphEpisode = OpenGraphMovie & {
96
+ series?: string;
97
+ };
98
+ type OpenGraphTvShow = OpenGraphMovie;
99
+ type OpenGraphVideoOther = OpenGraphMovie;
100
+ type OpenGraphArticle = {
101
+ published_time?: Date;
102
+ modified_time?: Date;
103
+ expiration_time?: Date;
104
+ authors?: string[];
105
+ section?: string;
106
+ tag?: string;
107
+ };
108
+ type OpenGraphBook = {
109
+ authors?: string[];
110
+ isbn?: string;
111
+ release_date?: Date;
112
+ tag?: string;
113
+ };
114
+ type OpenGraphProfile = {
115
+ first_name?: string;
116
+ last_name?: string;
117
+ username?: string;
118
+ gender?: "male" | "female";
119
+ };
120
+ export {};
package/shell.js ADDED
@@ -0,0 +1,253 @@
1
+ import { ServerOnlyWarning } from "./internal/util.js";
2
+ ServerOnlyWarning("shell");
3
+ export function ApplyMetaDescriptorDefaults(options, defaults) {
4
+ if (defaults.title && !options.title)
5
+ options.title = defaults.title;
6
+ if (defaults.description && !options.description)
7
+ options.description = defaults.description;
8
+ if (defaults.meta && !options.meta)
9
+ options.meta = defaults.meta;
10
+ if (defaults.og && !options.og)
11
+ options.og = defaults.og;
12
+ if (defaults.jsonLD && !options.jsonLD)
13
+ options.jsonLD = defaults.jsonLD;
14
+ }
15
+ export function RenderMetaDescriptor(options) {
16
+ let out = "";
17
+ if (options.title)
18
+ out += `<title>${EscapeHTML(options.title)}</title>`;
19
+ if (options.description)
20
+ out += `<meta name="description" content="${EscapeHTML(options.description)}">\n`;
21
+ if (options.meta)
22
+ for (const key in options.meta) {
23
+ out += `<meta name="${EscapeHTML(key)}" content="${EscapeHTML(options.meta[key])}">\n`;
24
+ }
25
+ if (options.jsonLD)
26
+ for (const json of options.jsonLD) {
27
+ out += `<script type="application/ld+json">${JSON.stringify(json)}</script>\n`;
28
+ }
29
+ // Auto apply og:title + og:description if not present
30
+ if (options.title && !options.og?.title)
31
+ out += `<meta property="og:title" content="${EscapeHTML(options.title)}">\n`;
32
+ if (options.description && !options.og?.description)
33
+ out += `<meta property="og:description" content="${EscapeHTML(options.description)}">\n`;
34
+ // Apply open graphs
35
+ if (options.og)
36
+ out += RenderOpenGraph(options.og);
37
+ return out;
38
+ }
39
+ function RenderOpenGraph(og) {
40
+ // Manually encoding everything rather than using a loop to ensure they are in the correct order
41
+ // And to ensure extra values can't leak in creating unsafe og tags
42
+ const type = og.type || "website";
43
+ let out = RenderProperty("og:type", type);
44
+ if (og.title)
45
+ out += RenderProperty("og:title", og.title);
46
+ if (og.description)
47
+ out += RenderProperty("og:description", og.description);
48
+ if (og.determiner)
49
+ out += RenderProperty("og:determiner", og.determiner);
50
+ if (og.url)
51
+ out += RenderProperty("og:url", og.url);
52
+ if (og.secure_url)
53
+ out += RenderProperty("og:secure_url", og.secure_url);
54
+ if (og.locale) {
55
+ if (typeof og.locale === "string")
56
+ out += RenderProperty("og:locale", og.locale);
57
+ else {
58
+ out += RenderProperty("og:locale", og.locale.base);
59
+ for (const l of og.locale.alternative)
60
+ out += RenderProperty("og:locale:alternative", l);
61
+ }
62
+ }
63
+ if (og.image)
64
+ for (const img of og.image) {
65
+ out += RenderProperty("og:image", img.url);
66
+ if (img.secure_url)
67
+ out += RenderProperty("og:image:secure_url", img.secure_url);
68
+ if (img.type)
69
+ out += RenderProperty("og:image:type", img.type);
70
+ if (img.width)
71
+ out += RenderProperty("og:image:width", img.width.toString());
72
+ if (img.height)
73
+ out += RenderProperty("og:image:height", img.height.toString());
74
+ if (img.alt)
75
+ out += RenderProperty("og:image:alt", img.alt);
76
+ }
77
+ if (og.video)
78
+ for (const vid of og.video) {
79
+ out += RenderProperty("og:video", vid.url);
80
+ if (vid.secure_url)
81
+ out += RenderProperty("og:video:secure_url", vid.secure_url);
82
+ if (vid.type)
83
+ out += RenderProperty("og:video:type", vid.type);
84
+ if (vid.width)
85
+ out += RenderProperty("og:video:width", vid.width.toString());
86
+ if (vid.height)
87
+ out += RenderProperty("og:video:height", vid.height.toString());
88
+ if (vid.alt)
89
+ out += RenderProperty("og:video:alt", vid.alt);
90
+ }
91
+ if (og.audio)
92
+ for (const audio of og.audio) {
93
+ out += RenderProperty("og:audio", audio.url);
94
+ if (audio.secure_url)
95
+ out += RenderProperty("og:audio:secure_url", audio.secure_url);
96
+ if (audio.type)
97
+ out += RenderProperty("og:audio:type", audio.type);
98
+ }
99
+ return out + RenderOpenGraphExtras(og);
100
+ }
101
+ function RenderProperty(name, value) {
102
+ return `<meta property="${name}" content="${EscapeHTML(value)}">\n`;
103
+ }
104
+ function RenderOpenGraphExtras(og) {
105
+ let out = "";
106
+ if (og.type === "music.song") {
107
+ const g = og;
108
+ if (g.duration)
109
+ out += RenderProperty("og:music:duration", g.duration.toString());
110
+ if (g.album)
111
+ for (const album of g.album) {
112
+ if (typeof album === "string")
113
+ out += RenderProperty("og:music:album", album);
114
+ else {
115
+ out += RenderProperty("og:music:album", album.url);
116
+ if (album.disc)
117
+ out += RenderProperty("og:music:album:disc", album.disc.toString());
118
+ if (album.track)
119
+ out += RenderProperty("og:music:album:track", album.track.toString());
120
+ }
121
+ }
122
+ if (g.musician)
123
+ for (const profile of g.musician)
124
+ out += RenderProperty("og:music:musician", profile);
125
+ return out;
126
+ }
127
+ if (og.type === "music.album") {
128
+ const g = og;
129
+ if (g.songs)
130
+ for (const song of g.songs) {
131
+ if (typeof song === "string")
132
+ out += RenderProperty("og:music:song", song);
133
+ else {
134
+ out += RenderProperty("og:music:song", song.url);
135
+ if (song.disc)
136
+ out += RenderProperty("og:music:song:disc", song.disc.toString());
137
+ if (song.track)
138
+ out += RenderProperty("og:music:song:track", song.track.toString());
139
+ }
140
+ }
141
+ if (g.musician)
142
+ for (const profile of g.musician)
143
+ out += RenderProperty("og:music:musician", profile);
144
+ if (g.release_date)
145
+ out += RenderProperty("og:music:release_date", g.release_date.toISOString());
146
+ return out;
147
+ }
148
+ if (og.type === "music.playlist") {
149
+ const g = og;
150
+ if (g.songs)
151
+ for (const song of g.songs) {
152
+ if (typeof song === "string")
153
+ out += RenderProperty("og:music:song", song);
154
+ else {
155
+ out += RenderProperty("og:music:song", song.url);
156
+ if (song.disc)
157
+ out += RenderProperty("og:music:song:disc", song.disc.toString());
158
+ if (song.track)
159
+ out += RenderProperty("og:music:song:track", song.track.toString());
160
+ }
161
+ }
162
+ if (g.creator)
163
+ for (const profile of g.creator)
164
+ out += RenderProperty("og:music:creator", profile);
165
+ return out;
166
+ }
167
+ if (og.type === "music.radio_station") {
168
+ const g = og;
169
+ if (g.creator)
170
+ for (const profile of g.creator)
171
+ out += RenderProperty("og:music:creator", profile);
172
+ return out;
173
+ }
174
+ if (og.type === "video.movie" || og.type === "video.episode" || og.type === "video.tv_show" || og.type === "video.other") {
175
+ const g = og;
176
+ if (g.actors)
177
+ for (const actor of g.actors) {
178
+ if (typeof actor === "string")
179
+ out += RenderProperty("og:video:actor", actor);
180
+ else {
181
+ out += RenderProperty("og:video:actor", actor.url);
182
+ out += RenderProperty("og:video:actor:role", actor.role);
183
+ }
184
+ }
185
+ if (g.directors)
186
+ for (const profile of g.directors)
187
+ out += RenderProperty("og:video:director", profile);
188
+ if (g.writers)
189
+ for (const profile of g.writers)
190
+ out += RenderProperty("og:video:writer", profile);
191
+ if (g.duration)
192
+ out += RenderProperty("og:video:duration", g.duration.toString());
193
+ if (g.release_date)
194
+ out += RenderProperty("og:video:release_date", g.release_date.toISOString());
195
+ if (g.tag)
196
+ for (const tag of g.tag)
197
+ out += RenderProperty("og:video:tag", tag);
198
+ if (g.series)
199
+ out += RenderProperty("og:video:series", g.series);
200
+ }
201
+ if (og.type === "article") {
202
+ const g = og;
203
+ if (g.published_time)
204
+ out += RenderProperty("og:article:published_time", g.published_time.toISOString());
205
+ if (g.modified_time)
206
+ out += RenderProperty("og:article:modified_time", g.modified_time.toISOString());
207
+ if (g.expiration_time)
208
+ out += RenderProperty("og:article:expiration_time", g.expiration_time.toISOString());
209
+ if (g.authors)
210
+ for (const profile of g.authors)
211
+ out += RenderProperty("og:article:author", profile);
212
+ if (g.section)
213
+ out += RenderProperty("og:article:section", g.section);
214
+ if (g.tag)
215
+ for (const tag of g.tag)
216
+ out += RenderProperty("og:video:tag", tag);
217
+ }
218
+ if (og.type === "book") {
219
+ const g = og;
220
+ if (g.authors)
221
+ for (const profile of g.authors)
222
+ out += RenderProperty("og:article:author", profile);
223
+ if (g.isbn)
224
+ out += RenderProperty("og:book:isbn", g.isbn);
225
+ if (g.release_date)
226
+ out += RenderProperty("og:book:release_date", g.release_date.toISOString());
227
+ if (g.tag)
228
+ for (const tag of g.tag)
229
+ out += RenderProperty("og:video:tag", tag);
230
+ }
231
+ if (og.type === "profile") {
232
+ const g = og;
233
+ if (g.first_name)
234
+ out += RenderProperty("og:profile:first_name", g.first_name);
235
+ if (g.last_name)
236
+ out += RenderProperty("og:profile:last_name", g.last_name);
237
+ if (g.username)
238
+ out += RenderProperty("og:profile:username", g.username);
239
+ if (g.gender)
240
+ out += RenderProperty("og:profile:gender", g.gender);
241
+ }
242
+ return "";
243
+ }
244
+ const escapeTo = {
245
+ "&": "&amp;",
246
+ "<": "&lt;",
247
+ ">": "&gt;",
248
+ "\"": "&quot;",
249
+ "'": "&#39;",
250
+ };
251
+ function EscapeHTML(str) {
252
+ return str.replace(/[&<>"']/g, (match) => escapeTo[match] || match);
253
+ }
@@ -1,6 +1,3 @@
1
- /**
2
- * This is used by GenericContext to convert itself to a RouteContext<T>
3
- */
4
1
  export type Parameterized<T extends ParameterShaper> = {
5
2
  [K in keyof T]: ReturnType<T[K]>;
6
3
  };
@@ -1,6 +1,3 @@
1
- /**
2
- * This is used by GenericContext to convert itself to a RouteContext<T>
3
- */
4
1
  export function Parameterize(params, shape) {
5
2
  const out = {};
6
3
  for (const key in shape) {
@@ -1,3 +1,5 @@
1
+ import { ServerOnlyWarning } from "../internal/util.js";
2
+ ServerOnlyWarning("path-builder");
1
3
  import { relative } from "path";
2
4
  /*
3
5
  // This feature is disabled because vite doesn't compile import.meta.url to the original url when making the SSR build
@@ -0,0 +1,2 @@
1
+ import { ParameterShaper } from "./parameters.js";
2
+ export declare function RoutePath<T extends ParameterShaper>(): (params: { [K in keyof T]: string; }) => string;
package/util/route.js ADDED
@@ -0,0 +1,58 @@
1
+ import { ServerOnlyWarning } from "../internal/util.js";
2
+ ServerOnlyWarning("route-path");
3
+ export function RoutePath() {
4
+ const frags = new Array();
5
+ return (params) => {
6
+ const t = params;
7
+ if (typeof t === "string") {
8
+ Compile("/" + t, frags);
9
+ return "";
10
+ }
11
+ return Parse(frags, params);
12
+ };
13
+ }
14
+ const indexRoute = "/_index";
15
+ function Compile(url, into) {
16
+ let cursor = 0;
17
+ let i = 1;
18
+ for (; i < url.length; i++) {
19
+ if (url[i] !== "$")
20
+ continue;
21
+ if (url[i - 1] !== "/")
22
+ continue;
23
+ let e = url.indexOf("/", i + 1);
24
+ if (e === -1)
25
+ e = url.length;
26
+ into.push(url.slice(cursor, i));
27
+ into.push(url.slice(i, e));
28
+ cursor = e;
29
+ i = e - 1;
30
+ }
31
+ // remainder
32
+ if (cursor != i)
33
+ into.push(url.slice(cursor));
34
+ // remove _index from end if present
35
+ const lastI = into.length - 1;
36
+ const last = into[lastI];
37
+ if (last && last.endsWith(indexRoute)) {
38
+ if (last.length === indexRoute.length)
39
+ into.length--;
40
+ else
41
+ into[lastI] = last.slice(0, -indexRoute.length);
42
+ }
43
+ }
44
+ function Parse(fragments, params) {
45
+ let out = "";
46
+ for (const frag of fragments) {
47
+ if (!frag.startsWith("$")) {
48
+ out += frag;
49
+ continue;
50
+ }
51
+ const key = frag.slice(1);
52
+ const param = params[key];
53
+ if (!param)
54
+ throw new Error(`Missing ${key} parameter required for route`);
55
+ out += param;
56
+ }
57
+ return out;
58
+ }
@@ -0,0 +1,4 @@
1
+ import type { UserConfig } from "vite";
2
+ type Plugin = NonNullable<UserConfig["plugins"]>[number];
3
+ export declare function BundleSplitter(): Plugin;
4
+ export {};
@@ -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-router.json", "utf-8"));
4
- }
package/bin/cli/index.js DELETED
@@ -1,72 +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 { RegisterDynamic } from "htmx-router/bin/util/dynamic";
18
- import { GetClientEntryURL } from 'htmx-router/bin/client/entry';
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: (params: T, ctx: GenericContext) => Promise<JSX.Element>
37
- children?: JSX.Element
38
- }): JSX.Element {
39
- const path = RegisterDynamic(props.loader);
40
-
41
- const query = new URLSearchParams();
42
- for (const key in props.params) query.set(key, props.params[key]);
43
- const url = path + query.toString();
44
-
45
- return <div
46
- hx-get={url}
47
- hx-trigger="load"
48
- hx-swap="outerHTML transition:true"
49
- style={{ display: "contents" }}
50
- >{props.children ? props.children : ""}</div>
51
- }
52
-
53
- let headCache: JSX.Element | null = null;
54
- const isProduction = process.env.NODE_ENV === "production";
55
- const clientEntry = await GetClientEntryURL();
56
- export function Scripts() {
57
- if (headCache) return headCache;
58
-
59
- const res = <>
60
- <link href={GetSheetUrl()} rel="stylesheet"></link>
61
- { isProduction ? "" : <script type="module" src="/@vite/client"></script> }
62
- <script type="module" src={clientEntry}></script>
63
- <script src={GetMountUrl()}></script>
64
- </>;
65
-
66
- if (isProduction) headCache = res;
67
- return res;
68
- }`);
69
- if (config.client) {
70
- console.info("Building client islands");
71
- await GenerateClient(config.client, true);
72
- }
@@ -1 +0,0 @@
1
- export declare function GetClientEntryURL(): Promise<any>;