vinext 0.1.1 → 0.1.2

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 (147) hide show
  1. package/README.md +2 -5
  2. package/dist/build/client-build-config.d.ts +7 -1
  3. package/dist/build/client-build-config.js +9 -1
  4. package/dist/check.js +4 -3
  5. package/dist/client/navigation-runtime.d.ts +3 -2
  6. package/dist/client/window-next.d.ts +6 -4
  7. package/dist/config/config-matchers.d.ts +11 -4
  8. package/dist/config/config-matchers.js +15 -2
  9. package/dist/config/next-config.d.ts +13 -0
  10. package/dist/config/next-config.js +2 -0
  11. package/dist/deploy.js +9 -2
  12. package/dist/entries/app-rsc-entry.js +7 -1
  13. package/dist/entries/pages-client-entry.js +1 -1
  14. package/dist/entries/pages-server-entry.js +7 -6
  15. package/dist/index.d.ts +0 -2
  16. package/dist/index.js +86 -78
  17. package/dist/plugins/dynamic-preload-metadata.d.ts +13 -0
  18. package/dist/plugins/dynamic-preload-metadata.js +415 -0
  19. package/dist/plugins/og-assets.js +2 -2
  20. package/dist/plugins/optimize-imports.d.ts +8 -4
  21. package/dist/plugins/optimize-imports.js +16 -12
  22. package/dist/plugins/sass.d.ts +53 -24
  23. package/dist/plugins/sass.js +249 -1
  24. package/dist/plugins/wasm-module-import.d.ts +15 -0
  25. package/dist/plugins/wasm-module-import.js +50 -0
  26. package/dist/routing/app-route-graph.d.ts +23 -1
  27. package/dist/routing/app-route-graph.js +47 -8
  28. package/dist/routing/file-matcher.js +1 -1
  29. package/dist/server/app-browser-entry.js +108 -213
  30. package/dist/server/app-browser-error.d.ts +4 -1
  31. package/dist/server/app-browser-error.js +7 -1
  32. package/dist/server/app-browser-history-controller.d.ts +104 -0
  33. package/dist/server/app-browser-history-controller.js +210 -0
  34. package/dist/server/app-browser-navigation-controller.d.ts +3 -2
  35. package/dist/server/app-browser-navigation-controller.js +10 -7
  36. package/dist/server/app-browser-rsc-redirect.d.ts +11 -2
  37. package/dist/server/app-browser-rsc-redirect.js +30 -8
  38. package/dist/server/app-browser-state.js +4 -7
  39. package/dist/server/app-browser-visible-commit.js +1 -1
  40. package/dist/server/app-fallback-renderer.d.ts +2 -1
  41. package/dist/server/app-fallback-renderer.js +3 -1
  42. package/dist/server/app-middleware.js +1 -0
  43. package/dist/server/app-optimistic-routing.js +22 -1
  44. package/dist/server/app-page-boundary-render.d.ts +2 -1
  45. package/dist/server/app-page-boundary-render.js +4 -2
  46. package/dist/server/app-page-cache.js +9 -7
  47. package/dist/server/app-page-dispatch.d.ts +8 -0
  48. package/dist/server/app-page-dispatch.js +18 -5
  49. package/dist/server/app-page-element-builder.d.ts +22 -2
  50. package/dist/server/app-page-element-builder.js +37 -8
  51. package/dist/server/app-page-execution.d.ts +1 -1
  52. package/dist/server/app-page-execution.js +32 -17
  53. package/dist/server/app-page-render.d.ts +1 -1
  54. package/dist/server/app-page-render.js +7 -14
  55. package/dist/server/app-page-request.d.ts +1 -0
  56. package/dist/server/app-page-request.js +3 -2
  57. package/dist/server/app-page-response.js +1 -1
  58. package/dist/server/app-page-route-wiring.d.ts +3 -1
  59. package/dist/server/app-page-route-wiring.js +8 -7
  60. package/dist/server/app-page-stream.d.ts +1 -6
  61. package/dist/server/app-page-stream.js +1 -4
  62. package/dist/server/app-route-handler-response.js +11 -10
  63. package/dist/server/app-route-handler-runtime.js +12 -1
  64. package/dist/server/app-rsc-handler.js +1 -1
  65. package/dist/server/app-rsc-response-finalizer.js +1 -1
  66. package/dist/server/app-server-action-execution.d.ts +11 -0
  67. package/dist/server/app-server-action-execution.js +5 -2
  68. package/dist/server/app-ssr-entry.js +2 -2
  69. package/dist/server/app-ssr-stream.js +9 -1
  70. package/dist/server/dev-lockfile.js +2 -1
  71. package/dist/server/dev-server.js +43 -12
  72. package/dist/server/headers.d.ts +8 -1
  73. package/dist/server/headers.js +8 -1
  74. package/dist/server/instrumentation-runtime.d.ts +6 -0
  75. package/dist/server/instrumentation-runtime.js +8 -0
  76. package/dist/server/isr-decision.d.ts +79 -0
  77. package/dist/server/isr-decision.js +70 -0
  78. package/dist/server/metadata-route-response.js +5 -3
  79. package/dist/server/middleware-runtime.d.ts +13 -0
  80. package/dist/server/middleware-runtime.js +11 -7
  81. package/dist/server/middleware.js +1 -0
  82. package/dist/server/navigation-planner.d.ts +62 -1
  83. package/dist/server/navigation-planner.js +188 -0
  84. package/dist/server/navigation-trace.d.ts +11 -1
  85. package/dist/server/navigation-trace.js +11 -1
  86. package/dist/server/normalize-path.d.ts +0 -8
  87. package/dist/server/normalize-path.js +3 -1
  88. package/dist/server/otel-tracer-extension.d.ts +45 -0
  89. package/dist/server/otel-tracer-extension.js +89 -0
  90. package/dist/server/pages-api-route.d.ts +14 -3
  91. package/dist/server/pages-api-route.js +6 -1
  92. package/dist/server/pages-asset-tags.d.ts +15 -4
  93. package/dist/server/pages-asset-tags.js +18 -12
  94. package/dist/server/pages-data-route.js +5 -1
  95. package/dist/server/pages-node-compat.d.ts +3 -11
  96. package/dist/server/pages-node-compat.js +174 -121
  97. package/dist/server/pages-page-data.d.ts +28 -0
  98. package/dist/server/pages-page-data.js +61 -17
  99. package/dist/server/pages-page-handler.d.ts +1 -0
  100. package/dist/server/pages-page-handler.js +22 -6
  101. package/dist/server/pages-page-response.d.ts +45 -1
  102. package/dist/server/pages-page-response.js +66 -5
  103. package/dist/server/pages-readiness.d.ts +1 -1
  104. package/dist/server/pages-request-pipeline.d.ts +15 -1
  105. package/dist/server/pages-request-pipeline.js +23 -2
  106. package/dist/server/prod-server.d.ts +39 -1
  107. package/dist/server/prod-server.js +98 -34
  108. package/dist/shims/cache-runtime.js +9 -2
  109. package/dist/shims/dynamic-preload-chunks.d.ts +8 -0
  110. package/dist/shims/dynamic-preload-chunks.js +77 -0
  111. package/dist/shims/dynamic.d.ts +4 -0
  112. package/dist/shims/dynamic.js +4 -2
  113. package/dist/shims/error-boundary.d.ts +4 -4
  114. package/dist/shims/error.js +37 -11
  115. package/dist/shims/fetch-cache.d.ts +9 -1
  116. package/dist/shims/fetch-cache.js +11 -1
  117. package/dist/shims/head.js +6 -1
  118. package/dist/shims/headers.d.ts +16 -2
  119. package/dist/shims/headers.js +37 -1
  120. package/dist/shims/image-config.js +7 -1
  121. package/dist/shims/internal/app-route-detection.d.ts +6 -3
  122. package/dist/shims/internal/app-route-detection.js +10 -6
  123. package/dist/shims/internal/app-router-context.d.ts +5 -0
  124. package/dist/shims/metadata.d.ts +6 -2
  125. package/dist/shims/metadata.js +32 -14
  126. package/dist/shims/navigation.d.ts +7 -16
  127. package/dist/shims/navigation.js +33 -16
  128. package/dist/shims/router.js +28 -1
  129. package/dist/shims/script-nonce-context.d.ts +1 -1
  130. package/dist/shims/script-nonce-context.js +11 -3
  131. package/dist/shims/server.d.ts +17 -1
  132. package/dist/shims/server.js +31 -6
  133. package/dist/shims/slot.js +1 -1
  134. package/dist/shims/unified-request-context.js +1 -0
  135. package/dist/typegen.js +1 -0
  136. package/dist/utils/client-build-manifest.js +15 -5
  137. package/dist/utils/client-runtime-metadata.d.ts +45 -0
  138. package/dist/utils/client-runtime-metadata.js +63 -0
  139. package/dist/utils/hash.d.ts +17 -1
  140. package/dist/utils/hash.js +36 -1
  141. package/dist/utils/lazy-chunks.d.ts +27 -1
  142. package/dist/utils/lazy-chunks.js +65 -1
  143. package/dist/utils/manifest-paths.d.ts +20 -2
  144. package/dist/utils/manifest-paths.js +38 -3
  145. package/dist/utils/path.d.ts +2 -1
  146. package/dist/utils/path.js +5 -1
  147. package/package.json +2 -2
@@ -1,4 +1,80 @@
1
+ import { createRequire } from "node:module";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { preprocessCSS } from "vite";
5
+ import { pathToFileURL } from "node:url";
1
6
  //#region src/plugins/sass.ts
7
+ /**
8
+ * Map a Next.js `sassOptions` object onto Vite's
9
+ * `css.preprocessorOptions.scss` / `.sass` shape.
10
+ *
11
+ * Next.js (webpack + sass-loader) accepts:
12
+ * - `additionalData` (or legacy `prependData`) — prepended to every source
13
+ * - `includePaths` — directories searched by `@import`
14
+ * - `loadPaths` — modern Sass equivalent of `includePaths`
15
+ * - `implementation` — Sass implementation package name (e.g. `sass-embedded`)
16
+ * - other Sass options that get forwarded as-is
17
+ *
18
+ * Reference (Next.js source — destructures the same keys before forwarding
19
+ * the rest to sass-loader):
20
+ * .nextjs-ref/packages/next/src/build/webpack/config/blocks/css/index.ts#L150-L180
21
+ * https://github.com/vercel/next.js/blob/canary/packages/next/src/build/webpack/config/blocks/css/index.ts
22
+ *
23
+ * Vite expects:
24
+ * - `additionalData` (string or function) on the preprocessor options
25
+ * - modern Sass options (`loadPaths`, `importers`, `implementation`, …)
26
+ * flattened next to `additionalData`
27
+ *
28
+ * @see https://vite.dev/config/shared-options.html#css-preprocessoroptions
29
+ */
30
+ /**
31
+ * Create a Sass `FileImporter` that resolves webpack-style tilde (`~`) imports.
32
+ *
33
+ * Next.js (via sass-loader's `webpackImporter`) supports two tilde forms:
34
+ *
35
+ * 1. `~pkg/path` — resolves `pkg/path` from `node_modules`. Used for
36
+ * third-party SCSS/CSS, e.g. `@import '~nprogress/nprogress.css'`.
37
+ *
38
+ * 2. `~/path` — resolves relative to the **project root** (the `~` acts as
39
+ * an alias for the root). Used with Turbopack's `resolveAlias: { '~*': '*' }`
40
+ * convention, e.g. `@use '~/styles/variables' as *`.
41
+ *
42
+ * Vite's built-in Sass resolver does not strip the `~` prefix, so any SCSS
43
+ * that uses tilde imports fails with "Can't find stylesheet to import" errors.
44
+ * This `FileImporter` runs before Vite's internal importer (added at the end
45
+ * of `importers[]` in the vite:css plugin) and canonicalises tilde URLs so
46
+ * Sass can load them from the filesystem.
47
+ *
48
+ * The returned object implements the modern Sass `FileImporter` interface:
49
+ * `findFileUrl` returns a `file://` URL and Sass automatically handles partial
50
+ * resolution (`_variables.scss` for `variables`), index files, and extensions.
51
+ *
52
+ * @param root - Absolute path to the Vite project root (used as the base for
53
+ * `~/path` resolution and for locating `node_modules`).
54
+ */
55
+ function createSassTildeImporter(root) {
56
+ const rootBaseUrl = pathToFileURL(root.endsWith("/") ? root : root + "/");
57
+ const nodeModulesBaseUrl = pathToFileURL(path.join(root, "node_modules") + "/");
58
+ return { findFileUrl(url) {
59
+ if (!url.startsWith("~")) return null;
60
+ const stripped = url.slice(1);
61
+ if (stripped.startsWith("/")) return new URL(stripped.slice(1), rootBaseUrl);
62
+ if (!stripped) return null;
63
+ const simpleResolved = new URL(stripped, nodeModulesBaseUrl);
64
+ const pkgName = stripped.startsWith("@") ? stripped.split("/").slice(0, 2).join("/") : stripped.split("/")[0] ?? "";
65
+ const directPkgDir = path.join(root, "node_modules", pkgName);
66
+ if (pkgName && fs.existsSync(directPkgDir)) return simpleResolved;
67
+ const req = createRequire(path.join(root, "package.json"));
68
+ try {
69
+ const pkgJsonPath = req.resolve(`${pkgName}/package.json`);
70
+ const pkgDir = path.dirname(pkgJsonPath);
71
+ const afterPkg = stripped.startsWith("@") ? stripped.split("/").slice(2).join("/") : stripped.split("/").slice(1).join("/");
72
+ return pathToFileURL(afterPkg ? path.join(pkgDir, afterPkg) : pkgDir);
73
+ } catch {
74
+ return null;
75
+ }
76
+ } };
77
+ }
2
78
  function buildSassPreprocessorOptions(sassOptions) {
3
79
  if (!sassOptions || typeof sassOptions !== "object") return void 0;
4
80
  const { prependData, additionalData, includePaths, loadPaths, ...rest } = sassOptions;
@@ -16,5 +92,177 @@ function buildSassPreprocessorOptions(sassOptions) {
16
92
  if (Object.keys(out).length === 0) return void 0;
17
93
  return out;
18
94
  }
95
+ /**
96
+ * Sort key comparator used by postcss-modules' FileSystemLoader to determine
97
+ * the order in which dependency CSS is prepended to the output.
98
+ * Mirrors the original `traceKeySorter` from postcss-modules source.
99
+ */
100
+ function traceKeySorter(a, b) {
101
+ if (a.length < b.length) return a < b.substring(0, a.length) ? -1 : 1;
102
+ if (a.length > b.length) return a.substring(0, b.length) <= b ? -1 : 1;
103
+ return a < b ? -1 : 1;
104
+ }
105
+ /**
106
+ * Mirrors Vite's internal `cssModuleRE` (`/\.module\.(css|less|sass|scss|…)/`).
107
+ *
108
+ * postcss-modules treats *every* `composes: x from './file'` dependency as a
109
+ * CSS module regardless of its filename, but Vite's pipeline only applies
110
+ * CSS-module scoping to `*.module.*` files. When a dependency is not named
111
+ * `*.module.*` (e.g. `composes: x from './plain.css'`), we hand
112
+ * `preprocessCSS` a virtual `*.module.*` filename (same directory, so Sass
113
+ * imports and relative resolution are unaffected) so the dependency's classes
114
+ * are scoped and its export tokens extracted — matching what postcss-modules'
115
+ * built-in `FileSystemLoader` did.
116
+ */
117
+ const CSS_MODULE_RE = /\.module\.\w+$/;
118
+ /**
119
+ * Sass-aware replacement for postcss-modules' `FileSystemLoader`.
120
+ *
121
+ * Implements the same constructor + `fetch` + `finalSource` interface that
122
+ * postcss-modules calls when resolving `composes: className from 'file'`
123
+ * dependencies.
124
+ *
125
+ * For every dependency file, Vite's `preprocessCSS` is used so that:
126
+ * - `.scss`/`.sass` files are compiled through Sass *before* CSS-module
127
+ * scoping runs (fixing the "Invalid empty selector" LightningCSS crash).
128
+ * - `.module.css` and `.module.scss` files have their class names scoped and
129
+ * export tokens extracted in exactly the same way as the top-level file.
130
+ *
131
+ * Failure handling: a missing Sass implementation is downgraded to a logged
132
+ * warning and an empty token map so the build continues (class composition
133
+ * will be incomplete); any other preprocessing error (e.g. a syntax error in
134
+ * the composed dependency) propagates and fails the build, matching the
135
+ * built-in `FileSystemLoader`. When no resolved config has been bound —
136
+ * only reachable if the Loader is used outside vinext's `configResolved`
137
+ * wiring — the Loader silently returns an empty token map.
138
+ *
139
+ * Recursion boundary: nested `composes` chains are handled by delegating to
140
+ * `preprocessCSS`, which re-applies postcss-modules (and therefore this
141
+ * Loader) for each dependency — every composed subtree gets its own inner
142
+ * Loader instance rather than sharing this instance's `tokensByFile`/
143
+ * `sources` state. Two intentional consequences, versus the built-in
144
+ * single-loader recursion:
145
+ * - A dependency reached from two sibling `composes` branches is inlined
146
+ * once per subtree; the duplicate identical rules are collapsed by
147
+ * LightningCSS during minification (bloat-free in practice, but the
148
+ * pre-minification CSS differs from the built-in loader's single pass).
149
+ * - Circular `composes` chains are not short-circuited across the
150
+ * `preprocessCSS` boundary (the built-in loader's shared cache caught
151
+ * them). Cycles are invalid CSS-module inputs — webpack/Next.js errors on
152
+ * them too — so no cycle bookkeeping is layered on here.
153
+ *
154
+ * Not exported directly: postcss-modules constructs the Loader itself with a
155
+ * fixed `(root, plugins, fileResolve)` signature, so the resolved Vite config
156
+ * cannot be a constructor argument. `createSassAwareFileSystemLoader` returns
157
+ * a subclass with the config bound per factory call (i.e. per vinext plugin
158
+ * instance) instead of a module-level singleton, so multiple builds in one
159
+ * process (monorepos, programmatic multi-build runners) each preprocess
160
+ * `composes` dependencies with their own root/sass options/scoped-name
161
+ * generator.
162
+ */
163
+ var SassAwareFileSystemLoader = class {
164
+ root;
165
+ fileResolve;
166
+ sources;
167
+ traces;
168
+ importNr;
169
+ tokensByFile;
170
+ constructor(root, _plugins, fileResolve) {
171
+ if (root === "/" && process.platform === "win32") {
172
+ const cwdDrive = process.cwd().slice(0, 3);
173
+ if (!/^[A-Za-z]:\\$/.test(cwdDrive)) throw new Error(`Failed to obtain root from "${process.cwd()}".`);
174
+ root = cwdDrive;
175
+ }
176
+ this.root = root;
177
+ this.fileResolve = fileResolve;
178
+ this.sources = {};
179
+ this.traces = {};
180
+ this.importNr = 0;
181
+ this.tokensByFile = {};
182
+ }
183
+ /**
184
+ * The resolved Vite config used to preprocess `composes` dependencies.
185
+ * The base implementation has no config (`fetch` silently returns empty
186
+ * tokens); `createSassAwareFileSystemLoader` overrides this with the
187
+ * config bound to that factory call.
188
+ */
189
+ getResolvedConfig() {
190
+ return null;
191
+ }
192
+ async fetch(_newPath, relativeTo, _trace) {
193
+ const newPath = _newPath.replace(/^["']|["']$/g, "");
194
+ const trace = _trace ?? String.fromCharCode(this.importNr++);
195
+ const useFileResolve = typeof this.fileResolve === "function";
196
+ const fileResolvedPath = useFileResolve ? await this.fileResolve(newPath, relativeTo) : void 0;
197
+ if (fileResolvedPath !== void 0 && !path.isAbsolute(fileResolvedPath)) throw new Error("The returned path from the \"fileResolve\" option must be absolute.");
198
+ const relativeDir = path.dirname(relativeTo);
199
+ const fileRelativePath = fileResolvedPath ?? (() => {
200
+ let resolved = path.resolve(path.resolve(this.root, relativeDir), newPath);
201
+ if (!useFileResolve && newPath[0] !== "." && !path.isAbsolute(newPath)) try {
202
+ resolved = createRequire(import.meta.url).resolve(newPath);
203
+ } catch {}
204
+ return resolved;
205
+ })();
206
+ const cached = this.tokensByFile[fileRelativePath];
207
+ if (cached) return cached;
208
+ const config = this.getResolvedConfig();
209
+ const rawSource = await fs.promises.readFile(fileRelativePath, "utf-8");
210
+ if (config) try {
211
+ const ext = path.extname(fileRelativePath);
212
+ const result = await preprocessCSS(rawSource, CSS_MODULE_RE.test(fileRelativePath) ? fileRelativePath : ext === "" ? `${fileRelativePath}.module.css` : `${fileRelativePath.slice(0, -ext.length)}.module${ext}`, config);
213
+ const exportTokens = result.modules ?? {};
214
+ this.sources[fileRelativePath] = result.code;
215
+ this.traces[trace] = fileRelativePath;
216
+ this.tokensByFile[fileRelativePath] = exportTokens;
217
+ return exportTokens;
218
+ } catch (error) {
219
+ const message = error instanceof Error ? error.message : String(error);
220
+ if (!message.includes("Preprocessor dependency")) throw error;
221
+ config.logger.warn(`[vinext] Failed to preprocess \`composes\` dependency ${fileRelativePath}: ${message}. Classes composed from this file will be missing from the build output.`);
222
+ }
223
+ this.sources[fileRelativePath] = "";
224
+ this.traces[trace] = fileRelativePath;
225
+ this.tokensByFile[fileRelativePath] = {};
226
+ return {};
227
+ }
228
+ get finalSource() {
229
+ const { traces, sources } = this;
230
+ const written = /* @__PURE__ */ new Set();
231
+ return Object.keys(traces).sort(traceKeySorter).map((key) => {
232
+ const filename = traces[key];
233
+ if (!filename || written.has(filename)) return null;
234
+ written.add(filename);
235
+ return sources[filename];
236
+ }).join("");
237
+ }
238
+ };
239
+ /**
240
+ * Create a per-build binding of {@link SassAwareFileSystemLoader}.
241
+ *
242
+ * Returns:
243
+ * - `Loader` — a class to inject as postcss-modules' `css.modules.Loader`
244
+ * option. postcss-modules instantiates it with the fixed
245
+ * `(root, plugins, fileResolve)` signature, so the resolved Vite config is
246
+ * captured in this factory's closure rather than passed to the constructor.
247
+ * - `setResolvedConfig` — called from vinext's `configResolved` hook to bind
248
+ * that build's resolved config.
249
+ *
250
+ * One binding is created per vinext plugin instance, so concurrent or
251
+ * back-to-back builds in a single process never observe another build's
252
+ * config (root, sass options, `generateScopedName`, logger, …).
253
+ */
254
+ function createSassAwareFileSystemLoader() {
255
+ let resolvedConfig = null;
256
+ return {
257
+ Loader: class extends SassAwareFileSystemLoader {
258
+ getResolvedConfig() {
259
+ return resolvedConfig;
260
+ }
261
+ },
262
+ setResolvedConfig(config) {
263
+ resolvedConfig = config;
264
+ }
265
+ };
266
+ }
19
267
  //#endregion
20
- export { buildSassPreprocessorOptions };
268
+ export { buildSassPreprocessorOptions, createSassAwareFileSystemLoader, createSassTildeImporter };
@@ -0,0 +1,15 @@
1
+ import { Plugin } from "vite";
2
+
3
+ //#region src/plugins/wasm-module-import.d.ts
4
+ /**
5
+ * vinext:wasm-module-import — handle `import x from '*.wasm?module'`.
6
+ *
7
+ * Resolutions marked external by Vite or a target adapter are preserved. For
8
+ * non-external resolutions, this plugin reads the WASM file, inlines it as
9
+ * base64, and exports a compiled WebAssembly.Module.
10
+ *
11
+ * Fixes #1351.
12
+ */
13
+ declare function createWasmModuleImportPlugin(): Plugin;
14
+ //#endregion
15
+ export { createWasmModuleImportPlugin };
@@ -0,0 +1,50 @@
1
+ import { stripViteModuleQuery } from "../utils/path.js";
2
+ import fs from "node:fs";
3
+ //#region src/plugins/wasm-module-import.ts
4
+ /**
5
+ * vinext:wasm-module-import — handle `import x from '*.wasm?module'`.
6
+ *
7
+ * Resolutions marked external by Vite or a target adapter are preserved. For
8
+ * non-external resolutions, this plugin reads the WASM file, inlines it as
9
+ * base64, and exports a compiled WebAssembly.Module.
10
+ *
11
+ * Fixes #1351.
12
+ */
13
+ function createWasmModuleImportPlugin() {
14
+ return {
15
+ name: "vinext:wasm-module-import",
16
+ enforce: "pre",
17
+ resolveId: {
18
+ filter: { id: /\.wasm\?module$/ },
19
+ async handler(source, importer) {
20
+ if (this.environment?.name === "client") return null;
21
+ if ((importer ? (importer.startsWith("\0") ? importer.slice(1) : importer).split("?")[0] : "").includes("@vercel/og")) return null;
22
+ const resolved = await this.resolve(source, importer, { skipSelf: true });
23
+ if (!resolved) return null;
24
+ if (resolved.external) return resolved;
25
+ return `\0vinext-wasm-module:${stripViteModuleQuery(resolved.id)}`;
26
+ }
27
+ },
28
+ load: {
29
+ filter: { id: /^\u0000vinext-wasm-module:/ },
30
+ handler(id) {
31
+ const filePath = id.replace(/^\u0000vinext-wasm-module:/, "");
32
+ this.addWatchFile(filePath);
33
+ let bytes;
34
+ try {
35
+ bytes = fs.readFileSync(filePath);
36
+ } catch {
37
+ return this.error(`[vinext] Could not read WASM file: ${filePath}`);
38
+ }
39
+ const base64 = bytes.toString("base64");
40
+ return [
41
+ `const _b64 = ${JSON.stringify(base64)};`,
42
+ `const _buf = Uint8Array.from(atob(_b64), c => c.charCodeAt(0));`,
43
+ `export default await WebAssembly.compile(_buf.buffer);`
44
+ ].join("\n");
45
+ }
46
+ }
47
+ };
48
+ }
49
+ //#endregion
50
+ export { createWasmModuleImportPlugin };
@@ -276,6 +276,28 @@ declare function buildAppRouteGraph(appDir: string, matcher: ValidFileMatcher):
276
276
  routeManifest: RouteManifest;
277
277
  }>;
278
278
  declare function computeRootParamNames(routeSegments: readonly string[], layoutTreePositions: readonly number[]): string[];
279
+ /**
280
+ * Find the best route to attach a sibling intercept to, given the directory
281
+ * that contains the interception marker.
282
+ *
283
+ * 1. Exact hit: a route whose page/handler lives directly in `dir`.
284
+ * 2. Subtree hit: shallowest route whose page lives anywhere under `dir`
285
+ * (handles catch-all routes like `/templates/:catchAll+`).
286
+ * 3. Ancestor walk: walk up the directory tree toward `appDir` looking for
287
+ * any of the above. This handles the case where the marker directory has
288
+ * no sibling pages at all (e.g. `deep/path/(...)target` with no
289
+ * `deep/path/page.tsx`).
290
+ *
291
+ * All comparisons happen in forward-slash space: `appDir` is forward-slash
292
+ * (normalized once in the config hook), but `dir` and route file paths
293
+ * descend through native `path.join`/`path.dirname`, which reintroduce
294
+ * backslashes on Windows. Without normalizing, the `current === appDir`
295
+ * termination never fires there and the walk overshoots the app root.
296
+ * `routesByDir` keys must be forward-slash dirnames of the route file paths.
297
+ *
298
+ * Exported for tests.
299
+ */
300
+ declare function findOwnerRouteForDir(dir: string, appDir: string, routes: readonly AppRouteGraphRoute[], routesByDir: Map<string, AppRouteGraphRoute>): AppRouteGraphRoute | null;
279
301
  /**
280
302
  * Convert filesystem path segments to URL route parts, skipping invisible segments
281
303
  * (route groups, @slots, ".") and converting dynamic segment syntax to Express-style
@@ -310,4 +332,4 @@ declare function computeAppRouteStaticSiblings(allRoutes: readonly {
310
332
  patternParts?: readonly string[] | null;
311
333
  }): string[];
312
334
  //#endregion
313
- export { AppRoute, AppRouteGraphRoute, AppRouteSemanticIds, GraphVersion, RootBoundaryId, RouteManifest, RouteManifestInterception, RouteManifestRootBoundary, RouteManifestRoute, RouteManifestSlotBinding, StaticSegmentGraph, buildAppRouteGraph, computeAppRouteStaticSiblings, computeRootParamNames, convertSegmentsToRouteParts, isInvisibleSegment };
335
+ export { AppRoute, AppRouteGraphRoute, AppRouteSemanticIds, GraphVersion, RootBoundaryId, RouteManifest, RouteManifestInterception, RouteManifestRootBoundary, RouteManifestRoute, RouteManifestSlotBinding, StaticSegmentGraph, buildAppRouteGraph, computeAppRouteStaticSiblings, computeRootParamNames, convertSegmentsToRouteParts, findOwnerRouteForDir, isInvisibleSegment };
@@ -1,3 +1,4 @@
1
+ import { normalizePathSeparators } from "../utils/path.js";
1
2
  import { compareRoutes, decodeRouteSegment, isInvisibleSegment } from "./utils.js";
2
3
  import { findFileWithExts, scanWithExtensions } from "./file-matcher.js";
3
4
  import { validateRoutePatterns } from "./route-validation.js";
@@ -485,7 +486,7 @@ function discoverSlotSubRoutes(routes, matcher, ghostParents = []) {
485
486
  const existingRoute = routesByPattern.get(pattern);
486
487
  if (existingRoute) {
487
488
  if (existingRoute.routePath && !existingRoute.pagePath) throw new Error(`You cannot have two routes that resolve to the same path ("${pattern}").`);
488
- applySlotSubPages(existingRoute, slotPages, rawSegments);
489
+ if (urlParts.length > 0) applySlotSubPages(existingRoute, slotPages, rawSegments);
489
490
  continue;
490
491
  }
491
492
  const syntheticParts = [...parentRoute.patternParts, ...urlParts];
@@ -1026,6 +1027,34 @@ function patternsStructurallyEquivalent(a, b) {
1026
1027
  return true;
1027
1028
  }
1028
1029
  /**
1030
+ * Find a page file at the root URL level of a parallel slot directory, including
1031
+ * through transparent route-group subdirectories (e.g. `@slot/(group)/page.tsx`
1032
+ * is equivalent to `@slot/page.tsx` since `(group)` is invisible in the URL).
1033
+ *
1034
+ * Returns the absolute page path, or null if no root-level page is found.
1035
+ *
1036
+ * Only descends into route-group directories (those whose name starts with `(`
1037
+ * and ends with `)`). Dynamic segments, regular named dirs, and `@slot` dirs
1038
+ * are not transparent and are therefore not searched.
1039
+ */
1040
+ function findSlotRootPage(slotDir, matcher) {
1041
+ const directPage = findFile(slotDir, "page", matcher);
1042
+ if (directPage) return directPage;
1043
+ let entries;
1044
+ try {
1045
+ entries = fs.readdirSync(slotDir, { withFileTypes: true });
1046
+ } catch {
1047
+ return null;
1048
+ }
1049
+ for (const entry of entries) {
1050
+ if (!entry.isDirectory()) continue;
1051
+ if (!entry.name.startsWith("(") || !entry.name.endsWith(")")) continue;
1052
+ const found = findSlotRootPage(path.join(slotDir, entry.name), matcher);
1053
+ if (found) return found;
1054
+ }
1055
+ return null;
1056
+ }
1057
+ /**
1029
1058
  * Discover parallel route slots (@team, @analytics, etc.) in a directory.
1030
1059
  * Returns a ParallelSlot for each @-prefixed subdirectory that has a page or default component.
1031
1060
  */
@@ -1038,7 +1067,7 @@ function discoverParallelSlots(dir, appDir, matcher) {
1038
1067
  if (entry.name === "@children") continue;
1039
1068
  const slotName = entry.name.slice(1);
1040
1069
  const slotDir = path.join(dir, entry.name);
1041
- const pagePath = findFile(slotDir, "page", matcher);
1070
+ const pagePath = findSlotRootPage(slotDir, matcher);
1042
1071
  const defaultPath = findFile(slotDir, "default", matcher);
1043
1072
  const interceptingRoutes = discoverInterceptingRoutes(slotDir, dir, appDir, matcher);
1044
1073
  if (!pagePath && !defaultPath && interceptingRoutes.length === 0) continue;
@@ -1127,7 +1156,7 @@ function discoverSiblingInterceptingRoutes(routes, appDir, matcher) {
1127
1156
  for (const route of routes) {
1128
1157
  const filePath = route.pagePath ?? route.routePath;
1129
1158
  if (!filePath) continue;
1130
- const routeDir = path.dirname(filePath);
1159
+ const routeDir = normalizePathSeparators(path.dirname(filePath));
1131
1160
  if (!routesByDir.has(routeDir)) routesByDir.set(routeDir, route);
1132
1161
  }
1133
1162
  function walk(dir) {
@@ -1171,22 +1200,32 @@ function discoverSiblingInterceptingRoutes(routes, appDir, matcher) {
1171
1200
  * any of the above. This handles the case where the marker directory has
1172
1201
  * no sibling pages at all (e.g. `deep/path/(...)target` with no
1173
1202
  * `deep/path/page.tsx`).
1203
+ *
1204
+ * All comparisons happen in forward-slash space: `appDir` is forward-slash
1205
+ * (normalized once in the config hook), but `dir` and route file paths
1206
+ * descend through native `path.join`/`path.dirname`, which reintroduce
1207
+ * backslashes on Windows. Without normalizing, the `current === appDir`
1208
+ * termination never fires there and the walk overshoots the app root.
1209
+ * `routesByDir` keys must be forward-slash dirnames of the route file paths.
1210
+ *
1211
+ * Exported for tests.
1174
1212
  */
1175
1213
  function findOwnerRouteForDir(dir, appDir, routes, routesByDir) {
1176
- let current = dir;
1214
+ const appRoot = normalizePathSeparators(appDir);
1215
+ let current = normalizePathSeparators(dir);
1177
1216
  while (true) {
1178
1217
  const exact = routesByDir.get(current);
1179
1218
  if (exact) return exact;
1180
- const currentWithSep = current + path.sep;
1219
+ const currentWithSep = current + "/";
1181
1220
  let best = null;
1182
1221
  for (const route of routes) {
1183
1222
  const filePath = route.pagePath ?? route.routePath;
1184
1223
  if (!filePath) continue;
1185
- if (!filePath.startsWith(currentWithSep)) continue;
1224
+ if (!normalizePathSeparators(filePath).startsWith(currentWithSep)) continue;
1186
1225
  if (!best || route.patternParts.length < best.patternParts.length) best = route;
1187
1226
  }
1188
1227
  if (best) return best;
1189
- if (current === appDir) break;
1228
+ if (current === appRoot) break;
1190
1229
  const parent = path.dirname(current);
1191
1230
  if (parent === current) break;
1192
1231
  current = parent;
@@ -1446,4 +1485,4 @@ function computeAppRouteStaticSiblings(allRoutes, matchedRoute) {
1446
1485
  return Array.from(siblings);
1447
1486
  }
1448
1487
  //#endregion
1449
- export { buildAppRouteGraph, computeAppRouteStaticSiblings, computeRootParamNames, convertSegmentsToRouteParts, isInvisibleSegment };
1488
+ export { buildAppRouteGraph, computeAppRouteStaticSiblings, computeRootParamNames, convertSegmentsToRouteParts, findOwnerRouteForDir, isInvisibleSegment };
@@ -69,7 +69,7 @@ function findFileWithExtensions(basePath, matcher) {
69
69
  */
70
70
  function findFileWithExts(dir, name, matcher) {
71
71
  for (const ext of matcher.dottedExtensions) {
72
- const filePath = path.join(dir, name + ext);
72
+ const filePath = path.posix.join(dir, name + ext);
73
73
  if (existsSync(filePath)) return filePath;
74
74
  }
75
75
  return null;