@nifrajs/web 2.2.0 → 2.4.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 (179) hide show
  1. package/README.md +3 -3
  2. package/dist/build-vite.d.ts +4 -4
  3. package/dist/build-vite.d.ts.map +1 -1
  4. package/dist/build-vite.js +20 -74
  5. package/dist/build-vite.js.map +1 -1
  6. package/dist/build.d.ts +64 -42
  7. package/dist/build.d.ts.map +1 -1
  8. package/dist/build.js +100 -78
  9. package/dist/build.js.map +1 -1
  10. package/dist/bun-dev-entry.d.ts +1 -1
  11. package/dist/bun-dev-entry.js +1 -1
  12. package/dist/chunk-names.js +1 -1
  13. package/dist/client.d.ts +3 -9
  14. package/dist/client.d.ts.map +1 -1
  15. package/dist/client.js +201 -29
  16. package/dist/client.js.map +1 -1
  17. package/dist/conformance.d.ts +2 -2
  18. package/dist/conformance.js +3 -3
  19. package/dist/deferred.d.ts +11 -11
  20. package/dist/deferred.d.ts.map +1 -1
  21. package/dist/deferred.js +16 -16
  22. package/dist/dev-diagnostics.d.ts +20 -0
  23. package/dist/dev-diagnostics.d.ts.map +1 -0
  24. package/dist/dev-diagnostics.js +34 -0
  25. package/dist/dev-diagnostics.js.map +1 -0
  26. package/dist/dev-error.d.ts +10 -13
  27. package/dist/dev-error.d.ts.map +1 -1
  28. package/dist/dev-error.js +51 -37
  29. package/dist/dev-error.js.map +1 -1
  30. package/dist/dev-port.d.ts +3 -3
  31. package/dist/dev-port.d.ts.map +1 -1
  32. package/dist/dev-port.js +10 -4
  33. package/dist/dev-port.js.map +1 -1
  34. package/dist/dev.d.ts +4 -4
  35. package/dist/dev.d.ts.map +1 -1
  36. package/dist/dev.js +28 -12
  37. package/dist/dev.js.map +1 -1
  38. package/dist/diagnostic.d.ts +93 -0
  39. package/dist/diagnostic.d.ts.map +1 -0
  40. package/dist/diagnostic.js +205 -0
  41. package/dist/diagnostic.js.map +1 -0
  42. package/dist/draft.d.ts +12 -12
  43. package/dist/draft.js +11 -11
  44. package/dist/fn-state.d.ts +56 -0
  45. package/dist/fn-state.d.ts.map +1 -0
  46. package/dist/fn-state.js +77 -0
  47. package/dist/fn-state.js.map +1 -0
  48. package/dist/fn.d.ts +111 -0
  49. package/dist/fn.d.ts.map +1 -0
  50. package/dist/fn.js +125 -0
  51. package/dist/fn.js.map +1 -0
  52. package/dist/fonts-google.d.ts +9 -9
  53. package/dist/fonts-google.js +10 -10
  54. package/dist/fonts.d.ts +7 -7
  55. package/dist/fonts.js +1 -1
  56. package/dist/forms.d.ts +8 -8
  57. package/dist/fs.d.ts +1 -1
  58. package/dist/fs.js +1 -1
  59. package/dist/index.d.ts +151 -70
  60. package/dist/index.d.ts.map +1 -1
  61. package/dist/index.js +317 -139
  62. package/dist/index.js.map +1 -1
  63. package/dist/internal/head-attributes.d.ts +15 -0
  64. package/dist/internal/head-attributes.d.ts.map +1 -0
  65. package/dist/internal/head-attributes.js +99 -0
  66. package/dist/internal/head-attributes.js.map +1 -0
  67. package/dist/internal/script-types.d.ts +14 -0
  68. package/dist/internal/script-types.d.ts.map +1 -0
  69. package/dist/internal/script-types.js +17 -0
  70. package/dist/internal/script-types.js.map +1 -0
  71. package/dist/internal/server-boundary.d.ts +60 -0
  72. package/dist/internal/server-boundary.d.ts.map +1 -0
  73. package/dist/internal/server-boundary.js +174 -0
  74. package/dist/internal/server-boundary.js.map +1 -0
  75. package/dist/internal/timing-safe-equal.d.ts +1 -1
  76. package/dist/internal/timing-safe-equal.js +1 -1
  77. package/dist/internal/vite-import.d.ts +63 -0
  78. package/dist/internal/vite-import.d.ts.map +1 -0
  79. package/dist/internal/vite-import.js +94 -0
  80. package/dist/internal/vite-import.js.map +1 -0
  81. package/dist/islands.d.ts +5 -5
  82. package/dist/islands.js +5 -5
  83. package/dist/isr.d.ts +15 -15
  84. package/dist/isr.js +7 -7
  85. package/dist/manifest.d.ts +73 -35
  86. package/dist/manifest.d.ts.map +1 -1
  87. package/dist/manifest.js +10 -16
  88. package/dist/manifest.js.map +1 -1
  89. package/dist/module-graph.d.ts +5 -5
  90. package/dist/module-graph.js +4 -4
  91. package/dist/navigation.d.ts +120 -5
  92. package/dist/navigation.d.ts.map +1 -1
  93. package/dist/navigation.js +37 -5
  94. package/dist/navigation.js.map +1 -1
  95. package/dist/plugins/css-modules.d.ts +3 -3
  96. package/dist/plugins/css-modules.js +0 -0
  97. package/dist/plugins/kit.d.ts +8 -8
  98. package/dist/plugins/kit.js +9 -9
  99. package/dist/plugins/scss.js +3 -3
  100. package/dist/plugins/vite-leak-guard.d.ts +1 -1
  101. package/dist/plugins/vite-leak-guard.js +1 -1
  102. package/dist/plugins/vite-server-fn.d.ts +23 -0
  103. package/dist/plugins/vite-server-fn.d.ts.map +1 -0
  104. package/dist/plugins/vite-server-fn.js +51 -0
  105. package/dist/plugins/vite-server-fn.js.map +1 -0
  106. package/dist/plugins/vite-server-only.d.ts +49 -0
  107. package/dist/plugins/vite-server-only.d.ts.map +1 -0
  108. package/dist/plugins/vite-server-only.js +57 -0
  109. package/dist/plugins/vite-server-only.js.map +1 -0
  110. package/dist/prerender.d.ts +5 -5
  111. package/dist/prerender.js +7 -7
  112. package/dist/query.d.ts +10 -10
  113. package/dist/query.js +15 -15
  114. package/dist/route-manifest.d.ts +1 -1
  115. package/dist/route-manifest.js +2 -2
  116. package/dist/router.d.ts +30 -21
  117. package/dist/router.d.ts.map +1 -1
  118. package/dist/router.js +62 -32
  119. package/dist/router.js.map +1 -1
  120. package/dist/search.d.ts +94 -0
  121. package/dist/search.d.ts.map +1 -0
  122. package/dist/search.js +232 -0
  123. package/dist/search.js.map +1 -0
  124. package/dist/server-only.d.ts +6 -6
  125. package/dist/server-only.js +7 -7
  126. package/dist/service-worker.d.ts +56 -0
  127. package/dist/service-worker.d.ts.map +1 -0
  128. package/dist/service-worker.js +127 -0
  129. package/dist/service-worker.js.map +1 -0
  130. package/dist/vite.d.ts +16 -14
  131. package/dist/vite.d.ts.map +1 -1
  132. package/dist/vite.js +51 -24
  133. package/dist/vite.js.map +1 -1
  134. package/package.json +38 -4
  135. package/server-manifest.d.ts +31 -0
  136. package/src/build-vite.ts +24 -77
  137. package/src/build.ts +137 -105
  138. package/src/bun-dev-entry.ts +1 -1
  139. package/src/chunk-names.ts +1 -1
  140. package/src/client.ts +215 -32
  141. package/src/conformance.ts +3 -3
  142. package/src/deferred.ts +17 -17
  143. package/src/dev-diagnostics.ts +47 -0
  144. package/src/dev-error.ts +64 -33
  145. package/src/dev-port.ts +17 -5
  146. package/src/dev.ts +36 -20
  147. package/src/diagnostic.ts +284 -0
  148. package/src/draft.ts +15 -15
  149. package/src/fn-state.ts +107 -0
  150. package/src/fn.ts +199 -0
  151. package/src/fonts-google.ts +13 -13
  152. package/src/fonts.ts +7 -7
  153. package/src/forms.ts +8 -8
  154. package/src/fs.ts +2 -2
  155. package/src/index.ts +460 -181
  156. package/src/internal/head-attributes.ts +109 -0
  157. package/src/internal/script-types.ts +18 -0
  158. package/src/internal/server-boundary.ts +187 -0
  159. package/src/internal/timing-safe-equal.ts +1 -1
  160. package/src/internal/vite-import.ts +92 -0
  161. package/src/islands.ts +7 -7
  162. package/src/isr.ts +17 -17
  163. package/src/manifest.ts +83 -42
  164. package/src/module-graph.ts +6 -6
  165. package/src/navigation.ts +151 -5
  166. package/src/plugins/css-modules.ts +0 -0
  167. package/src/plugins/kit.ts +11 -11
  168. package/src/plugins/scss.ts +3 -3
  169. package/src/plugins/vite-leak-guard.ts +2 -2
  170. package/src/plugins/vite-server-fn.ts +67 -0
  171. package/src/plugins/vite-server-only.ts +75 -0
  172. package/src/prerender.ts +10 -10
  173. package/src/query.ts +22 -22
  174. package/src/route-manifest.ts +3 -3
  175. package/src/router.ts +98 -45
  176. package/src/search.ts +297 -0
  177. package/src/server-only.ts +7 -7
  178. package/src/service-worker.ts +165 -0
  179. package/src/vite.ts +61 -32
@@ -1,31 +1,146 @@
1
1
  /**
2
- * `@nifrajs/web` navigation bridge — a tiny, DOM-free seam that lets an adapter's `useNavigate`
3
- * (a route component, which must import only from the agnostic `@nifrajs/web` main entry — never the
2
+ * `@nifrajs/web` navigation bridge - a tiny, DOM-free seam that lets an adapter's `useNavigate`
3
+ * (a route component, which must import only from the agnostic `@nifrajs/web` main entry - never the
4
4
  * DOM-only `/client`) reach the browser's history-aware navigate, WITHOUT the route component pulling
5
5
  * `@nifrajs/web/client` (and its `document`/`history` access) into a bundle that also renders on the
6
6
  * server.
7
7
  *
8
8
  * The browser layer (`installHistory`, in `./client.ts`) populates this on setup and clears it on
9
- * teardown; a framework binding reads it via {@link getBrowserNavigate}. Module-level singleton — the
9
+ * teardown; a framework binding reads it via {@link getBrowserNavigate}. Module-level singleton - the
10
10
  * browser mounts exactly one app per page (the same convention the adapters' `setMountedRouter` uses).
11
11
  * On the server (and before hydration) the getter returns `undefined`, so a binding degrades to the
12
- * native `<a href>` full-page navigation — progressive enhancement, no throw.
12
+ * native `<a href>` full-page navigation - progressive enhancement, no throw.
13
13
  */
14
14
  /** Options for a programmatic navigation. */
15
15
  export interface NavigateOptions {
16
16
  /** Replace the current history entry instead of pushing a new one (like `history.replaceState`). */
17
17
  readonly replace?: boolean;
18
18
  }
19
+ /**
20
+ * The augmentable route -> search-type map for typed cross-route navigation. Empty by default (so an
21
+ * object-form navigate to any path is allowed with a loose `search`). A build step (`nifra sync-routes`)
22
+ * OR the app declares one entry per route path against its `searchSchema` output:
23
+ *
24
+ * ```ts
25
+ * declare module "@nifrajs/web" {
26
+ * interface RouteSearch {
27
+ * "/reports": { page: number; q: string }
28
+ * }
29
+ * }
30
+ * ```
31
+ *
32
+ * With that, `navigate({ to: "/reports", search: { page: 2 } })` type-checks `search` against
33
+ * `/reports`'s schema, and a wrong shape is a compile error. See {@link NavigateTarget}.
34
+ */
35
+ export interface RouteSearch {
36
+ }
37
+ /**
38
+ * The `search` type for a navigate to `To`: the route's schema output when `To` is a mapped
39
+ * {@link RouteSearch} key, otherwise the loose `Record<string, unknown>` (so a navigate to any path is
40
+ * always allowed). Keyed on `To` rather than a union, so a mapped route can't fall back to the loose form
41
+ * with a wrong shape - `navigate({ to: "/reports", search: { page: "x" } })` is a compile error.
42
+ */
43
+ export type NavigateSearchOf<To extends string> = To extends keyof RouteSearch ? RouteSearch[To] : Record<string, unknown>;
44
+ /** The runtime shape of an object-form navigate target (loose - the typed narrowing lives in
45
+ * {@link NavigateFunction}'s generic call signature). `to` is a bare pathname; `search` is serialized
46
+ * onto it; `replace` folds into the options. */
47
+ export interface NavigateTargetInput {
48
+ readonly to: string;
49
+ readonly search?: Record<string, unknown>;
50
+ readonly replace?: boolean;
51
+ }
52
+ /**
53
+ * A programmatic navigate, shared by every adapter's `useNavigate`. Three forms: a string path (push, or
54
+ * replace via `{ replace: true }`), a history delta (`-1`/`1`), or an object target `{ to, search, replace }`
55
+ * whose `search` is typed against `to`'s route schema via {@link NavigateSearchOf} (a wrong shape for a
56
+ * mapped route is a compile error; an unmapped path takes a loose `search`). The object form's `search` is
57
+ * serialized onto `to`. A no-op on the server / before hydration.
58
+ */
59
+ export interface NavigateFunction {
60
+ (to: string | number, options?: NavigateOptions): void;
61
+ <To extends string>(target: {
62
+ readonly to: To;
63
+ readonly search?: NavigateSearchOf<To>;
64
+ readonly replace?: boolean;
65
+ }): void;
66
+ }
67
+ /**
68
+ * Normalize a navigate argument to the bridge's `(to, options)`: a string path or history-delta passes
69
+ * through; an object target has its `search` serialized onto `to` and its `replace` folded into the
70
+ * options. The one place the object form becomes a URL, so every adapter's `navigate` resolves it
71
+ * identically.
72
+ */
73
+ export declare function resolveNavigate(to: string | number | NavigateTargetInput, options?: NavigateOptions): {
74
+ readonly to: string | number;
75
+ readonly options: NavigateOptions | undefined;
76
+ };
19
77
  /**
20
78
  * A history-aware navigate. A **string** `to` is a same-origin path (`/users/7?tab=a`) navigated to
21
79
  * (push, or replace with `{ replace: true }`); a **number** is a history delta (`-1` back, `1`
22
80
  * forward), matching the browser's `history.go`. Registered by `installHistory`.
23
81
  */
24
82
  export type BrowserNavigate = (to: string | number, options?: NavigateOptions) => void;
25
- /** Register (or clear, with `undefined`) the browser navigate — called by `installHistory`. Not for
83
+ /** Register (or clear, with `undefined`) the browser navigate - called by `installHistory`. Not for
26
84
  * app use. */
27
85
  export declare function setBrowserNavigate(navigate: BrowserNavigate | undefined): void;
28
86
  /** The active browser navigate, or `undefined` on the server / before `installHistory` has run. A
29
87
  * binding calls it when present and falls back to native navigation otherwise. */
30
88
  export declare function getBrowserNavigate(): BrowserNavigate | undefined;
89
+ /** A parsed navigation target the {@link BlockerFunction} decides on. `pathname`/`search`/`hash` match
90
+ * the DOM `Location` shape (search keeps its `?`, hash its `#`). */
91
+ export interface BlockerLocation {
92
+ readonly pathname: string;
93
+ readonly search: string;
94
+ readonly hash: string;
95
+ }
96
+ /**
97
+ * Decide whether a navigation should be halted. Receives where the app is (`currentLocation`) and where
98
+ * it's heading (`nextLocation`), so a guard can allow same-section moves and block only real exits. A
99
+ * boolean form (`useBlocker(isDirty)`) is sugar for `() => isDirty`. Runs synchronously at navigation
100
+ * time - return the current answer; the async "are you sure?" happens afterward via {@link Blocker}'s
101
+ * `proceed`/`reset`.
102
+ */
103
+ export type BlockerFunction = (args: {
104
+ readonly currentLocation: BlockerLocation;
105
+ readonly nextLocation: BlockerLocation;
106
+ }) => boolean;
107
+ /**
108
+ * The blocker's lifecycle. `unblocked` - idle, nothing intercepted. `blocked` - a navigation was halted
109
+ * and is awaiting the app's decision (`proceed`/`reset` are live). `proceeding` - the app called
110
+ * `proceed`; the held navigation is being replayed.
111
+ */
112
+ export type BlockerState = "unblocked" | "blocked" | "proceeding";
113
+ /**
114
+ * A navigation guard, mirroring react-router's shape. When `state` is `blocked`, `proceed()` lets the
115
+ * held navigation through and `reset()` cancels it (staying put); both are `undefined` otherwise. The
116
+ * pair is what a boolean `when` can't express - the app shows its OWN async confirmation UI, then calls
117
+ * one of them, instead of the browser's synchronous `confirm()`.
118
+ */
119
+ export interface Blocker {
120
+ readonly state: BlockerState;
121
+ readonly proceed: (() => void) | undefined;
122
+ readonly reset: (() => void) | undefined;
123
+ }
124
+ /** The idle blocker - a stable reference (no needless adapter re-renders while unblocked). */
125
+ export declare const IDLE_BLOCKER: Blocker;
126
+ /**
127
+ * The browser layer's blocker registry - installed by `installHistory` (which owns navigation and can
128
+ * therefore halt, restore, and replay it) and read by an adapter's `useBlocker` through
129
+ * {@link registerBlocker}. Kept here, DOM-free, for the same reason as {@link BrowserNavigate}: a route
130
+ * component's blocker hook must import only this agnostic entry.
131
+ */
132
+ export interface BlockerController {
133
+ register(shouldBlock: BlockerFunction, onChange: (blocker: Blocker) => void): () => void;
134
+ }
135
+ /** Register (or clear, with `undefined`) the blocker controller - called by `installHistory`. Not for
136
+ * app use. */
137
+ export declare function setBlockerController(controller: BlockerController | undefined): void;
138
+ /**
139
+ * Register a navigation guard, returning an unregister function. `onChange` is called with the current
140
+ * {@link Blocker} whenever its state changes (so the adapter can re-render its confirmation UI). Before
141
+ * `installHistory` has run (SSR, pre-hydration), there's no controller: registration is a no-op and the
142
+ * caller stays on {@link IDLE_BLOCKER}, degrading to native navigation. Called by an adapter's
143
+ * `useBlocker`, not by app code directly.
144
+ */
145
+ export declare function registerBlocker(shouldBlock: BlockerFunction, onChange: (blocker: Blocker) => void): () => void;
31
146
  //# sourceMappingURL=navigation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../src/navigation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,6CAA6C;AAC7C,MAAM,WAAW,eAAe;IAC9B,oGAAoG;IACpG,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAC3B;AAED;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,KAAK,IAAI,CAAA;AAMtF;cACc;AACd,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,eAAe,GAAG,SAAS,GAAG,IAAI,CAE9E;AAED;kFACkF;AAClF,wBAAgB,kBAAkB,IAAI,eAAe,GAAG,SAAS,CAEhE"}
1
+ {"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../src/navigation.ts"],"names":[],"mappings":"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"}
@@ -1,20 +1,33 @@
1
1
  /**
2
- * `@nifrajs/web` navigation bridge — a tiny, DOM-free seam that lets an adapter's `useNavigate`
3
- * (a route component, which must import only from the agnostic `@nifrajs/web` main entry — never the
2
+ * `@nifrajs/web` navigation bridge - a tiny, DOM-free seam that lets an adapter's `useNavigate`
3
+ * (a route component, which must import only from the agnostic `@nifrajs/web` main entry - never the
4
4
  * DOM-only `/client`) reach the browser's history-aware navigate, WITHOUT the route component pulling
5
5
  * `@nifrajs/web/client` (and its `document`/`history` access) into a bundle that also renders on the
6
6
  * server.
7
7
  *
8
8
  * The browser layer (`installHistory`, in `./client.ts`) populates this on setup and clears it on
9
- * teardown; a framework binding reads it via {@link getBrowserNavigate}. Module-level singleton — the
9
+ * teardown; a framework binding reads it via {@link getBrowserNavigate}. Module-level singleton - the
10
10
  * browser mounts exactly one app per page (the same convention the adapters' `setMountedRouter` uses).
11
11
  * On the server (and before hydration) the getter returns `undefined`, so a binding degrades to the
12
- * native `<a href>` full-page navigation — progressive enhancement, no throw.
12
+ * native `<a href>` full-page navigation - progressive enhancement, no throw.
13
13
  */
14
+ import { serializeSearch } from "./search.js";
15
+ /**
16
+ * Normalize a navigate argument to the bridge's `(to, options)`: a string path or history-delta passes
17
+ * through; an object target has its `search` serialized onto `to` and its `replace` folded into the
18
+ * options. The one place the object form becomes a URL, so every adapter's `navigate` resolves it
19
+ * identically.
20
+ */
21
+ export function resolveNavigate(to, options) {
22
+ if (typeof to !== "object")
23
+ return { to, options };
24
+ const query = to.search !== undefined ? serializeSearch(to.search) : "";
25
+ return { to: to.to + query, options: to.replace === true ? { replace: true } : options };
26
+ }
14
27
  // The active browser navigate (set by `installHistory`, cleared on teardown). Module-scoped: one app
15
28
  // per page; absent on the server and before hydration.
16
29
  let browserNavigate;
17
- /** Register (or clear, with `undefined`) the browser navigate — called by `installHistory`. Not for
30
+ /** Register (or clear, with `undefined`) the browser navigate - called by `installHistory`. Not for
18
31
  * app use. */
19
32
  export function setBrowserNavigate(navigate) {
20
33
  browserNavigate = navigate;
@@ -24,4 +37,23 @@ export function setBrowserNavigate(navigate) {
24
37
  export function getBrowserNavigate() {
25
38
  return browserNavigate;
26
39
  }
40
+ /** The idle blocker - a stable reference (no needless adapter re-renders while unblocked). */
41
+ export const IDLE_BLOCKER = { state: "unblocked", proceed: undefined, reset: undefined };
42
+ // The active controller (set by `installHistory`, cleared on teardown). Module-scoped: one app per page.
43
+ let blockerController;
44
+ /** Register (or clear, with `undefined`) the blocker controller - called by `installHistory`. Not for
45
+ * app use. */
46
+ export function setBlockerController(controller) {
47
+ blockerController = controller;
48
+ }
49
+ /**
50
+ * Register a navigation guard, returning an unregister function. `onChange` is called with the current
51
+ * {@link Blocker} whenever its state changes (so the adapter can re-render its confirmation UI). Before
52
+ * `installHistory` has run (SSR, pre-hydration), there's no controller: registration is a no-op and the
53
+ * caller stays on {@link IDLE_BLOCKER}, degrading to native navigation. Called by an adapter's
54
+ * `useBlocker`, not by app code directly.
55
+ */
56
+ export function registerBlocker(shouldBlock, onChange) {
57
+ return blockerController?.register(shouldBlock, onChange) ?? (() => { });
58
+ }
27
59
  //# sourceMappingURL=navigation.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"navigation.js","sourceRoot":"","sources":["../src/navigation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAeH,qGAAqG;AACrG,uDAAuD;AACvD,IAAI,eAA4C,CAAA;AAEhD;cACc;AACd,MAAM,UAAU,kBAAkB,CAAC,QAAqC;IACtE,eAAe,GAAG,QAAQ,CAAA;AAC5B,CAAC;AAED;kFACkF;AAClF,MAAM,UAAU,kBAAkB;IAChC,OAAO,eAAe,CAAA;AACxB,CAAC"}
1
+ {"version":3,"file":"navigation.js","sourceRoot":"","sources":["../src/navigation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AA8D7C;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAC7B,EAAyC,EACzC,OAAyB;IAEzB,IAAI,OAAO,EAAE,KAAK,QAAQ;QAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,CAAA;IAClD,MAAM,KAAK,GAAG,EAAE,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,eAAe,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IACvE,OAAO,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAA;AAC1F,CAAC;AASD,qGAAqG;AACrG,uDAAuD;AACvD,IAAI,eAA4C,CAAA;AAEhD;cACc;AACd,MAAM,UAAU,kBAAkB,CAAC,QAAqC;IACtE,eAAe,GAAG,QAAQ,CAAA;AAC5B,CAAC;AAED;kFACkF;AAClF,MAAM,UAAU,kBAAkB;IAChC,OAAO,eAAe,CAAA;AACxB,CAAC;AAyCD,8FAA8F;AAC9F,MAAM,CAAC,MAAM,YAAY,GAAY,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,CAAA;AAYjG,yGAAyG;AACzG,IAAI,iBAAgD,CAAA;AAEpD;cACc;AACd,MAAM,UAAU,oBAAoB,CAAC,UAAyC;IAC5E,iBAAiB,GAAG,UAAU,CAAA;AAChC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAC7B,WAA4B,EAC5B,QAAoC;IAEpC,OAAO,iBAAiB,EAAE,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;AACzE,CAAC"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `@nifrajs/web/plugins/css-modules` — a dependency-free CSS Modules (`*.module.css`) Bun plugin, in its
2
+ * `@nifrajs/web/plugins/css-modules` - a dependency-free CSS Modules (`*.module.css`) Bun plugin, in its
3
3
  * OWN module so the SSR preload registers it BEFORE any `.module.css` file loads. Mirrors the
4
4
  * `@nifrajs/web-vue/plugin` seam exactly: pass `"dom"` for the client bundle
5
5
  * (`buildClient({ plugins: [...] })`) and preload `"ssr"` for the server (`bun --preload`).
@@ -7,7 +7,7 @@
7
7
  * Each `import styles from "./x.module.css"` becomes a JS module whose **default export** is the
8
8
  * `{ originalClassName: scopedClassName }` map; the rewritten (scoped) CSS is emitted into the client
9
9
  * stylesheet via the `?nifra-css-module` virtual-module idiom (the same trick the Vue plugin uses for
10
- * `?vue-css`). The `"ssr"` form emits **no** CSS — the stylesheet ships from the client build — but
10
+ * `?vue-css`). The `"ssr"` form emits **no** CSS - the stylesheet ships from the client build - but
11
11
  * produces the **identical** class map, so SSR markup's `class={styles.foo}` matches the bundled
12
12
  * selectors (scoped names are a pure function of file path + class name, so both builds agree).
13
13
  *
@@ -17,7 +17,7 @@
17
17
  * pseudo-classes/`:not(...)`, native nesting, at-rules (`@media`/`@supports`/`@container`/`@layer`),
18
18
  * `:global(...)`/`:local(...)` (function form), and **`@keyframes` names + their `animation`/
19
19
  * `animation-name` references** (scoped together, so two modules' same-named keyframes don't clash).
20
- * Not handled: the bare `:global`/`:local` *switch* form and `composes:` — out of scope by design.
20
+ * Not handled: the bare `:global`/`:local` *switch* form and `composes:` - out of scope by design.
21
21
  */
22
22
  import type { BunPlugin } from "bun";
23
23
  /** The transform result: the `{ original: scoped }` export map + the rewritten (scoped) stylesheet. */
Binary file
@@ -1,24 +1,24 @@
1
1
  import type { BunPlugin } from "bun";
2
- /** The argument Bun passes to a plugin's `setup` — Bun doesn't export the type, so derive it. */
2
+ /** The argument Bun passes to a plugin's `setup` - Bun doesn't export the type, so derive it. */
3
3
  export type PluginBuilder = Parameters<BunPlugin["setup"]>[0];
4
4
  /**
5
- * A **package-root-relative**, forward-slashed form of an absolute path — the input to {@link hash8} for
5
+ * A **package-root-relative**, forward-slashed form of an absolute path - the input to {@link hash8} for
6
6
  * any build-stable identifier (e.g. CSS-module scoped names). Anchoring on the file's nearest
7
7
  * `package.json` (not the absolute path, not `process.cwd()`) makes the result independent of BOTH the
8
- * machine's directory layout AND the working directory: the dom build and the ssr runtime — even on
9
- * different machines or from different cwds — derive the SAME relative path for a given file, so their
8
+ * machine's directory layout AND the working directory: the dom build and the ssr runtime - even on
9
+ * different machines or from different cwds - derive the SAME relative path for a given file, so their
10
10
  * scoped class maps always agree. (Hashing the absolute path would differ across CI/host; a
11
11
  * cwd-relative path would silently desync if the SSR server started from a non-project-root cwd.)
12
12
  */
13
13
  export declare function reproduciblePath(absolutePath: string): string;
14
14
  /**
15
- * Deterministic 8-hex hash (djb2/xor). Stable across builds — no `Date.now`/`Math.random` — so build
15
+ * Deterministic 8-hex hash (djb2/xor). Stable across builds - no `Date.now`/`Math.random` - so build
16
16
  * output is reproducible. The single hash implementation behind CSS-module scoped names (and a drop-in
17
17
  * for any SFC scope id).
18
18
  */
19
19
  export declare function hash8(input: string): string;
20
20
  /**
21
- * Records compiled CSS and wires it into the client bundle through a virtual `?<namespace>` module —
21
+ * Records compiled CSS and wires it into the client bundle through a virtual `?<namespace>` module -
22
22
  * the idiom the Vue plugin established (`?vue-css`). Register one per plugin `setup`; call `emit` per
23
23
  * file to stash its CSS and get back the `import` line to append to the JS module.
24
24
  */
@@ -29,12 +29,12 @@ export interface StylesheetEmitter {
29
29
  /**
30
30
  * Wire the virtual-CSS-module handlers onto `build` for `namespace`, returning an {@link StylesheetEmitter}.
31
31
  * The `namespace` must be a plain identifier (letters/`-`); it's used verbatim as the import suffix and
32
- * the Bun namespace. Only the `"dom"` build should emit CSS — the `"ssr"` build ships no stylesheet.
32
+ * the Bun namespace. Only the `"dom"` build should emit CSS - the `"ssr"` build ships no stylesheet.
33
33
  */
34
34
  export declare function createStylesheetEmitter(build: PluginBuilder, namespace: string): StylesheetEmitter;
35
35
  /**
36
36
  * Load an optional peer compiler at build time, throwing a consistent, actionable install-hint error if
37
- * it's absent — the `@vue/compiler-sfc` peer pattern, centralized. Build-time only, so the dynamic
37
+ * it's absent - the `@vue/compiler-sfc` peer pattern, centralized. Build-time only, so the dynamic
38
38
  * `import` (which keeps the peer out of the package's hard dependencies) is correct here.
39
39
  */
40
40
  export declare function requirePeer<T>(specifier: string, hint: {
@@ -7,7 +7,7 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
7
7
  return path;
8
8
  };
9
9
  /**
10
- * `@nifrajs/web/plugins` shared kit — the small infrastructure every CSS-bearing asset plugin reuses, so
10
+ * `@nifrajs/web/plugins` shared kit - the small infrastructure every CSS-bearing asset plugin reuses, so
11
11
  * P1 (CSS Modules), P2 (SCSS), and any future P4 (LESS/Stylus/PostCSS) don't each re-implement the
12
12
  * CSS→client-bundle wiring, the deterministic hash, or the optional-peer guard. Internal to
13
13
  * `@nifrajs/web` (imported by the sibling plugin modules); not a public subpath.
@@ -27,7 +27,7 @@ function packageRootOf(startDir) {
27
27
  break;
28
28
  const parent = dirname(dir);
29
29
  if (parent === dir) {
30
- dir = startDir; // reached the filesystem root with no package.json — anchor on the file's own dir
30
+ dir = startDir; // reached the filesystem root with no package.json - anchor on the file's own dir
31
31
  break;
32
32
  }
33
33
  dir = parent;
@@ -36,11 +36,11 @@ function packageRootOf(startDir) {
36
36
  return dir;
37
37
  }
38
38
  /**
39
- * A **package-root-relative**, forward-slashed form of an absolute path — the input to {@link hash8} for
39
+ * A **package-root-relative**, forward-slashed form of an absolute path - the input to {@link hash8} for
40
40
  * any build-stable identifier (e.g. CSS-module scoped names). Anchoring on the file's nearest
41
41
  * `package.json` (not the absolute path, not `process.cwd()`) makes the result independent of BOTH the
42
- * machine's directory layout AND the working directory: the dom build and the ssr runtime — even on
43
- * different machines or from different cwds — derive the SAME relative path for a given file, so their
42
+ * machine's directory layout AND the working directory: the dom build and the ssr runtime - even on
43
+ * different machines or from different cwds - derive the SAME relative path for a given file, so their
44
44
  * scoped class maps always agree. (Hashing the absolute path would differ across CI/host; a
45
45
  * cwd-relative path would silently desync if the SSR server started from a non-project-root cwd.)
46
46
  */
@@ -49,7 +49,7 @@ export function reproduciblePath(absolutePath) {
49
49
  return relative(root, absolutePath).replaceAll("\\", "/");
50
50
  }
51
51
  /**
52
- * Deterministic 8-hex hash (djb2/xor). Stable across builds — no `Date.now`/`Math.random` — so build
52
+ * Deterministic 8-hex hash (djb2/xor). Stable across builds - no `Date.now`/`Math.random` - so build
53
53
  * output is reproducible. The single hash implementation behind CSS-module scoped names (and a drop-in
54
54
  * for any SFC scope id).
55
55
  */
@@ -62,7 +62,7 @@ export function hash8(input) {
62
62
  /**
63
63
  * Wire the virtual-CSS-module handlers onto `build` for `namespace`, returning an {@link StylesheetEmitter}.
64
64
  * The `namespace` must be a plain identifier (letters/`-`); it's used verbatim as the import suffix and
65
- * the Bun namespace. Only the `"dom"` build should emit CSS — the `"ssr"` build ships no stylesheet.
65
+ * the Bun namespace. Only the `"dom"` build should emit CSS - the `"ssr"` build ships no stylesheet.
66
66
  */
67
67
  export function createStylesheetEmitter(build, namespace) {
68
68
  const suffix = `?${namespace}`;
@@ -85,7 +85,7 @@ export function createStylesheetEmitter(build, namespace) {
85
85
  }
86
86
  /**
87
87
  * Load an optional peer compiler at build time, throwing a consistent, actionable install-hint error if
88
- * it's absent — the `@vue/compiler-sfc` peer pattern, centralized. Build-time only, so the dynamic
88
+ * it's absent - the `@vue/compiler-sfc` peer pattern, centralized. Build-time only, so the dynamic
89
89
  * `import` (which keeps the peer out of the package's hard dependencies) is correct here.
90
90
  */
91
91
  export async function requirePeer(specifier, hint) {
@@ -93,7 +93,7 @@ export async function requirePeer(specifier, hint) {
93
93
  return (await import(__rewriteRelativeImportExtension(specifier)));
94
94
  }
95
95
  catch (err) {
96
- // Only a genuine resolution failure means "not installed" — surface anything else (a corrupt
96
+ // Only a genuine resolution failure means "not installed" - surface anything else (a corrupt
97
97
  // install, a native-binding error, a throw at module top-level) as the real error rather than
98
98
  // masking it behind a misleading "please install it" hint.
99
99
  if (err.code === "ERR_MODULE_NOT_FOUND") {
@@ -1,17 +1,17 @@
1
1
  /**
2
- * `@nifrajs/web/plugins/scss` — a SASS/SCSS (`*.scss`, `*.sass`) Bun plugin, in its OWN module so the SSR
2
+ * `@nifrajs/web/plugins/scss` - a SASS/SCSS (`*.scss`, `*.sass`) Bun plugin, in its OWN module so the SSR
3
3
  * preload registers it BEFORE any Sass file loads. Mirrors the `@nifrajs/web-vue/plugin` seam: pass
4
4
  * `"dom"` for the client bundle (`buildClient({ plugins: [...] })`) and preload `"ssr"` for the server
5
5
  * (`bun --preload`).
6
6
  *
7
- * The Dart Sass compiler is an **optional peer** (`sass`, or the faster `sass-embedded` — same API), not
7
+ * The Dart Sass compiler is an **optional peer** (`sass`, or the faster `sass-embedded` - same API), not
8
8
  * a hard dependency: it's loaded on first Sass file and fails loud with an install hint if absent. Pass
9
9
  * your own `compiler` to override (e.g. `sass-embedded`, or a stub in tests).
10
10
  *
11
11
  * Composes with CSS Modules: a `*.module.scss` / `*.module.sass` file is compiled to CSS and then run
12
12
  * through the same scoped-class transform as `@nifrajs/web/plugins/css-modules`, so its `import styles`
13
13
  * yields the `{ original: scoped }` map (SSR/dom class-map parity included). A plain `*.scss` is a
14
- * side-effect import — its CSS is bundled (dom) and it resolves to an empty module (ssr).
14
+ * side-effect import - its CSS is bundled (dom) and it resolves to an empty module (ssr).
15
15
  */
16
16
  import { dirname } from "node:path";
17
17
  import { pathToFileURL } from "node:url";
@@ -19,7 +19,7 @@ interface RollupPluginContext {
19
19
  */
20
20
  error(error: Error): never;
21
21
  }
22
- /** The minimal Rollup plugin shape this returns — `generateBundle` bound to the plugin context. */
22
+ /** The minimal Rollup plugin shape this returns - `generateBundle` bound to the plugin context. */
23
23
  export interface LeakGuardPlugin {
24
24
  readonly name: string;
25
25
  /**
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `@nifrajs/web/plugins/vite-leak-guard` — nifra's two client-leak guards, for a Vite/Rollup production
2
+ * `@nifrajs/web/plugins/vite-leak-guard` - nifra's two client-leak guards, for a Vite/Rollup production
3
3
  * build.
4
4
  *
5
5
  * ## Why this exists
@@ -0,0 +1,23 @@
1
+ /** The slice of a Vite/Rollup plugin this returns. Structural, so `vite` stays an optional peer. */
2
+ export interface ServerFnStubPlugin {
3
+ readonly name: string;
4
+ readonly enforce: "pre";
5
+ /** Applied only to the CLIENT build; the server build keeps the real module. */
6
+ readonly applyToEnvironment?: (environment: {
7
+ readonly name: string;
8
+ }) => boolean;
9
+ transform(code: string, id: string, options?: {
10
+ readonly ssr?: boolean;
11
+ }): {
12
+ code: string;
13
+ map: null;
14
+ } | null;
15
+ }
16
+ /**
17
+ * Replace every `*.fn` module with its client stubs.
18
+ *
19
+ * Client-only by construction: nifra passes this to the client build and the dev server, never to the
20
+ * SSR build, because the server is what actually runs these functions.
21
+ */
22
+ export declare function viteServerFnStub(): ServerFnStubPlugin;
23
+ //# sourceMappingURL=vite-server-fn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite-server-fn.d.ts","sourceRoot":"","sources":["../../src/plugins/vite-server-fn.ts"],"names":[],"mappings":"AA2BA,oGAAoG;AACpG,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAA;IACvB,gFAAgF;IAChF,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC,WAAW,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAA;IACjF,SAAS,CACP,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,EACV,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAA;KAAE,GACnC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,IAAI,CAAA;KAAE,GAAG,IAAI,CAAA;CACtC;AAKD;;;;;GAKG;AACH,wBAAgB,gBAAgB,IAAI,kBAAkB,CAiBrD"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * `@nifrajs/web/plugins/vite-server-fn` - the Vite half of the server-function client transform.
3
+ *
4
+ * A `*.fn` module must never reach a browser: it holds the function bodies and everything they import.
5
+ * The Bun build replaces each one with client stubs, and this does the same for Vite, in both the dev
6
+ * server and the production build.
7
+ *
8
+ * ## Why the generation is not in here
9
+ *
10
+ * The stub text comes from `internal/server-boundary.ts`, shared verbatim with the Bun plugin. Two
11
+ * implementations of "what a stub looks like" would drift, and the drift would be invisible until a
12
+ * client that worked in dev 404'd in production - the exact shape of failure this codebase has already
13
+ * paid for once. A parity test asserts both pipelines emit the same bytes.
14
+ *
15
+ * ## Why `transform` and not `load`
16
+ *
17
+ * Vite resolves and reads the file itself; taking `load` would mean re-implementing its resolution.
18
+ * `transform` receives the source Vite already read, and returning replacement code discards the
19
+ * original along with its imports - so the server subtree is never followed. `enforce: "pre"` puts this
20
+ * ahead of framework transforms, which have no business seeing server code either.
21
+ */
22
+ import { generateServerFnStub, SERVER_FN_MODULE, serverFnNamespace, } from "../internal/server-boundary.js";
23
+ /** Strips a Vite id's `?query` and `#hash` so the suffix test sees a plain path. */
24
+ const bare = (id) => id.split("?")[0]?.split("#")[0] ?? id;
25
+ /**
26
+ * Replace every `*.fn` module with its client stubs.
27
+ *
28
+ * Client-only by construction: nifra passes this to the client build and the dev server, never to the
29
+ * SSR build, because the server is what actually runs these functions.
30
+ */
31
+ export function viteServerFnStub() {
32
+ return {
33
+ name: "nifra:server-fn-stub",
34
+ enforce: "pre",
35
+ // Vite 6+ environments: the SSR environment must keep the real module. Older Vite ignores this and
36
+ // relies on nifra only registering the plugin on the client side.
37
+ applyToEnvironment: (environment) => environment.name === "client",
38
+ transform(code, id, options) {
39
+ // Vite 5 ignores `applyToEnvironment` but supplies this transform context for SSR.
40
+ if (options?.ssr === true)
41
+ return null;
42
+ const path = bare(id);
43
+ if (!SERVER_FN_MODULE.test(path))
44
+ return null;
45
+ // `map: null` is honest rather than lazy: the stub shares no lines with the source, so any
46
+ // mapping would point a debugger at unrelated code.
47
+ return { code: generateServerFnStub(code, serverFnNamespace(path)), map: null };
48
+ },
49
+ };
50
+ }
51
+ //# sourceMappingURL=vite-server-fn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite-server-fn.js","sourceRoot":"","sources":["../../src/plugins/vite-server-fn.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,gCAAgC,CAAA;AAevC,oFAAoF;AACpF,MAAM,IAAI,GAAG,CAAC,EAAU,EAAU,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;AAE1E;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB;IAC9B,OAAO;QACL,IAAI,EAAE,sBAAsB;QAC5B,OAAO,EAAE,KAAK;QACd,mGAAmG;QACnG,kEAAkE;QAClE,kBAAkB,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,KAAK,QAAQ;QAClE,SAAS,CAAC,IAAI,EAAE,EAAE,EAAE,OAAO;YACzB,mFAAmF;YACnF,IAAI,OAAO,EAAE,GAAG,KAAK,IAAI;gBAAE,OAAO,IAAI,CAAA;YACtC,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,CAAA;YACrB,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC;gBAAE,OAAO,IAAI,CAAA;YAC7C,2FAA2F;YAC3F,oDAAoD;YACpD,OAAO,EAAE,IAAI,EAAE,oBAAoB,CAAC,IAAI,EAAE,iBAAiB,CAAC,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,CAAA;QACjF,CAAC;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * `@nifrajs/web/plugins/vite-server-only` - the Vite half of the `.server` convention.
3
+ *
4
+ * A module named `*.server.ts(x)` is server-only: the Bun client build empties it so its import
5
+ * subtree - `node:` builtins, native modules, secrets - never reaches a browser. This does the same
6
+ * for Vite, in both the dev server and the production build.
7
+ *
8
+ * ## Why this exists separately
9
+ *
10
+ * The convention was implemented once, as a `Bun.build` plugin, which meant it held in exactly one of
11
+ * the four pipelines. `nifra build` emptied the module; `nifra dev` (Vite), a Vite production build,
12
+ * and `nifra dev --bun` all shipped it whole. A guard that holds in one pipeline and not the others is
13
+ * worse than none, because the file name reads as protection everywhere.
14
+ *
15
+ * `*.fn` already had this shape - a Bun plugin, a Vite plugin, and a refusal in the one pipeline that
16
+ * cannot transform. `.server` now matches it.
17
+ *
18
+ * ## Why `transform` and not `load`
19
+ *
20
+ * Vite resolves and reads the file itself; taking `load` would mean re-implementing its resolution.
21
+ * `transform` receives the source Vite already read, and returning replacement code discards the
22
+ * original along with its imports - so the server subtree is never followed. `enforce: "pre"` puts
23
+ * this ahead of framework transforms, which have no business seeing server code either.
24
+ */
25
+ import { SERVER_ONLY_MODULE, SERVER_ONLY_REPLACEMENT, viteServerOnlyReplacement } from "../internal/server-boundary.js";
26
+ export { SERVER_ONLY_MODULE, SERVER_ONLY_REPLACEMENT, viteServerOnlyReplacement };
27
+ /** The slice of a Vite/Rollup plugin this returns. Structural, so `vite` stays an optional peer. */
28
+ export interface ServerOnlyEmptyPlugin {
29
+ readonly name: string;
30
+ readonly enforce: "pre";
31
+ /** Applied only to the CLIENT build; the server build keeps the real module. */
32
+ readonly applyToEnvironment?: (environment: {
33
+ readonly name: string;
34
+ }) => boolean;
35
+ transform(code: string, id: string, options?: {
36
+ readonly ssr?: boolean;
37
+ }): {
38
+ code: string;
39
+ map: null;
40
+ } | null;
41
+ }
42
+ /**
43
+ * Empty every `*.server` module in the client build.
44
+ *
45
+ * Client-only by construction: nifra passes this to the client build and the dev server, never to the
46
+ * SSR build, where the real module is what runs.
47
+ */
48
+ export declare function viteServerOnlyEmpty(): ServerOnlyEmptyPlugin;
49
+ //# sourceMappingURL=vite-server-only.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite-server-only.d.ts","sourceRoot":"","sources":["../../src/plugins/vite-server-only.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EACL,kBAAkB,EAClB,uBAAuB,EACvB,yBAAyB,EAC1B,MAAM,gCAAgC,CAAA;AAIvC,OAAO,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,yBAAyB,EAAE,CAAA;AAEjF,oGAAoG;AACpG,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAA;IACvB,gFAAgF;IAChF,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC,WAAW,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAA;IACjF,SAAS,CACP,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,EACV,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAA;KAAE,GACnC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,IAAI,CAAA;KAAE,GAAG,IAAI,CAAA;CACtC;AAKD;;;;;GAKG;AACH,wBAAgB,mBAAmB,IAAI,qBAAqB,CAiB3D"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * `@nifrajs/web/plugins/vite-server-only` - the Vite half of the `.server` convention.
3
+ *
4
+ * A module named `*.server.ts(x)` is server-only: the Bun client build empties it so its import
5
+ * subtree - `node:` builtins, native modules, secrets - never reaches a browser. This does the same
6
+ * for Vite, in both the dev server and the production build.
7
+ *
8
+ * ## Why this exists separately
9
+ *
10
+ * The convention was implemented once, as a `Bun.build` plugin, which meant it held in exactly one of
11
+ * the four pipelines. `nifra build` emptied the module; `nifra dev` (Vite), a Vite production build,
12
+ * and `nifra dev --bun` all shipped it whole. A guard that holds in one pipeline and not the others is
13
+ * worse than none, because the file name reads as protection everywhere.
14
+ *
15
+ * `*.fn` already had this shape - a Bun plugin, a Vite plugin, and a refusal in the one pipeline that
16
+ * cannot transform. `.server` now matches it.
17
+ *
18
+ * ## Why `transform` and not `load`
19
+ *
20
+ * Vite resolves and reads the file itself; taking `load` would mean re-implementing its resolution.
21
+ * `transform` receives the source Vite already read, and returning replacement code discards the
22
+ * original along with its imports - so the server subtree is never followed. `enforce: "pre"` puts
23
+ * this ahead of framework transforms, which have no business seeing server code either.
24
+ */
25
+ import { SERVER_ONLY_MODULE, SERVER_ONLY_REPLACEMENT, viteServerOnlyReplacement, } from "../internal/server-boundary.js";
26
+ // Re-exported so a caller wiring its own bundler gets the matcher from the same owner the pipelines
27
+ // use, rather than writing a fifth copy of the regex.
28
+ export { SERVER_ONLY_MODULE, SERVER_ONLY_REPLACEMENT, viteServerOnlyReplacement };
29
+ /** Strips a Vite id's `?query` and `#hash` so the suffix test sees a plain path. */
30
+ const bare = (id) => id.split("?")[0]?.split("#")[0] ?? id;
31
+ /**
32
+ * Empty every `*.server` module in the client build.
33
+ *
34
+ * Client-only by construction: nifra passes this to the client build and the dev server, never to the
35
+ * SSR build, where the real module is what runs.
36
+ */
37
+ export function viteServerOnlyEmpty() {
38
+ return {
39
+ name: "nifra:server-only-empty",
40
+ enforce: "pre",
41
+ // Vite 6+ environments: the SSR environment must keep the real module. Older Vite ignores this and
42
+ // relies on nifra only registering the plugin on the client side.
43
+ applyToEnvironment: (environment) => environment.name === "client",
44
+ transform(code, id, options) {
45
+ // Vite 5 ignores `applyToEnvironment` but supplies this transform context for SSR.
46
+ if (options?.ssr === true)
47
+ return null;
48
+ const path = bare(id);
49
+ if (!SERVER_ONLY_MODULE.test(path))
50
+ return null;
51
+ // `map: null` is honest rather than lazy: the replacement shares no lines with the source, so any
52
+ // mapping would point a debugger at unrelated code.
53
+ return { code: viteServerOnlyReplacement(code), map: null };
54
+ },
55
+ };
56
+ }
57
+ //# sourceMappingURL=vite-server-only.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite-server-only.js","sourceRoot":"","sources":["../../src/plugins/vite-server-only.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EACL,kBAAkB,EAClB,uBAAuB,EACvB,yBAAyB,GAC1B,MAAM,gCAAgC,CAAA;AAEvC,oGAAoG;AACpG,sDAAsD;AACtD,OAAO,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,yBAAyB,EAAE,CAAA;AAejF,oFAAoF;AACpF,MAAM,IAAI,GAAG,CAAC,EAAU,EAAU,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;AAE1E;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB;IACjC,OAAO;QACL,IAAI,EAAE,yBAAyB;QAC/B,OAAO,EAAE,KAAK;QACd,mGAAmG;QACnG,kEAAkE;QAClE,kBAAkB,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,KAAK,QAAQ;QAClE,SAAS,CAAC,IAAI,EAAE,EAAE,EAAE,OAAO;YACzB,mFAAmF;YACnF,IAAI,OAAO,EAAE,GAAG,KAAK,IAAI;gBAAE,OAAO,IAAI,CAAA;YACtC,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,CAAA;YACrB,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC;gBAAE,OAAO,IAAI,CAAA;YAC/C,kGAAkG;YAClG,oDAAoD;YACpD,OAAO,EAAE,IAAI,EAAE,yBAAyB,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,CAAA;QAC7D,CAAC;KACF,CAAA;AACH,CAAC"}