@nifrajs/web 2.1.0 → 2.3.0

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 (194) hide show
  1. package/README.md +3 -3
  2. package/dist/build-vite.d.ts +68 -0
  3. package/dist/build-vite.d.ts.map +1 -0
  4. package/dist/build-vite.js +433 -0
  5. package/dist/build-vite.js.map +1 -0
  6. package/dist/build.d.ts +174 -65
  7. package/dist/build.d.ts.map +1 -1
  8. package/dist/build.js +376 -135
  9. package/dist/build.js.map +1 -1
  10. package/dist/bun-dev-entry.d.ts +83 -0
  11. package/dist/bun-dev-entry.d.ts.map +1 -0
  12. package/dist/bun-dev-entry.js +136 -0
  13. package/dist/bun-dev-entry.js.map +1 -0
  14. package/dist/chunk-names.js +1 -1
  15. package/dist/client.d.ts +3 -9
  16. package/dist/client.d.ts.map +1 -1
  17. package/dist/client.js +209 -27
  18. package/dist/client.js.map +1 -1
  19. package/dist/conformance.d.ts +2 -2
  20. package/dist/conformance.js +3 -3
  21. package/dist/deferred.d.ts +11 -11
  22. package/dist/deferred.d.ts.map +1 -1
  23. package/dist/deferred.js +16 -16
  24. package/dist/dev-error.d.ts +2 -2
  25. package/dist/dev-error.js +3 -3
  26. package/dist/dev-port.d.ts +36 -0
  27. package/dist/dev-port.d.ts.map +1 -0
  28. package/dist/dev-port.js +48 -0
  29. package/dist/dev-port.js.map +1 -0
  30. package/dist/dev.d.ts +62 -5
  31. package/dist/dev.d.ts.map +1 -1
  32. package/dist/dev.js +419 -60
  33. package/dist/dev.js.map +1 -1
  34. package/dist/draft.d.ts +44 -7
  35. package/dist/draft.d.ts.map +1 -1
  36. package/dist/draft.js +92 -9
  37. package/dist/draft.js.map +1 -1
  38. package/dist/fn-state.d.ts +56 -0
  39. package/dist/fn-state.d.ts.map +1 -0
  40. package/dist/fn-state.js +77 -0
  41. package/dist/fn-state.js.map +1 -0
  42. package/dist/fn.d.ts +111 -0
  43. package/dist/fn.d.ts.map +1 -0
  44. package/dist/fn.js +125 -0
  45. package/dist/fn.js.map +1 -0
  46. package/dist/fonts-google.d.ts +9 -9
  47. package/dist/fonts-google.js +10 -10
  48. package/dist/fonts.d.ts +7 -7
  49. package/dist/fonts.js +1 -1
  50. package/dist/forms.d.ts +8 -8
  51. package/dist/fs.d.ts +15 -2
  52. package/dist/fs.d.ts.map +1 -1
  53. package/dist/fs.js +8 -2
  54. package/dist/fs.js.map +1 -1
  55. package/dist/index.d.ts +266 -74
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +869 -179
  58. package/dist/index.js.map +1 -1
  59. package/dist/internal/head-attributes.d.ts +15 -0
  60. package/dist/internal/head-attributes.d.ts.map +1 -0
  61. package/dist/internal/head-attributes.js +99 -0
  62. package/dist/internal/head-attributes.js.map +1 -0
  63. package/dist/internal/script-types.d.ts +14 -0
  64. package/dist/internal/script-types.d.ts.map +1 -0
  65. package/dist/internal/script-types.js +17 -0
  66. package/dist/internal/script-types.js.map +1 -0
  67. package/dist/internal/server-boundary.d.ts +60 -0
  68. package/dist/internal/server-boundary.d.ts.map +1 -0
  69. package/dist/internal/server-boundary.js +174 -0
  70. package/dist/internal/server-boundary.js.map +1 -0
  71. package/dist/internal/timing-safe-equal.d.ts +16 -0
  72. package/dist/internal/timing-safe-equal.d.ts.map +1 -0
  73. package/dist/internal/timing-safe-equal.js +26 -0
  74. package/dist/internal/timing-safe-equal.js.map +1 -0
  75. package/dist/internal/vite-import.d.ts +63 -0
  76. package/dist/internal/vite-import.d.ts.map +1 -0
  77. package/dist/internal/vite-import.js +94 -0
  78. package/dist/internal/vite-import.js.map +1 -0
  79. package/dist/islands.d.ts +5 -5
  80. package/dist/islands.js +5 -5
  81. package/dist/isr.d.ts +15 -15
  82. package/dist/isr.d.ts.map +1 -1
  83. package/dist/isr.js +8 -21
  84. package/dist/isr.js.map +1 -1
  85. package/dist/llms-txt.d.ts.map +1 -1
  86. package/dist/llms-txt.js +12 -6
  87. package/dist/llms-txt.js.map +1 -1
  88. package/dist/manifest.d.ts +121 -40
  89. package/dist/manifest.d.ts.map +1 -1
  90. package/dist/manifest.js +136 -26
  91. package/dist/manifest.js.map +1 -1
  92. package/dist/module-graph.d.ts +91 -0
  93. package/dist/module-graph.d.ts.map +1 -0
  94. package/dist/module-graph.js +92 -0
  95. package/dist/module-graph.js.map +1 -0
  96. package/dist/navigation.d.ts +120 -5
  97. package/dist/navigation.d.ts.map +1 -1
  98. package/dist/navigation.js +37 -5
  99. package/dist/navigation.js.map +1 -1
  100. package/dist/plugins/css-modules.d.ts +3 -3
  101. package/dist/plugins/css-modules.d.ts.map +1 -1
  102. package/dist/plugins/css-modules.js +0 -0
  103. package/dist/plugins/css-modules.js.map +1 -1
  104. package/dist/plugins/kit.d.ts +8 -8
  105. package/dist/plugins/kit.js +9 -9
  106. package/dist/plugins/scss.js +3 -3
  107. package/dist/plugins/vite-leak-guard.d.ts +45 -0
  108. package/dist/plugins/vite-leak-guard.d.ts.map +1 -0
  109. package/dist/plugins/vite-leak-guard.js +67 -0
  110. package/dist/plugins/vite-leak-guard.js.map +1 -0
  111. package/dist/plugins/vite-server-fn.d.ts +23 -0
  112. package/dist/plugins/vite-server-fn.d.ts.map +1 -0
  113. package/dist/plugins/vite-server-fn.js +51 -0
  114. package/dist/plugins/vite-server-fn.js.map +1 -0
  115. package/dist/plugins/vite-server-only.d.ts +49 -0
  116. package/dist/plugins/vite-server-only.d.ts.map +1 -0
  117. package/dist/plugins/vite-server-only.js +57 -0
  118. package/dist/plugins/vite-server-only.js.map +1 -0
  119. package/dist/prerender.d.ts +5 -5
  120. package/dist/prerender.js +7 -7
  121. package/dist/public-dir.d.ts +36 -0
  122. package/dist/public-dir.d.ts.map +1 -0
  123. package/dist/public-dir.js +89 -0
  124. package/dist/public-dir.js.map +1 -0
  125. package/dist/query.d.ts +10 -10
  126. package/dist/query.js +15 -15
  127. package/dist/route-manifest.d.ts +91 -0
  128. package/dist/route-manifest.d.ts.map +1 -0
  129. package/dist/route-manifest.js +128 -0
  130. package/dist/route-manifest.js.map +1 -0
  131. package/dist/router.d.ts +94 -22
  132. package/dist/router.d.ts.map +1 -1
  133. package/dist/router.js +170 -58
  134. package/dist/router.js.map +1 -1
  135. package/dist/search.d.ts +94 -0
  136. package/dist/search.d.ts.map +1 -0
  137. package/dist/search.js +232 -0
  138. package/dist/search.js.map +1 -0
  139. package/dist/server-only.d.ts +6 -6
  140. package/dist/server-only.js +7 -7
  141. package/dist/service-worker.d.ts +56 -0
  142. package/dist/service-worker.d.ts.map +1 -0
  143. package/dist/service-worker.js +127 -0
  144. package/dist/service-worker.js.map +1 -0
  145. package/dist/vite.d.ts +25 -17
  146. package/dist/vite.d.ts.map +1 -1
  147. package/dist/vite.js +75 -33
  148. package/dist/vite.js.map +1 -1
  149. package/package.json +48 -4
  150. package/server-manifest.d.ts +31 -0
  151. package/src/build-vite.ts +553 -0
  152. package/src/build.ts +563 -172
  153. package/src/bun-dev-entry.ts +184 -0
  154. package/src/chunk-names.ts +1 -1
  155. package/src/client.ts +221 -30
  156. package/src/conformance.ts +3 -3
  157. package/src/deferred.ts +17 -17
  158. package/src/dev-error.ts +3 -3
  159. package/src/dev-port.ts +63 -0
  160. package/src/dev.ts +468 -66
  161. package/src/draft.ts +124 -12
  162. package/src/fn-state.ts +107 -0
  163. package/src/fn.ts +199 -0
  164. package/src/fonts-google.ts +13 -13
  165. package/src/fonts.ts +7 -7
  166. package/src/forms.ts +8 -8
  167. package/src/fs.ts +24 -6
  168. package/src/index.ts +1174 -225
  169. package/src/internal/head-attributes.ts +109 -0
  170. package/src/internal/script-types.ts +18 -0
  171. package/src/internal/server-boundary.ts +187 -0
  172. package/src/internal/timing-safe-equal.ts +24 -0
  173. package/src/internal/vite-import.ts +92 -0
  174. package/src/islands.ts +7 -7
  175. package/src/isr.ts +18 -30
  176. package/src/llms-txt.ts +13 -5
  177. package/src/manifest.ts +264 -48
  178. package/src/module-graph.ts +146 -0
  179. package/src/navigation.ts +151 -5
  180. package/src/plugins/css-modules.ts +0 -0
  181. package/src/plugins/kit.ts +11 -11
  182. package/src/plugins/scss.ts +3 -3
  183. package/src/plugins/vite-leak-guard.ts +110 -0
  184. package/src/plugins/vite-server-fn.ts +67 -0
  185. package/src/plugins/vite-server-only.ts +75 -0
  186. package/src/prerender.ts +10 -10
  187. package/src/public-dir.ts +108 -0
  188. package/src/query.ts +22 -22
  189. package/src/route-manifest.ts +240 -0
  190. package/src/router.ts +288 -74
  191. package/src/search.ts +297 -0
  192. package/src/server-only.ts +7 -7
  193. package/src/service-worker.ts +165 -0
  194. package/src/vite.ts +103 -49
@@ -0,0 +1,240 @@
1
+ /**
2
+ * `@nifrajs/web/route-manifest` - what each route actually DOES, as one artifact.
3
+ *
4
+ * The facts are already in the route modules: `prerender`, `getStaticPaths`, `revalidate`, `hydrate`.
5
+ * What has never existed is anywhere to read them together. To answer "which pages are static?", "which
6
+ * ones revalidate, and how often?", "which ship no JS?", you open every route file and hold the answer in
7
+ * your head - and the answer changes on the target you deploy to, which is nowhere near the route file.
8
+ *
9
+ * That gap is not just inconvenient. The interesting cases are the ones where a route's declaration and
10
+ * its deploy target disagree, and there is currently nothing that can notice: an ISR route on a target
11
+ * with no revalidation story regenerates on every request; a `static` build containing a route that was
12
+ * never prerenderable ships a page that 404s. Both are silent, and both are decided at build time by
13
+ * information no single place holds.
14
+ *
15
+ * So this derives one record per route - render mode, hydration, cache policy - resolves it against the
16
+ * target, and reports what the target cannot honour. Pure and fs-free: it takes an already-built
17
+ * `Manifest` so it runs at build time, in `nifra routes`, and in a test with a hand-built manifest.
18
+ */
19
+ import type { Manifest, RouteModule } from "./manifest.ts"
20
+
21
+ /**
22
+ * How a route produces its HTML.
23
+ *
24
+ * - `static` - prerendered at build; served as a file, no server render at request time.
25
+ * - `isr` - server-rendered, then cached and revalidated on a timer (`export const revalidate`).
26
+ * - `ssr` - server-rendered per request. The default, and the only mode that needs a live server.
27
+ */
28
+ export type RenderMode = "static" | "isr" | "ssr"
29
+
30
+ /** What a route needs from its host in order to behave as declared. */
31
+ export type RouteCapability = "server" | "revalidation"
32
+
33
+ /** One route's resolved behaviour. */
34
+ export interface RouteManifestEntry {
35
+ /** The route's id (its key in the build manifest and in `routes`). */
36
+ readonly id: string
37
+ /** The matching pattern, e.g. `/blog/:slug`. */
38
+ readonly pattern: string
39
+ readonly mode: RenderMode
40
+ /** Whether the full-document client is shipped. `false` means the page loads no framework JS. */
41
+ readonly hydrate: boolean
42
+ /** Revalidation window in seconds. Present only for `isr`. */
43
+ readonly revalidate?: number
44
+ /**
45
+ * Concrete paths prerendered at build. Present for `static` routes: a single path for a static route,
46
+ * one per `getStaticPaths` entry for a dynamic one. Absent means "no path was enumerated", which for a
47
+ * dynamic route is exactly the condition that makes it unprerenderable.
48
+ */
49
+ readonly prerenderedPaths?: readonly string[]
50
+ /** What this route needs from the host to behave as declared. */
51
+ readonly requires: readonly RouteCapability[]
52
+ }
53
+
54
+ /** A route whose declaration the chosen target cannot honour, and what actually happens if it ships. */
55
+ export interface RouteManifestConflict {
56
+ readonly id: string
57
+ readonly pattern: string
58
+ readonly capability: RouteCapability
59
+ /** What goes wrong on this target - the consequence, not the rule that was broken. */
60
+ readonly consequence: string
61
+ }
62
+
63
+ /** The whole artifact: every route's behaviour, plus anything the target cannot honour. */
64
+ export interface RouteManifest {
65
+ /** The deploy target this was resolved against, when one was given. */
66
+ readonly target?: string
67
+ readonly routes: readonly RouteManifestEntry[]
68
+ readonly conflicts: readonly RouteManifestConflict[]
69
+ /** Route counts by mode - the summary a report leads with. */
70
+ readonly totals: Readonly<Record<RenderMode, number>>
71
+ }
72
+
73
+ /** A route is dynamic when its pattern carries a param or wildcard segment. */
74
+ const isDynamic = (pattern: string): boolean => /[:*]/.test(pattern)
75
+
76
+ /**
77
+ * Derive one route's behaviour from its module exports.
78
+ *
79
+ * `prerender`/`getStaticPaths` win over `revalidate`: a page rendered at build time is not revalidated at
80
+ * runtime, so if a route declares both, the build-time answer is the one that describes what ships. That
81
+ * combination is worth surfacing rather than silently resolving, which is what `conflicts` is for once a
82
+ * target is known.
83
+ */
84
+ export function deriveRouteEntry(
85
+ id: string,
86
+ pattern: string,
87
+ module: Pick<RouteModule, "prerender" | "getStaticPaths" | "revalidate" | "hydrate">,
88
+ prerenderedPaths?: readonly string[],
89
+ ): RouteManifestEntry {
90
+ const wantsPrerender = module.prerender === true || module.getStaticPaths !== undefined
91
+ // A dynamic route is only really static if concrete paths were enumerated for it. `getStaticPaths`
92
+ // declares the intent; the paths are the evidence, and without them there is nothing prerendered to
93
+ // serve. Treating intent as sufficient is how a "static" build ships a page that 404s.
94
+ const enumerated = prerenderedPaths !== undefined && prerenderedPaths.length > 0
95
+ const isStatic = wantsPrerender && (!isDynamic(pattern) || enumerated)
96
+ const mode: RenderMode = isStatic ? "static" : module.revalidate !== undefined ? "isr" : "ssr"
97
+
98
+ const requires: RouteCapability[] = []
99
+ if (mode !== "static") requires.push("server")
100
+ if (mode === "isr") requires.push("revalidation")
101
+
102
+ const entry: {
103
+ id: string
104
+ pattern: string
105
+ mode: RenderMode
106
+ hydrate: boolean
107
+ revalidate?: number
108
+ prerenderedPaths?: readonly string[]
109
+ requires: readonly RouteCapability[]
110
+ } = { id, pattern, mode, hydrate: module.hydrate !== false, requires }
111
+ if (mode === "isr" && module.revalidate !== undefined) entry.revalidate = module.revalidate
112
+ if (prerenderedPaths !== undefined) entry.prerenderedPaths = prerenderedPaths
113
+ return entry as RouteManifestEntry
114
+ }
115
+
116
+ /**
117
+ * What each deploy target can actually do.
118
+ *
119
+ * `static` is the one that matters: it has no server at all, so every non-prerendered route is a page
120
+ * that will 404 in production while working perfectly in dev. The rest all run a server; they differ in
121
+ * how revalidation is wired, not in whether it exists, so this deliberately does not invent finer
122
+ * distinctions it cannot verify.
123
+ */
124
+ const TARGET_CAPABILITIES: Readonly<Record<string, readonly RouteCapability[]>> = {
125
+ bun: ["server"],
126
+ node: ["server"],
127
+ deno: ["server"],
128
+ "cf-pages": ["server"],
129
+ vercel: ["server"],
130
+ static: [],
131
+ }
132
+
133
+ const CONSEQUENCE: Readonly<Record<RouteCapability, string>> = {
134
+ server:
135
+ "this target ships no server, so the route is never rendered - the URL 404s in production while working in dev. Add `export const prerender = true` (or `getStaticPaths` for a dynamic route), or build for a target that runs a server.",
136
+ revalidation:
137
+ "this target cannot revalidate a cached page, so `revalidate` has no effect and the route renders on every request.",
138
+ }
139
+
140
+ /**
141
+ * Build the route manifest for a discovered app, optionally resolved against a deploy target.
142
+ *
143
+ * `prerendered` maps a route id to the concrete paths the build actually emitted for it - pass what
144
+ * `prerenderRoutes` produced. Without it, dynamic routes are reported by their declaration alone, which
145
+ * is the right answer before a build has run and the wrong one after.
146
+ */
147
+ export async function buildRouteManifest(
148
+ manifest: Manifest,
149
+ options: {
150
+ readonly target?: string
151
+ readonly prerendered?: Readonly<Record<string, readonly string[]>>
152
+ /** Runtime capabilities supplied outside generated entries (for example an explicit `withISR`). */
153
+ readonly capabilities?: readonly RouteCapability[]
154
+ } = {},
155
+ ): Promise<RouteManifest> {
156
+ const entries: RouteManifestEntry[] = []
157
+ for (const route of manifest.routes) {
158
+ // Loading the module is what makes this authoritative rather than a guess from the filename: the
159
+ // render mode lives in the module's exports and nowhere else.
160
+ const module = (await route.load()) as RouteModule
161
+ entries.push(deriveRouteEntry(route.id, route.pattern, module, options.prerendered?.[route.id]))
162
+ }
163
+ entries.sort((a, b) => a.pattern.localeCompare(b.pattern))
164
+
165
+ const conflicts: RouteManifestConflict[] = []
166
+ const capabilities =
167
+ options.capabilities ??
168
+ (options.target === undefined ? undefined : TARGET_CAPABILITIES[options.target])
169
+ if (capabilities !== undefined) {
170
+ for (const entry of entries) {
171
+ for (const capability of entry.requires) {
172
+ if (capabilities.includes(capability)) continue
173
+ conflicts.push({
174
+ id: entry.id,
175
+ pattern: entry.pattern,
176
+ capability,
177
+ consequence: CONSEQUENCE[capability],
178
+ })
179
+ }
180
+ }
181
+ }
182
+
183
+ const totals: Record<RenderMode, number> = { static: 0, isr: 0, ssr: 0 }
184
+ for (const entry of entries) totals[entry.mode] += 1
185
+
186
+ const result: {
187
+ target?: string
188
+ routes: RouteManifestEntry[]
189
+ conflicts: RouteManifestConflict[]
190
+ totals: Record<RenderMode, number>
191
+ } = { routes: entries, conflicts, totals }
192
+ if (options.target !== undefined) result.target = options.target
193
+ return result as RouteManifest
194
+ }
195
+
196
+ /** Render the manifest as a readable report - the `nifra routes --modes` output. */
197
+ export function renderRouteManifest(manifest: RouteManifest): string {
198
+ const lines: string[] = []
199
+ lines.push(
200
+ manifest.target === undefined
201
+ ? "nifra route manifest"
202
+ : `nifra route manifest - target: ${manifest.target}`,
203
+ )
204
+ lines.push("")
205
+ if (manifest.routes.length === 0) {
206
+ lines.push("no routes found.")
207
+ return lines.join("\n")
208
+ }
209
+
210
+ const width = Math.max(10, ...manifest.routes.map((r) => r.pattern.length)) + 2
211
+ lines.push(`${"route".padEnd(width)}${"mode".padEnd(9)}${"hydrate".padEnd(9)}cache`)
212
+ lines.push("-".repeat(width + 18 + 24))
213
+ for (const route of manifest.routes) {
214
+ const cache =
215
+ route.mode === "isr"
216
+ ? `revalidate ${route.revalidate}s`
217
+ : route.mode === "static"
218
+ ? `prerendered${route.prerenderedPaths ? ` (${route.prerenderedPaths.length})` : ""}`
219
+ : "per request"
220
+ lines.push(
221
+ route.pattern.padEnd(width) +
222
+ route.mode.padEnd(9) +
223
+ (route.hydrate ? "yes" : "no").padEnd(9) +
224
+ cache,
225
+ )
226
+ }
227
+ lines.push("")
228
+ lines.push(
229
+ `${manifest.totals.static} static · ${manifest.totals.isr} isr · ${manifest.totals.ssr} ssr`,
230
+ )
231
+
232
+ if (manifest.conflicts.length > 0) {
233
+ lines.push("")
234
+ lines.push(`✗ ${manifest.conflicts.length} route(s) the target cannot honour:`)
235
+ for (const conflict of manifest.conflicts) {
236
+ lines.push(` ✗ ${conflict.pattern} - ${conflict.consequence}`)
237
+ }
238
+ }
239
+ return lines.join("\n")
240
+ }