@nifrajs/web 3.3.0 → 3.5.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 (77) hide show
  1. package/dist/build-plan.d.ts +17 -0
  2. package/dist/build-plan.d.ts.map +1 -1
  3. package/dist/build-plan.js +7 -1
  4. package/dist/build-plan.js.map +1 -1
  5. package/dist/build-vite.d.ts +14 -0
  6. package/dist/build-vite.d.ts.map +1 -1
  7. package/dist/build-vite.js +29 -4
  8. package/dist/build-vite.js.map +1 -1
  9. package/dist/build.d.ts +15 -0
  10. package/dist/build.d.ts.map +1 -1
  11. package/dist/build.js +57 -15
  12. package/dist/build.js.map +1 -1
  13. package/dist/client.d.ts +18 -0
  14. package/dist/client.d.ts.map +1 -1
  15. package/dist/client.js +115 -0
  16. package/dist/client.js.map +1 -1
  17. package/dist/css-contract.d.ts +21 -0
  18. package/dist/css-contract.d.ts.map +1 -0
  19. package/dist/css-contract.js +28 -0
  20. package/dist/css-contract.js.map +1 -0
  21. package/dist/index.d.ts +3 -1
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +2 -0
  24. package/dist/index.js.map +1 -1
  25. package/dist/internal/codegen.d.ts +3 -0
  26. package/dist/internal/codegen.d.ts.map +1 -1
  27. package/dist/internal/codegen.js +11 -6
  28. package/dist/internal/codegen.js.map +1 -1
  29. package/dist/internal/page-execution.d.ts +57 -0
  30. package/dist/internal/page-execution.d.ts.map +1 -0
  31. package/dist/internal/page-execution.js +555 -0
  32. package/dist/internal/page-execution.js.map +1 -0
  33. package/dist/internal/render-document.d.ts +12 -2
  34. package/dist/internal/render-document.d.ts.map +1 -1
  35. package/dist/internal/render-document.js +28 -5
  36. package/dist/internal/render-document.js.map +1 -1
  37. package/dist/internal/request-url.d.ts +3 -0
  38. package/dist/internal/request-url.d.ts.map +1 -0
  39. package/dist/internal/request-url.js +14 -0
  40. package/dist/internal/request-url.js.map +1 -0
  41. package/dist/internal/web-app.d.ts +20 -1
  42. package/dist/internal/web-app.d.ts.map +1 -1
  43. package/dist/internal/web-app.js +37 -734
  44. package/dist/internal/web-app.js.map +1 -1
  45. package/dist/isr.d.ts.map +1 -1
  46. package/dist/isr.js +39 -4
  47. package/dist/isr.js.map +1 -1
  48. package/dist/llms-txt.d.ts +3 -0
  49. package/dist/llms-txt.d.ts.map +1 -1
  50. package/dist/llms-txt.js +6 -3
  51. package/dist/llms-txt.js.map +1 -1
  52. package/dist/manifest.d.ts +1 -1
  53. package/dist/manifest.d.ts.map +1 -1
  54. package/dist/public-dir.d.ts.map +1 -1
  55. package/dist/public-dir.js +69 -44
  56. package/dist/public-dir.js.map +1 -1
  57. package/dist/trusted-html.d.ts +34 -0
  58. package/dist/trusted-html.d.ts.map +1 -0
  59. package/dist/trusted-html.js +35 -0
  60. package/dist/trusted-html.js.map +1 -0
  61. package/package.json +3 -3
  62. package/src/build-plan.ts +23 -1
  63. package/src/build-vite.ts +47 -4
  64. package/src/build.ts +69 -6
  65. package/src/client.ts +140 -0
  66. package/src/css-contract.ts +44 -0
  67. package/src/index.ts +15 -0
  68. package/src/internal/codegen.ts +20 -6
  69. package/src/internal/page-execution.ts +761 -0
  70. package/src/internal/render-document.ts +39 -6
  71. package/src/internal/request-url.ts +14 -0
  72. package/src/internal/web-app.ts +68 -835
  73. package/src/isr.ts +42 -3
  74. package/src/llms-txt.ts +16 -4
  75. package/src/manifest.ts +1 -1
  76. package/src/public-dir.ts +70 -45
  77. package/src/trusted-html.ts +44 -0
@@ -0,0 +1,44 @@
1
+ /**
2
+ * How framework-owned stylesheets are made active during the first document load.
3
+ *
4
+ * `blocking` is the backwards-compatible default. `deferred` is intended for an aggregate
5
+ * stylesheet: the document fetches it with `media="print"`, then the generated client promotes it
6
+ * to `media="all"` after the resource settles and before mounting the app.
7
+ */
8
+ export type CssLoadingMode = "blocking" | "deferred"
9
+
10
+ /** The default remains the ordinary render-blocking stylesheet link. */
11
+ export const DEFAULT_CSS_LOADING: CssLoadingMode = "blocking"
12
+
13
+ /** Runtime validation for the Vite CSS output switch. `undefined` means Vite's default (`true`). */
14
+ export function normalizeCssCodeSplit(value: unknown): boolean {
15
+ if (value === undefined) return true
16
+ if (typeof value === "boolean") return value
17
+ throw new TypeError(
18
+ `[nifra/web] cssCodeSplit must be a boolean when provided, received ${JSON.stringify(value)}`,
19
+ )
20
+ }
21
+
22
+ /** Runtime validation for JavaScript callers, generated manifests, and hand-authored integrations. */
23
+ export function normalizeCssLoading(value: unknown): CssLoadingMode {
24
+ if (value === "blocking" || value === "deferred") return value
25
+ throw new TypeError(
26
+ `[nifra/web] cssLoading must be "blocking" or "deferred", received ${JSON.stringify(value)}`,
27
+ )
28
+ }
29
+
30
+ /**
31
+ * Guard the only Vite combination that would claim to solve the stylesheet-insertion regression while
32
+ * leaving the cause in place. A deferred split build can still attach CSS when a prefetched lazy chunk
33
+ * evaluates, so it must fail closed instead of producing a misleadingly healthy deployment.
34
+ */
35
+ export function assertCssLoadingCompatible(
36
+ cssCodeSplit: boolean,
37
+ cssLoading: CssLoadingMode,
38
+ ): void {
39
+ if (cssLoading !== "deferred" || !cssCodeSplit) return
40
+ throw new Error(
41
+ '[nifra/web] cssLoading: "deferred" requires cssCodeSplit: false. Vite can otherwise inject ' +
42
+ "stylesheets when prefetched lazy chunks evaluate, reintroducing page-wide style/layout work.",
43
+ )
44
+ }
package/src/index.ts CHANGED
@@ -41,6 +41,12 @@ export {
41
41
  RenderAdapterConformanceError,
42
42
  type RenderAdapterConformanceFixture,
43
43
  } from "./conformance.ts"
44
+ export {
45
+ assertCssLoadingCompatible,
46
+ type CssLoadingMode,
47
+ DEFAULT_CSS_LOADING,
48
+ normalizeCssLoading,
49
+ } from "./css-contract.ts"
44
50
  // Deferred loader data (`defer()` + the `Deferred<T>` type) - consumed by the adapter's `<Await>`.
45
51
  export { type Deferred, defer } from "./deferred.ts"
46
52
  export {
@@ -114,6 +120,7 @@ export { SERVER_FN_MODULE, SERVER_ONLY_MODULE } from "./internal/server-boundary
114
120
  export {
115
121
  type CreateWebAppOptions,
116
122
  createWebApp,
123
+ type NonceResolver,
117
124
  } from "./internal/web-app.ts"
118
125
  // ISR (incremental static regeneration): a pluggable cache store + the `withISR` stale-while-revalidate
119
126
  // wrapper for rendered SSR responses.
@@ -258,3 +265,11 @@ export {
258
265
  // ctx + `renderPage`) and a client adapter mount call this with the same URL + schema, so the two sides
259
266
  // produce the identical value by construction. An adapter's `useSearch` binding reads its result.
260
267
  export { type SearchOf, searchOf, searchOfChain, serializeSearch } from "./search.ts"
268
+ export {
269
+ type HtmlSanitizer,
270
+ type SanitizedHtml,
271
+ sanitizedHtml,
272
+ sanitizeHtml,
273
+ type TrustedHtml,
274
+ trustHtml,
275
+ } from "./trusted-html.ts"
@@ -1,3 +1,4 @@
1
+ import { type CssLoadingMode, DEFAULT_CSS_LOADING, normalizeCssLoading } from "../css-contract.ts"
1
2
  import { MAP_DEFERRED_SOURCE } from "../deferred.ts"
2
3
  import type { LayoutEntry, Manifest } from "../manifest.ts"
3
4
  import {
@@ -85,7 +86,7 @@ export function generateClientEntry(
85
86
  // `/client`, never the root: the root's graph carries the server (renderPage, the static-file
86
87
  // server), and Vite's dev server evaluates what it is given instead of tree-shaking it.
87
88
  'import { createClientRouter, createMatcher, mergeHeads, resolveMeta } from "@nifrajs/web/client"',
88
- 'import { applyHead, installForms, installHistory, signalHydrated } from "@nifrajs/web/client"',
89
+ 'import { applyHead, installForms, installHistory, signalHydrated, waitForStyles } from "@nifrajs/web/client"',
89
90
  // Namespace import: `errorBoundary` is optional (an adapter may not export it). A namespace member
90
91
  // access yields `undefined` if absent - unlike a named import, which would be a link error.
91
92
  `import * as __adapter from ${JSON.stringify(clientModule)}`,
@@ -219,9 +220,9 @@ export function generateClientEntry(
219
220
  "is set, `#root` otherwise - and this document has neither. If you emit the document yourself, " +
220
221
  'keep `<div id="root">` around the SSR markup.',
221
222
  )})`,
222
- // Load the initial route's chunk, then hydrate the Router (chain is cached). The initial head
223
- // is server-rendered; subsequent navigations update it from the matched route's meta + data.
224
- "loadModule(initial.routeId).then(() => {",
223
+ // Wait for framework-owned deferred CSS before loading/mounting the initial route. The coordinator
224
+ // is a no-op for the default blocking links and always has a bounded fail-open terminal state.
225
+ "waitForStyles().then(() => loadModule(initial.routeId)).then(() => {",
225
226
  " mountRouter({ router, routes: chains, searchSchemas, container: root })",
226
227
  // Run the optional client loader only after the adapter has mounted the SSR tree. The initial
227
228
  // server data is already in `window.__NIFRA_DATA__`, so `serverLoader()` reuses it and cannot
@@ -240,7 +241,7 @@ export function generateClientEntry(
240
241
  " applyHead(mergeHeads((metas[s.routeId] ?? [undefined]).map((m) => resolveMeta(m, args))))",
241
242
  " }",
242
243
  " })",
243
- "})",
244
+ "}).catch((error) => console.error('[nifra/web] hydration failed:', error))",
244
245
  ].join("\n")}\n`
245
246
  }
246
247
 
@@ -305,6 +306,8 @@ export interface GenerateServerManifestOptions {
305
306
  /** Per-route stylesheet URLs (`buildClient`'s `BuildManifest.routeStyles`) - baked as
306
307
  * `export const routeStyles` so a page links only the CSS its route chain uses. */
307
308
  readonly routeStyles?: Readonly<Record<string, readonly string[]>> | undefined
309
+ /** How framework-owned stylesheet links should activate before the generated client mounts. */
310
+ readonly cssLoading?: CssLoadingMode
308
311
  /** Emit **lazy** per-route loaders (`() => import("./routes/x")`, a static specifier) instead of
309
312
  * eager `import * as`, so a bundler with code-splitting emits one chunk per route - loaded on the
310
313
  * first request to it, not all at boot (smaller cold-start parse). Default `false` (eager). Both
@@ -339,7 +342,15 @@ export function generateServerManifest(
339
342
  manifest: Manifest,
340
343
  options: GenerateServerManifestOptions,
341
344
  ): string {
342
- const { resolve, clientEntry, styles, routeStyles, lazy = false } = options
345
+ const {
346
+ resolve,
347
+ clientEntry,
348
+ styles,
349
+ routeStyles,
350
+ cssLoading: requestedCssLoading,
351
+ lazy = false,
352
+ } = options
353
+ const cssLoading = normalizeCssLoading(requestedCssLoading ?? DEFAULT_CSS_LOADING)
343
354
  // Every unique source file in the manifest (routes + layouts + error/status pages), sorted for stable output.
344
355
  const files = [
345
356
  ...new Set([
@@ -361,6 +372,7 @@ export function generateServerManifest(
361
372
  // Always emitted (default empty) so consumers can `import { styles, routeStyles }` unconditionally.
362
373
  const stylesLine = `export const styles = ${JSON.stringify(styles ?? [])}`
363
374
  const routeStylesLine = `export const routeStyles = ${JSON.stringify(routeStyles ?? {})}`
375
+ const cssLoadingLine = `export const cssLoading = ${JSON.stringify(cssLoading)}`
364
376
  if (lazy) {
365
377
  // Lazy: `() => import("./routes/x")` per route (static specifier → one chunk per route under a
366
378
  // code-splitting bundler, loaded on first request). The map keys are the route-relative paths
@@ -377,6 +389,7 @@ export function generateServerManifest(
377
389
  clientEntryLine,
378
390
  stylesLine,
379
391
  routeStylesLine,
392
+ cssLoadingLine,
380
393
  "export const manifest = buildManifest(Object.keys(loaders), (file) => () => loaders[file]())",
381
394
  ].join("\n")}\n`
382
395
  }
@@ -395,6 +408,7 @@ export function generateServerManifest(
395
408
  clientEntryLine,
396
409
  stylesLine,
397
410
  routeStylesLine,
411
+ cssLoadingLine,
398
412
  "export const manifest = buildManifest(Object.keys(modules), (file) => () => Promise.resolve(modules[file]))",
399
413
  ].join("\n")}\n`
400
414
  }