vinext 0.1.0 → 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 (205) hide show
  1. package/README.md +2 -5
  2. package/dist/build/assets-ignore.d.ts +32 -0
  3. package/dist/build/assets-ignore.js +48 -0
  4. package/dist/build/client-build-config.d.ts +33 -1
  5. package/dist/build/client-build-config.js +66 -1
  6. package/dist/check.js +4 -3
  7. package/dist/cli.js +2 -0
  8. package/dist/client/navigation-runtime.d.ts +11 -2
  9. package/dist/client/navigation-runtime.js +1 -1
  10. package/dist/client/vinext-next-data.d.ts +2 -1
  11. package/dist/client/window-next.d.ts +6 -4
  12. package/dist/config/config-matchers.d.ts +31 -5
  13. package/dist/config/config-matchers.js +50 -3
  14. package/dist/config/next-config.d.ts +29 -3
  15. package/dist/config/next-config.js +32 -2
  16. package/dist/deploy.js +47 -304
  17. package/dist/entries/app-rsc-entry.d.ts +8 -2
  18. package/dist/entries/app-rsc-entry.js +61 -5
  19. package/dist/entries/app-rsc-manifest.js +20 -2
  20. package/dist/entries/pages-client-entry.js +1 -1
  21. package/dist/entries/pages-server-entry.js +16 -7
  22. package/dist/index.d.ts +0 -2
  23. package/dist/index.js +233 -280
  24. package/dist/plugins/dynamic-preload-metadata.d.ts +13 -0
  25. package/dist/plugins/dynamic-preload-metadata.js +415 -0
  26. package/dist/plugins/og-assets.js +2 -2
  27. package/dist/plugins/optimize-imports.d.ts +8 -4
  28. package/dist/plugins/optimize-imports.js +16 -12
  29. package/dist/plugins/postcss.js +18 -14
  30. package/dist/plugins/require-context.d.ts +6 -0
  31. package/dist/plugins/require-context.js +184 -0
  32. package/dist/plugins/sass.d.ts +53 -24
  33. package/dist/plugins/sass.js +249 -1
  34. package/dist/plugins/wasm-module-import.d.ts +15 -0
  35. package/dist/plugins/wasm-module-import.js +50 -0
  36. package/dist/routing/app-route-graph.d.ts +35 -2
  37. package/dist/routing/app-route-graph.js +179 -8
  38. package/dist/routing/file-matcher.js +1 -1
  39. package/dist/routing/route-pattern.d.ts +2 -1
  40. package/dist/routing/route-pattern.js +16 -1
  41. package/dist/server/api-handler.js +4 -0
  42. package/dist/server/app-browser-entry.js +155 -215
  43. package/dist/server/app-browser-error.d.ts +4 -1
  44. package/dist/server/app-browser-error.js +7 -1
  45. package/dist/server/app-browser-history-controller.d.ts +104 -0
  46. package/dist/server/app-browser-history-controller.js +210 -0
  47. package/dist/server/app-browser-interception-context.d.ts +2 -1
  48. package/dist/server/app-browser-interception-context.js +15 -2
  49. package/dist/server/app-browser-navigation-controller.d.ts +13 -2
  50. package/dist/server/app-browser-navigation-controller.js +83 -4
  51. package/dist/server/app-browser-popstate.d.ts +12 -3
  52. package/dist/server/app-browser-popstate.js +19 -4
  53. package/dist/server/app-browser-rsc-redirect.d.ts +11 -2
  54. package/dist/server/app-browser-rsc-redirect.js +30 -8
  55. package/dist/server/app-browser-state.d.ts +3 -0
  56. package/dist/server/app-browser-state.js +10 -10
  57. package/dist/server/app-browser-visible-commit.js +10 -8
  58. package/dist/server/app-fallback-renderer.d.ts +2 -1
  59. package/dist/server/app-fallback-renderer.js +3 -1
  60. package/dist/server/app-history-state.d.ts +45 -1
  61. package/dist/server/app-history-state.js +109 -1
  62. package/dist/server/app-middleware.js +1 -0
  63. package/dist/server/app-optimistic-routing.js +22 -1
  64. package/dist/server/app-page-boundary-render.d.ts +2 -1
  65. package/dist/server/app-page-boundary-render.js +45 -21
  66. package/dist/server/app-page-cache.js +9 -7
  67. package/dist/server/app-page-dispatch.d.ts +14 -0
  68. package/dist/server/app-page-dispatch.js +21 -6
  69. package/dist/server/app-page-element-builder.d.ts +23 -2
  70. package/dist/server/app-page-element-builder.js +58 -17
  71. package/dist/server/app-page-execution.d.ts +1 -1
  72. package/dist/server/app-page-execution.js +32 -17
  73. package/dist/server/app-page-render.d.ts +7 -1
  74. package/dist/server/app-page-render.js +11 -16
  75. package/dist/server/app-page-request.d.ts +9 -6
  76. package/dist/server/app-page-request.js +14 -10
  77. package/dist/server/app-page-response.d.ts +2 -2
  78. package/dist/server/app-page-response.js +2 -2
  79. package/dist/server/app-page-route-wiring.d.ts +3 -1
  80. package/dist/server/app-page-route-wiring.js +10 -8
  81. package/dist/server/app-page-stream.d.ts +37 -7
  82. package/dist/server/app-page-stream.js +36 -6
  83. package/dist/server/app-pages-bridge.d.ts +16 -0
  84. package/dist/server/app-pages-bridge.js +23 -3
  85. package/dist/server/app-route-handler-cache.d.ts +1 -0
  86. package/dist/server/app-route-handler-cache.js +1 -0
  87. package/dist/server/app-route-handler-dispatch.d.ts +1 -0
  88. package/dist/server/app-route-handler-dispatch.js +2 -0
  89. package/dist/server/app-route-handler-execution.d.ts +1 -0
  90. package/dist/server/app-route-handler-execution.js +1 -0
  91. package/dist/server/app-route-handler-response.js +11 -10
  92. package/dist/server/app-route-handler-runtime.d.ts +1 -0
  93. package/dist/server/app-route-handler-runtime.js +15 -3
  94. package/dist/server/app-rsc-handler.d.ts +1 -0
  95. package/dist/server/app-rsc-handler.js +5 -4
  96. package/dist/server/app-rsc-response-finalizer.js +1 -1
  97. package/dist/server/app-rsc-route-matching.d.ts +20 -1
  98. package/dist/server/app-rsc-route-matching.js +29 -4
  99. package/dist/server/app-server-action-execution.d.ts +22 -1
  100. package/dist/server/app-server-action-execution.js +73 -12
  101. package/dist/server/app-ssr-entry.d.ts +6 -0
  102. package/dist/server/app-ssr-entry.js +19 -3
  103. package/dist/server/app-ssr-stream.js +9 -1
  104. package/dist/server/dev-lockfile.js +2 -1
  105. package/dist/server/dev-server.d.ts +1 -1
  106. package/dist/server/dev-server.js +97 -43
  107. package/dist/server/headers.d.ts +8 -1
  108. package/dist/server/headers.js +8 -1
  109. package/dist/server/instrumentation-runtime.d.ts +6 -0
  110. package/dist/server/instrumentation-runtime.js +8 -0
  111. package/dist/server/isr-cache.d.ts +37 -1
  112. package/dist/server/isr-cache.js +85 -1
  113. package/dist/server/isr-decision.d.ts +79 -0
  114. package/dist/server/isr-decision.js +70 -0
  115. package/dist/server/metadata-route-response.js +5 -3
  116. package/dist/server/middleware-runtime.d.ts +13 -0
  117. package/dist/server/middleware-runtime.js +11 -7
  118. package/dist/server/middleware.js +1 -0
  119. package/dist/server/navigation-planner.d.ts +62 -1
  120. package/dist/server/navigation-planner.js +193 -3
  121. package/dist/server/navigation-trace.d.ts +12 -2
  122. package/dist/server/navigation-trace.js +11 -1
  123. package/dist/server/normalize-path.d.ts +0 -8
  124. package/dist/server/normalize-path.js +3 -1
  125. package/dist/server/otel-tracer-extension.d.ts +45 -0
  126. package/dist/server/otel-tracer-extension.js +89 -0
  127. package/dist/server/pages-api-route.d.ts +14 -3
  128. package/dist/server/pages-api-route.js +6 -1
  129. package/dist/server/pages-asset-tags.d.ts +15 -4
  130. package/dist/server/pages-asset-tags.js +18 -12
  131. package/dist/server/pages-data-route.js +5 -1
  132. package/dist/server/pages-node-compat.d.ts +5 -11
  133. package/dist/server/pages-node-compat.js +175 -118
  134. package/dist/server/pages-page-data.d.ts +38 -7
  135. package/dist/server/pages-page-data.js +64 -18
  136. package/dist/server/pages-page-handler.d.ts +10 -2
  137. package/dist/server/pages-page-handler.js +49 -20
  138. package/dist/server/pages-page-response.d.ts +55 -2
  139. package/dist/server/pages-page-response.js +74 -6
  140. package/dist/server/pages-readiness.d.ts +36 -0
  141. package/dist/server/pages-readiness.js +21 -0
  142. package/dist/server/pages-request-pipeline.d.ts +113 -0
  143. package/dist/server/pages-request-pipeline.js +230 -0
  144. package/dist/server/pages-revalidate.d.ts +15 -0
  145. package/dist/server/pages-revalidate.js +19 -0
  146. package/dist/server/prod-server.d.ts +45 -3
  147. package/dist/server/prod-server.js +182 -234
  148. package/dist/server/socket-error-backstop.d.ts +19 -1
  149. package/dist/server/socket-error-backstop.js +77 -4
  150. package/dist/shims/app-router-scroll.js +22 -4
  151. package/dist/shims/cache-runtime.js +39 -2
  152. package/dist/shims/dynamic-preload-chunks.d.ts +8 -0
  153. package/dist/shims/dynamic-preload-chunks.js +77 -0
  154. package/dist/shims/dynamic.d.ts +4 -0
  155. package/dist/shims/dynamic.js +4 -2
  156. package/dist/shims/error-boundary.d.ts +17 -7
  157. package/dist/shims/error-boundary.js +8 -1
  158. package/dist/shims/error.js +37 -11
  159. package/dist/shims/fetch-cache.d.ts +22 -1
  160. package/dist/shims/fetch-cache.js +28 -1
  161. package/dist/shims/hash-scroll.d.ts +1 -0
  162. package/dist/shims/hash-scroll.js +3 -1
  163. package/dist/shims/head.js +6 -1
  164. package/dist/shims/headers.d.ts +16 -2
  165. package/dist/shims/headers.js +37 -1
  166. package/dist/shims/image-config.js +7 -1
  167. package/dist/shims/internal/app-route-detection.d.ts +6 -3
  168. package/dist/shims/internal/app-route-detection.js +10 -6
  169. package/dist/shims/internal/app-router-context.d.ts +5 -0
  170. package/dist/shims/internal/link-status-registry.d.ts +43 -0
  171. package/dist/shims/internal/link-status-registry.js +42 -0
  172. package/dist/shims/internal/route-pattern-for-warning.d.ts +27 -0
  173. package/dist/shims/internal/route-pattern-for-warning.js +40 -0
  174. package/dist/shims/internal/utils.d.ts +1 -0
  175. package/dist/shims/link.js +20 -6
  176. package/dist/shims/metadata.d.ts +6 -2
  177. package/dist/shims/metadata.js +32 -14
  178. package/dist/shims/navigation.d.ts +9 -18
  179. package/dist/shims/navigation.js +96 -23
  180. package/dist/shims/router-state.d.ts +1 -0
  181. package/dist/shims/router-state.js +2 -0
  182. package/dist/shims/router.d.ts +6 -3
  183. package/dist/shims/router.js +156 -22
  184. package/dist/shims/script-nonce-context.d.ts +1 -1
  185. package/dist/shims/script-nonce-context.js +11 -3
  186. package/dist/shims/server.d.ts +17 -1
  187. package/dist/shims/server.js +31 -6
  188. package/dist/shims/slot.js +1 -1
  189. package/dist/shims/unified-request-context.js +1 -0
  190. package/dist/typegen.js +1 -0
  191. package/dist/utils/client-build-manifest.d.ts +8 -1
  192. package/dist/utils/client-build-manifest.js +41 -6
  193. package/dist/utils/client-entry-manifest.d.ts +11 -0
  194. package/dist/utils/client-entry-manifest.js +29 -0
  195. package/dist/utils/client-runtime-metadata.d.ts +45 -0
  196. package/dist/utils/client-runtime-metadata.js +63 -0
  197. package/dist/utils/hash.d.ts +17 -1
  198. package/dist/utils/hash.js +36 -1
  199. package/dist/utils/lazy-chunks.d.ts +27 -1
  200. package/dist/utils/lazy-chunks.js +65 -1
  201. package/dist/utils/manifest-paths.d.ts +20 -2
  202. package/dist/utils/manifest-paths.js +38 -3
  203. package/dist/utils/path.d.ts +2 -1
  204. package/dist/utils/path.js +5 -1
  205. package/package.json +6 -2
@@ -0,0 +1,184 @@
1
+ import { forEachAstChild, hasRange, isAstRecord, nodeArray } from "./ast-utils.js";
2
+ import { parseAst } from "vite";
3
+ import MagicString from "magic-string";
4
+ //#region src/plugins/require-context.ts
5
+ const TRANSFORMABLE_EXTENSIONS = new Set([
6
+ ".js",
7
+ ".jsx",
8
+ ".ts",
9
+ ".tsx",
10
+ ".mjs",
11
+ ".cjs",
12
+ ".mts",
13
+ ".cts"
14
+ ]);
15
+ function createRequireContextPlugin() {
16
+ return {
17
+ name: "vinext:require-context",
18
+ enforce: "pre",
19
+ transform(code, id) {
20
+ if (!mayContainRequireContext(code)) return null;
21
+ const lang = langForId(id);
22
+ if (!lang) return null;
23
+ let ast;
24
+ try {
25
+ ast = parseAst(code, { lang });
26
+ } catch {
27
+ return null;
28
+ }
29
+ const calls = collectRequireContextCalls(ast);
30
+ if (calls.length === 0) return null;
31
+ const output = new MagicString(code);
32
+ for (const call of calls) output.overwrite(call.range.start, call.range.end, buildReplacement(call));
33
+ return {
34
+ code: output.toString(),
35
+ map: output.generateMap({ hires: "boundary" })
36
+ };
37
+ }
38
+ };
39
+ }
40
+ function mayContainRequireContext(code) {
41
+ return code.includes("require") && code.includes(".context");
42
+ }
43
+ function langForId(id) {
44
+ const clean = id.split("?", 1)[0];
45
+ const dot = clean.lastIndexOf(".");
46
+ if (dot < 0) return null;
47
+ const ext = clean.slice(dot).toLowerCase();
48
+ if (!TRANSFORMABLE_EXTENSIONS.has(ext)) return null;
49
+ switch (ext) {
50
+ case ".ts":
51
+ case ".cts":
52
+ case ".mts": return "ts";
53
+ case ".tsx": return "tsx";
54
+ case ".jsx": return "jsx";
55
+ default: return "jsx";
56
+ }
57
+ }
58
+ function collectRequireContextCalls(ast) {
59
+ const calls = [];
60
+ function visit(value) {
61
+ if (!isAstRecord(value)) return;
62
+ const parsed = parseRequireContextCall(value);
63
+ if (parsed) {
64
+ calls.push(parsed);
65
+ return;
66
+ }
67
+ forEachAstChild(value, visit);
68
+ }
69
+ visit(ast);
70
+ return calls;
71
+ }
72
+ function parseRequireContextCall(node) {
73
+ if (node.type !== "CallExpression" || !hasRange(node)) return null;
74
+ const callee = node.callee;
75
+ if (!isAstRecord(callee) || callee.type !== "MemberExpression" || callee.computed === true || callee.optional === true) return null;
76
+ if (!isPropertyNamed(callee.property, "context")) return null;
77
+ if (!isRequireExpression(callee.object)) return null;
78
+ const args = nodeArray(node.arguments);
79
+ const dir = stringLiteralValue(args[0]);
80
+ if (dir == null || !(dir.startsWith("./") || dir.startsWith("../"))) return null;
81
+ let recursive = true;
82
+ if (args.length >= 2) {
83
+ const value = booleanLiteralValue(args[1]);
84
+ if (value == null) return null;
85
+ recursive = value;
86
+ }
87
+ let pattern = "";
88
+ let flags = "";
89
+ if (args.length >= 3) {
90
+ const regex = regexLiteralValue(args[2]);
91
+ if (regex == null) return null;
92
+ pattern = regex.pattern;
93
+ flags = regex.flags;
94
+ } else if (args.length > 3) return null;
95
+ return {
96
+ range: node,
97
+ dir,
98
+ recursive,
99
+ pattern,
100
+ flags
101
+ };
102
+ }
103
+ function isRequireExpression(value) {
104
+ let node = value;
105
+ while (isAstRecord(node)) {
106
+ if (node.type === "Identifier") return node.name === "require";
107
+ if (node.type === "TSAsExpression" || node.type === "TSSatisfiesExpression") {
108
+ node = node.expression;
109
+ continue;
110
+ }
111
+ if (node.type === "TSNonNullExpression") {
112
+ node = node.expression;
113
+ continue;
114
+ }
115
+ if (node.type === "ParenthesizedExpression") {
116
+ node = node.expression;
117
+ continue;
118
+ }
119
+ return false;
120
+ }
121
+ return false;
122
+ }
123
+ function isPropertyNamed(value, name) {
124
+ return isAstRecord(value) && value.type === "Identifier" && value.name === name;
125
+ }
126
+ function stringLiteralValue(value) {
127
+ if (isAstRecord(value) && value.type === "Literal" && typeof value.value === "string") return value.value;
128
+ return null;
129
+ }
130
+ function booleanLiteralValue(value) {
131
+ if (isAstRecord(value) && value.type === "Literal" && typeof value.value === "boolean") return value.value;
132
+ return null;
133
+ }
134
+ function regexLiteralValue(value) {
135
+ if (!isAstRecord(value) || value.type !== "Literal") return null;
136
+ const regex = value.regex;
137
+ if (typeof regex === "object" && regex !== null && typeof regex.pattern === "string" && typeof regex.flags === "string") return {
138
+ pattern: regex.pattern,
139
+ flags: regex.flags
140
+ };
141
+ return null;
142
+ }
143
+ function buildReplacement(call) {
144
+ const globPattern = globPatternFor(call.dir, call.recursive);
145
+ const glob = `import.meta.glob(${JSON.stringify(globPattern)}, { eager: true })`;
146
+ const base = JSON.stringify(stripTrailingSlash(call.dir));
147
+ const filterFlags = call.flags.replace(/[gy]/g, "");
148
+ const regexArgs = `${JSON.stringify(call.pattern)}, ${JSON.stringify(filterFlags)}`;
149
+ return [
150
+ "(() => {",
151
+ ` const __modules = ${glob};`,
152
+ ` const __base = ${base};`,
153
+ ` const __re = ${call.pattern ? `new RegExp(${regexArgs})` : "null"};`,
154
+ " const __prefix = __base.endsWith('/') ? __base : __base + '/';",
155
+ " const __map = Object.create(null);",
156
+ " for (const __abs in __modules) {",
157
+ " if (!__abs.startsWith(__prefix)) continue;",
158
+ " const __key = './' + __abs.slice(__prefix.length);",
159
+ " if (__re && !__re.test(__key)) continue;",
160
+ " __map[__key] = __modules[__abs];",
161
+ " }",
162
+ " const __keys = Object.keys(__map).sort();",
163
+ " const __ctx = (__key) => {",
164
+ " if (__key in __map) return __map[__key];",
165
+ " const __err = new Error('Cannot find module \\'' + __key + '\\'');",
166
+ " __err.code = 'MODULE_NOT_FOUND';",
167
+ " throw __err;",
168
+ " };",
169
+ " __ctx.keys = () => __keys.slice();",
170
+ " __ctx.resolve = (__key) => __key;",
171
+ ` __ctx.id = __base;`,
172
+ " return __ctx;",
173
+ "})()"
174
+ ].join("\n");
175
+ }
176
+ function globPatternFor(dir, recursive) {
177
+ const base = stripTrailingSlash(dir);
178
+ return recursive ? `${base}/**/*` : `${base}/*`;
179
+ }
180
+ function stripTrailingSlash(value) {
181
+ return value.endsWith("/") ? value.slice(0, -1) : value;
182
+ }
183
+ //#endregion
184
+ export { createRequireContextPlugin };
@@ -1,33 +1,62 @@
1
+ import { ResolvedConfig } from "vite";
2
+
1
3
  //#region src/plugins/sass.d.ts
2
- /**
3
- * Map a Next.js `sassOptions` object onto Vite's
4
- * `css.preprocessorOptions.scss` / `.sass` shape.
5
- *
6
- * Next.js (webpack + sass-loader) accepts:
7
- * - `additionalData` (or legacy `prependData`) — prepended to every source
8
- * - `includePaths` — directories searched by `@import`
9
- * - `loadPaths` — modern Sass equivalent of `includePaths`
10
- * - `implementation` — Sass implementation package name (e.g. `sass-embedded`)
11
- * - other Sass options that get forwarded as-is
12
- *
13
- * Reference (Next.js source — destructures the same keys before forwarding
14
- * the rest to sass-loader):
15
- * .nextjs-ref/packages/next/src/build/webpack/config/blocks/css/index.ts#L150-L180
16
- * https://github.com/vercel/next.js/blob/canary/packages/next/src/build/webpack/config/blocks/css/index.ts
17
- *
18
- * Vite expects:
19
- * - `additionalData` (string or function) on the preprocessor options
20
- * - modern Sass options (`loadPaths`, `importers`, `implementation`, …)
21
- * flattened next to `additionalData`
22
- *
23
- * @see https://vite.dev/config/shared-options.html#css-preprocessoroptions
24
- */
25
4
  type AdditionalData = string | ((source: string, filename: string) => string | Promise<string>);
26
5
  type VitePreprocessorOptions = {
27
6
  additionalData?: AdditionalData;
28
7
  loadPaths?: string[];
29
8
  [key: string]: any;
30
9
  };
10
+ /**
11
+ * Create a Sass `FileImporter` that resolves webpack-style tilde (`~`) imports.
12
+ *
13
+ * Next.js (via sass-loader's `webpackImporter`) supports two tilde forms:
14
+ *
15
+ * 1. `~pkg/path` — resolves `pkg/path` from `node_modules`. Used for
16
+ * third-party SCSS/CSS, e.g. `@import '~nprogress/nprogress.css'`.
17
+ *
18
+ * 2. `~/path` — resolves relative to the **project root** (the `~` acts as
19
+ * an alias for the root). Used with Turbopack's `resolveAlias: { '~*': '*' }`
20
+ * convention, e.g. `@use '~/styles/variables' as *`.
21
+ *
22
+ * Vite's built-in Sass resolver does not strip the `~` prefix, so any SCSS
23
+ * that uses tilde imports fails with "Can't find stylesheet to import" errors.
24
+ * This `FileImporter` runs before Vite's internal importer (added at the end
25
+ * of `importers[]` in the vite:css plugin) and canonicalises tilde URLs so
26
+ * Sass can load them from the filesystem.
27
+ *
28
+ * The returned object implements the modern Sass `FileImporter` interface:
29
+ * `findFileUrl` returns a `file://` URL and Sass automatically handles partial
30
+ * resolution (`_variables.scss` for `variables`), index files, and extensions.
31
+ *
32
+ * @param root - Absolute path to the Vite project root (used as the base for
33
+ * `~/path` resolution and for locating `node_modules`).
34
+ */
35
+ declare function createSassTildeImporter(root: string): {
36
+ findFileUrl(url: string): URL | null;
37
+ };
31
38
  declare function buildSassPreprocessorOptions(sassOptions: Record<string, unknown> | null | undefined): VitePreprocessorOptions | undefined;
39
+ /**
40
+ * Create a per-build binding of {@link SassAwareFileSystemLoader}.
41
+ *
42
+ * Returns:
43
+ * - `Loader` — a class to inject as postcss-modules' `css.modules.Loader`
44
+ * option. postcss-modules instantiates it with the fixed
45
+ * `(root, plugins, fileResolve)` signature, so the resolved Vite config is
46
+ * captured in this factory's closure rather than passed to the constructor.
47
+ * - `setResolvedConfig` — called from vinext's `configResolved` hook to bind
48
+ * that build's resolved config.
49
+ *
50
+ * One binding is created per vinext plugin instance, so concurrent or
51
+ * back-to-back builds in a single process never observe another build's
52
+ * config (root, sass options, `generateScopedName`, logger, …).
53
+ */
54
+ declare function createSassAwareFileSystemLoader(): {
55
+ Loader: new (root: string, plugins: unknown[], fileResolve?: (newPath: string, relativeTo: string) => Promise<string>) => {
56
+ fetch(newPath: string, relativeTo: string, trace?: string): Promise<Record<string, string>>;
57
+ readonly finalSource: string;
58
+ };
59
+ setResolvedConfig: (config: ResolvedConfig) => void;
60
+ };
32
61
  //#endregion
33
- export { buildSassPreprocessorOptions };
62
+ export { buildSassPreprocessorOptions, createSassAwareFileSystemLoader, createSassTildeImporter };
@@ -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 };
@@ -24,6 +24,11 @@ type InterceptingRoute = {
24
24
  pagePath: string; /** Absolute layout paths inside the intercepting route tree, outermost to innermost */
25
25
  layoutPaths: string[]; /** Parameter names for dynamic segments */
26
26
  params: string[];
27
+ /**
28
+ * Synthetic page-carrier slot id for sibling (slot-less) interception.
29
+ * Set only when the marker has no `@slot` wrapper; undefined for slot intercepts.
30
+ */
31
+ slotId?: string;
27
32
  };
28
33
  type ParallelSlot = {
29
34
  /** Graph-owned semantic slot identity. Required on AppRouteGraphParallelSlot. */id?: string; /** Stable slot identity (name + owning directory), used for route serialization keys. */
@@ -73,7 +78,13 @@ type AppRoute = {
73
78
  routePath: string | null; /** Ordered list of layout files from root to leaf */
74
79
  layouts: string[]; /** Ordered list of all discovered template files from root to leaf (not necessarily aligned 1:1 with layouts) */
75
80
  templates: string[]; /** Parallel route slots (from @slot directories at the route's directory level) */
76
- parallelSlots: ParallelSlot[]; /** Loading component path */
81
+ parallelSlots: ParallelSlot[];
82
+ /**
83
+ * Interception markers not wrapped in an `@slot` directory.
84
+ * On soft-nav, the intercepting page replaces the entire page response.
85
+ * Empty array when there are no sibling-style interception markers.
86
+ */
87
+ siblingIntercepts: InterceptingRoute[]; /** Loading component path */
77
88
  loadingPath: string | null; /** Error component path (leaf directory only) */
78
89
  errorPath: string | null;
79
90
  /**
@@ -265,6 +276,28 @@ declare function buildAppRouteGraph(appDir: string, matcher: ValidFileMatcher):
265
276
  routeManifest: RouteManifest;
266
277
  }>;
267
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;
268
301
  /**
269
302
  * Convert filesystem path segments to URL route parts, skipping invisible segments
270
303
  * (route groups, @slots, ".") and converting dynamic segment syntax to Express-style
@@ -299,4 +332,4 @@ declare function computeAppRouteStaticSiblings(allRoutes: readonly {
299
332
  patternParts?: readonly string[] | null;
300
333
  }): string[];
301
334
  //#endregion
302
- 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 };