@nifrajs/web 2.14.1 → 3.2.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 (145) hide show
  1. package/dist/boundary.d.ts +158 -0
  2. package/dist/boundary.d.ts.map +1 -0
  3. package/dist/boundary.js +237 -0
  4. package/dist/boundary.js.map +1 -0
  5. package/dist/build-plan.d.ts +228 -0
  6. package/dist/build-plan.d.ts.map +1 -0
  7. package/dist/build-plan.js +514 -0
  8. package/dist/build-plan.js.map +1 -0
  9. package/dist/build-vite.d.ts +2 -1
  10. package/dist/build-vite.d.ts.map +1 -1
  11. package/dist/build-vite.js +5 -3
  12. package/dist/build-vite.js.map +1 -1
  13. package/dist/build.d.ts +2 -197
  14. package/dist/build.d.ts.map +1 -1
  15. package/dist/build.js +92 -515
  16. package/dist/build.js.map +1 -1
  17. package/dist/chunk-names.d.ts.map +1 -1
  18. package/dist/chunk-names.js +3 -2
  19. package/dist/chunk-names.js.map +1 -1
  20. package/dist/dev-ssr-graph.d.ts.map +1 -1
  21. package/dist/dev-ssr-graph.js +63 -28
  22. package/dist/dev-ssr-graph.js.map +1 -1
  23. package/dist/diagnostic.d.ts.map +1 -1
  24. package/dist/diagnostic.js +9 -2
  25. package/dist/diagnostic.js.map +1 -1
  26. package/dist/fonts-google.d.ts.map +1 -1
  27. package/dist/fonts-google.js +3 -1
  28. package/dist/fonts-google.js.map +1 -1
  29. package/dist/fs.d.ts.map +1 -1
  30. package/dist/fs.js +2 -1
  31. package/dist/fs.js.map +1 -1
  32. package/dist/index.d.ts +16 -589
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +23 -1824
  35. package/dist/index.js.map +1 -1
  36. package/dist/internal/codegen.d.ts +68 -0
  37. package/dist/internal/codegen.d.ts.map +1 -0
  38. package/dist/internal/codegen.js +317 -0
  39. package/dist/internal/codegen.js.map +1 -0
  40. package/dist/internal/fetcher-runtime.d.ts +19 -0
  41. package/dist/internal/fetcher-runtime.d.ts.map +1 -0
  42. package/dist/internal/fetcher-runtime.js +26 -0
  43. package/dist/internal/fetcher-runtime.js.map +1 -0
  44. package/dist/internal/head-merge.d.ts +2 -0
  45. package/dist/internal/head-merge.d.ts.map +1 -1
  46. package/dist/internal/head-merge.js +17 -1
  47. package/dist/internal/head-merge.js.map +1 -1
  48. package/dist/internal/identity-policy.d.ts +51 -0
  49. package/dist/internal/identity-policy.d.ts.map +1 -0
  50. package/dist/internal/identity-policy.js +67 -0
  51. package/dist/internal/identity-policy.js.map +1 -0
  52. package/dist/internal/parity.d.ts +104 -8
  53. package/dist/internal/parity.d.ts.map +1 -1
  54. package/dist/internal/parity.js +472 -56
  55. package/dist/internal/parity.js.map +1 -1
  56. package/dist/internal/query-runtime.d.ts +8 -0
  57. package/dist/internal/query-runtime.d.ts.map +1 -0
  58. package/dist/internal/query-runtime.js +29 -0
  59. package/dist/internal/query-runtime.js.map +1 -0
  60. package/dist/internal/render-document.d.ts +372 -0
  61. package/dist/internal/render-document.d.ts.map +1 -0
  62. package/dist/internal/render-document.js +744 -0
  63. package/dist/internal/render-document.js.map +1 -0
  64. package/dist/internal/runtime-contract.d.ts +48 -0
  65. package/dist/internal/runtime-contract.d.ts.map +1 -0
  66. package/dist/internal/runtime-contract.js +27 -0
  67. package/dist/internal/runtime-contract.js.map +1 -0
  68. package/dist/internal/web-app.d.ts +171 -0
  69. package/dist/internal/web-app.d.ts.map +1 -0
  70. package/dist/internal/web-app.js +844 -0
  71. package/dist/internal/web-app.js.map +1 -0
  72. package/dist/islands.d.ts +50 -2
  73. package/dist/islands.d.ts.map +1 -1
  74. package/dist/islands.js +96 -36
  75. package/dist/islands.js.map +1 -1
  76. package/dist/manifest.d.ts +41 -6
  77. package/dist/manifest.d.ts.map +1 -1
  78. package/dist/manifest.js.map +1 -1
  79. package/dist/module-graph.d.ts.map +1 -1
  80. package/dist/module-graph.js +22 -9
  81. package/dist/module-graph.js.map +1 -1
  82. package/dist/nano.d.ts +125 -0
  83. package/dist/nano.d.ts.map +1 -0
  84. package/dist/nano.js +190 -0
  85. package/dist/nano.js.map +1 -0
  86. package/dist/plugins/css-modules.js +2 -2
  87. package/dist/plugins/css-modules.js.map +1 -1
  88. package/dist/plugins/kit.d.ts +12 -0
  89. package/dist/plugins/kit.d.ts.map +1 -1
  90. package/dist/plugins/kit.js +62 -12
  91. package/dist/plugins/kit.js.map +1 -1
  92. package/dist/plugins/postcss.js +2 -2
  93. package/dist/plugins/postcss.js.map +1 -1
  94. package/dist/plugins/scss.js +2 -2
  95. package/dist/plugins/scss.js.map +1 -1
  96. package/dist/plugins/stylex.d.ts +70 -0
  97. package/dist/plugins/stylex.d.ts.map +1 -0
  98. package/dist/plugins/stylex.js +166 -0
  99. package/dist/plugins/stylex.js.map +1 -0
  100. package/dist/plugins/svg.js +2 -2
  101. package/dist/plugins/svg.js.map +1 -1
  102. package/dist/render-seam.d.ts +5 -0
  103. package/dist/render-seam.d.ts.map +1 -1
  104. package/dist/render-seam.js +10 -0
  105. package/dist/render-seam.js.map +1 -1
  106. package/dist/router.d.ts +11 -1
  107. package/dist/router.d.ts.map +1 -1
  108. package/dist/router.js +254 -47
  109. package/dist/router.js.map +1 -1
  110. package/dist/vite.d.ts.map +1 -1
  111. package/dist/vite.js +34 -16
  112. package/dist/vite.js.map +1 -1
  113. package/package.json +49 -2
  114. package/src/boundary.ts +407 -0
  115. package/src/build-plan.ts +741 -0
  116. package/src/build-vite.ts +6 -7
  117. package/src/build.ts +120 -730
  118. package/src/chunk-names.ts +3 -2
  119. package/src/dev-ssr-graph.ts +63 -28
  120. package/src/diagnostic.ts +14 -2
  121. package/src/fonts-google.ts +3 -1
  122. package/src/fs.ts +2 -1
  123. package/src/index.ts +107 -2553
  124. package/src/internal/codegen.ts +400 -0
  125. package/src/internal/fetcher-runtime.ts +36 -0
  126. package/src/internal/head-merge.ts +19 -1
  127. package/src/internal/identity-policy.ts +94 -0
  128. package/src/internal/parity.ts +603 -71
  129. package/src/internal/query-runtime.ts +31 -0
  130. package/src/internal/render-document.ts +1116 -0
  131. package/src/internal/runtime-contract.ts +54 -0
  132. package/src/internal/web-app.ts +1114 -0
  133. package/src/islands.ts +106 -40
  134. package/src/manifest.ts +50 -3
  135. package/src/module-graph.ts +23 -9
  136. package/src/nano.ts +246 -0
  137. package/src/plugins/css-modules.ts +2 -2
  138. package/src/plugins/kit.ts +65 -12
  139. package/src/plugins/postcss.ts +2 -2
  140. package/src/plugins/scss.ts +2 -2
  141. package/src/plugins/stylex.ts +298 -0
  142. package/src/plugins/svg.ts +2 -2
  143. package/src/render-seam.ts +6 -0
  144. package/src/router.ts +293 -51
  145. package/src/vite.ts +41 -15
@@ -8,8 +8,9 @@
8
8
  * unsafe→safe basename map lets the build manifest point at the renamed files.
9
9
  */
10
10
  import { readFileSync, renameSync, writeFileSync } from "node:fs"
11
+ import { dirname, join, basename as pathBasename } from "node:path"
11
12
 
12
- const basename = (path: string): string => path.slice(path.lastIndexOf("/") + 1)
13
+ const basename = (path: string): string => pathBasename(path)
13
14
  const URL_UNSAFE_NAME = /[^A-Za-z0-9._-]/g
14
15
 
15
16
  export function sanitizeOutputNames(
@@ -38,7 +39,7 @@ export function sanitizeOutputNames(
38
39
  for (const out of outputs) {
39
40
  const safe = renames.get(basename(out.path))
40
41
  if (safe !== undefined) {
41
- renameSync(out.path, out.path.slice(0, out.path.lastIndexOf("/") + 1) + safe)
42
+ renameSync(out.path, join(dirname(out.path), safe))
42
43
  }
43
44
  }
44
45
  return renames
@@ -55,7 +55,8 @@
55
55
  * Bun-only + dev-only; never imported by the edge runtime or by a production build.
56
56
  */
57
57
  import { statSync } from "node:fs"
58
- import { dirname } from "node:path"
58
+ import { dirname, isAbsolute, relative, resolve, sep } from "node:path"
59
+ import { fileURLToPath } from "node:url"
59
60
  import type { BunPlugin } from "bun"
60
61
 
61
62
  /**
@@ -76,11 +77,25 @@ const REWRITABLE = "mts|cts|mjs|cjs|tsx|jsx|ts|js"
76
77
  * no dot-directory (`.nifra-bun/`, `.git/`), and a source extension, with an optional cache-busting
77
78
  * query. It mirrors {@link SsrGraph}'s ownership test; keep the two in step.
78
79
  */
79
- const appSourceFilter = (root: string): RegExp =>
80
- new RegExp(
81
- `^${root.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/` +
82
- `(?!.*/node_modules/)(?!\\.)(?!.*/\\.)[^?]*\\.(?:${REWRITABLE})(?:\\?|$)`,
80
+ const escapeRegExp = (value: string): string => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
81
+
82
+ /** All path spellings Bun has used for an absolute Windows module id. */
83
+ const pathForms = (path: string): string[] => {
84
+ const native = plainPath(path)
85
+ const portable = native.replaceAll("\\", "/")
86
+ const forms = [native, portable]
87
+ if (process.platform === "win32" && /^[A-Za-z]:\//.test(portable)) forms.push(`/${portable}`)
88
+ return [...new Set(forms)]
89
+ }
90
+
91
+ const appSourceFilter = (root: string): RegExp => {
92
+ const roots = pathForms(root).map(escapeRegExp).join("|")
93
+ return new RegExp(
94
+ `^(?:${roots})[\\\\/]` +
95
+ `(?!(?:.*[\\\\/])?node_modules[\\\\/])` +
96
+ `(?!\\.)(?!.*[\\\\/]\\.)[^?]*\\.(?:${REWRITABLE})(?:\\?|$)`,
83
97
  )
98
+ }
84
99
 
85
100
  /**
86
101
  * `from "x"`, `import "x"`, `import("x")`, `require("x")` - every form that carries a static specifier,
@@ -113,10 +128,18 @@ export interface SsrGraph {
113
128
 
114
129
  /** `/abs/path.ts?v=2` and `file:///abs/path.ts` both name `/abs/path.ts`. */
115
130
  function plainPath(path: string): string {
116
- const withoutQuery = path.split("?")[0] ?? path
117
- return withoutQuery.startsWith("file://")
118
- ? decodeURIComponent(withoutQuery.slice("file://".length))
119
- : withoutQuery
131
+ const withoutQuery = path.split("?", 1)[0] ?? path
132
+ let value = withoutQuery
133
+ if (value.startsWith("file://")) {
134
+ try {
135
+ value = fileURLToPath(value)
136
+ } catch {
137
+ // Let the caller's filesystem/resolver operation report a malformed URL.
138
+ }
139
+ }
140
+ if (process.platform !== "win32") return value
141
+ if (/^\/[A-Za-z]:[\\/]/.test(value)) value = value.slice(1)
142
+ return value.replaceAll("/", "\\")
120
143
  }
121
144
 
122
145
  const mtimeOf = (path: string): number =>
@@ -161,8 +184,7 @@ export function rewriteSsrImports(contents: string, path: string, generate: "dom
161
184
 
162
185
  /** Track and version the app's own server-side module graph. See the module doc for the whole design. */
163
186
  export function createSsrGraph(options: SsrGraphOptions): SsrGraph {
164
- const root = options.root.endsWith("/") ? options.root.slice(0, -1) : options.root
165
- const prefix = `${root}/`
187
+ const root = resolve(plainPath(options.root))
166
188
  /** Every tracked module, by absolute path: its current cache version and the mtime it was read at. */
167
189
  const nodes = new Map<string, { version: number; mtime: number }>()
168
190
  /** Reverse edges - who imports this file - which is the direction invalidation travels. */
@@ -171,20 +193,30 @@ export function createSsrGraph(options: SsrGraphOptions): SsrGraph {
171
193
 
172
194
  /** Inside the app, outside `node_modules`, outside dot-directories, and a file worth re-reading. */
173
195
  const owned = (abs: string): boolean => {
174
- if (!abs.startsWith(prefix)) return false
175
- if (abs.includes("/node_modules/")) return false
196
+ const candidate = resolve(plainPath(abs))
197
+ const rel = relative(root, candidate)
198
+ if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel) || rel === "") return false
176
199
  // Relative to the app, so a project that itself lives under a dotted directory is not excluded
177
200
  // wholesale - this is about the app's own generated trees (`.nifra/`, `.nifra-bun/`, `.git/`).
178
- const rel = abs.slice(prefix.length)
179
- if (rel.startsWith(".") || rel.includes("/.")) return false
180
- return VERSIONED.test(abs)
201
+ const portable = rel.replaceAll("\\", "/")
202
+ if (
203
+ portable === "node_modules" ||
204
+ portable.startsWith("node_modules/") ||
205
+ portable.includes("/node_modules/") ||
206
+ portable.startsWith(".") ||
207
+ portable.includes("/.")
208
+ )
209
+ return false
210
+ return VERSIONED.test(candidate)
181
211
  }
182
212
 
183
213
  const track = (abs: string, importer: string): void => {
184
- if (!nodes.has(abs)) nodes.set(abs, { version: 0, mtime: mtimeOf(abs) })
185
- const parents = importers.get(abs)
186
- if (parents === undefined) importers.set(abs, new Set([importer]))
187
- else parents.add(importer)
214
+ const file = resolve(plainPath(abs))
215
+ const parent = resolve(plainPath(importer))
216
+ if (!nodes.has(file)) nodes.set(file, { version: 0, mtime: mtimeOf(file) })
217
+ const parents = importers.get(file)
218
+ if (parents === undefined) importers.set(file, new Set([parent]))
219
+ else parents.add(parent)
188
220
  }
189
221
 
190
222
  // `Bun.resolveSync` re-enters this plugin's own `onResolve`, which is an unbounded recursion rather
@@ -210,18 +242,21 @@ export function createSsrGraph(options: SsrGraphOptions): SsrGraph {
210
242
  */
211
243
  const keyFor = (specifier: string, importer: string): string | undefined => {
212
244
  const abs = resolveFrom(specifier, dirname(importer))
213
- if (abs === undefined || !owned(abs)) return undefined
214
- track(abs, importer)
215
- const version = nodes.get(abs)?.version ?? 0
245
+ if (abs === undefined) return undefined
246
+ const file = resolve(plainPath(abs))
247
+ if (!owned(file)) return undefined
248
+ track(file, importer)
249
+ const version = nodes.get(file)?.version ?? 0
216
250
  // Version 0 is the shared, unversioned instance - see the module doc.
217
- return version === 0 ? undefined : `${abs}?v=${version}`
251
+ return version === 0 ? undefined : `${file.replaceAll("\\", "/")}?v=${version}`
218
252
  }
219
253
 
220
254
  const rewrite = (contents: string, importer: string): string => {
221
- if (!importer.startsWith(prefix) || importer.includes("/node_modules/")) return contents
255
+ const importerPath = resolve(plainPath(importer))
256
+ if (!owned(importerPath)) return contents
222
257
  let scanned: readonly { readonly path: string }[]
223
258
  try {
224
- scanned = new Bun.Transpiler({ loader: scanLoaderFor(importer) }).scanImports(contents)
259
+ scanned = new Bun.Transpiler({ loader: scanLoaderFor(importerPath) }).scanImports(contents)
225
260
  } catch {
226
261
  // Source Bun cannot parse is source Bun is about to reject with a real error message. Hand it
227
262
  // through untouched rather than adding a second, worse one.
@@ -231,7 +266,7 @@ export function createSsrGraph(options: SsrGraphOptions): SsrGraph {
231
266
  if (candidates.size === 0) return contents
232
267
  const keys = new Map<string, string>()
233
268
  for (const specifier of candidates) {
234
- const key = keyFor(specifier, importer)
269
+ const key = keyFor(specifier, importerPath)
235
270
  if (key !== undefined) keys.set(specifier, key)
236
271
  }
237
272
  if (keys.size === 0) return contents
@@ -260,7 +295,7 @@ export function createSsrGraph(options: SsrGraphOptions): SsrGraph {
260
295
  // virtual sub-request (`?vue-css` and friends) - both are already keyed.
261
296
  if (args.path.includes("?")) return undefined
262
297
  const importer = plainPath(args.importer)
263
- if (!importer.startsWith(prefix) || importer.includes("/node_modules/")) return undefined
298
+ if (!owned(importer)) return undefined
264
299
  const key = keyFor(args.path, importer)
265
300
  return key === undefined ? undefined : { path: key }
266
301
  })
package/src/diagnostic.ts CHANGED
@@ -13,6 +13,7 @@
13
13
  */
14
14
  import { readFileSync, realpathSync } from "node:fs"
15
15
  import { isAbsolute, relative, resolve } from "node:path"
16
+ import { fileURLToPath } from "node:url"
16
17
 
17
18
  /** Shared endpoint name used by both dev pipelines and the agent-facing MCP tools. */
18
19
  export const LAST_ERROR_PATH = "/__nifra/last-error"
@@ -71,7 +72,13 @@ function cleanFramePath(path: string): string | undefined {
71
72
  let p = path.trim()
72
73
  if (p.startsWith("file://")) {
73
74
  try {
74
- p = decodeURIComponent(new URL(p).pathname)
75
+ const url = new URL(p)
76
+ const pathname = decodeURIComponent(url.pathname)
77
+ // Keep synthetic POSIX paths such as file:///app/... stable for injected readers and unit
78
+ // fixtures. A drive-qualified or UNC URL, however, needs Node's native conversion on Windows;
79
+ // using URL.pathname there leaves the leading slash (`/C:/…`) and makes the filesystem look in
80
+ // the wrong directory.
81
+ p = url.hostname !== "" || /^\/[A-Za-z]:[\\/]/.test(pathname) ? fileURLToPath(url) : pathname
75
82
  } catch {
76
83
  return undefined
77
84
  }
@@ -259,7 +266,12 @@ export function buildDiagnostic(err: unknown, options: BuildDiagnosticOptions =
259
266
  const top = topUserFrame(frames, root)
260
267
  const codeframe =
261
268
  top?.file !== undefined && top.line !== undefined
262
- ? buildCodeframe(canonicalPath(top.file), top.line, top.column, options.read)
269
+ ? buildCodeframe(
270
+ options.read === undefined ? canonicalPath(top.file) : top.file,
271
+ top.line,
272
+ top.column,
273
+ options.read,
274
+ )
263
275
  : undefined
264
276
  const { code, cause, fix, docsAnchor } = classify(error.name || "Error", message)
265
277
  return {
@@ -424,7 +424,9 @@ export async function loadGoogleFont(
424
424
  const hash = await contentHash(bytes)
425
425
  const style = face.style === "italic" ? "italic" : "normal"
426
426
  const fileName = `${slug(family)}-${slug(face.subset)}-${style}-${face.weight.replace(/\s+/g, "_")}-${hash}.woff2`
427
- const path = join(io.outDir, fileName)
427
+ // The injected writer is also a public/test seam. Forward slashes are accepted by Node/Bun on
428
+ // Windows and keep its observed path stable instead of leaking the host separator.
429
+ const path = join(io.outDir, fileName).replaceAll("\\", "/")
428
430
  await writeFile(path, bytes)
429
431
 
430
432
  const href = `${publicPath}/${fileName}`
package/src/fs.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  * only. It scans a routes dir and feeds the pure `buildManifest`.
5
5
  */
6
6
  import { readdirSync } from "node:fs"
7
+ import { join } from "node:path"
7
8
  import { buildManifest, type Manifest, type RouteModule } from "./manifest.ts"
8
9
 
9
10
  /** Options for {@link discoverRoutes}. */
@@ -39,6 +40,6 @@ export function discoverRoutes(dir: string, options: DiscoverRoutesOptions = {})
39
40
  : // No `importQuery` here: an injected loader owns its own invalidation (Vite's module graph
40
41
  // re-evaluates on change), and appending a cache-buster would defeat it by minting a new
41
42
  // module id per request.
42
- () => load(`${dir}/${file}`) as Promise<RouteModule>,
43
+ () => load(join(dir, file)) as Promise<RouteModule>,
43
44
  )
44
45
  }