vinext 0.2.0 → 0.2.1

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 (128) hide show
  1. package/dist/build/css-url-assets.d.ts +3 -1
  2. package/dist/build/css-url-assets.js +18 -1
  3. package/dist/build/inject-pregenerated-paths.d.ts +3 -0
  4. package/dist/build/inject-pregenerated-paths.js +4 -1
  5. package/dist/build/prerender-server-entry.d.ts +1 -0
  6. package/dist/build/prerender-server-entry.js +49 -0
  7. package/dist/build/prerender-server-pool.d.ts +44 -0
  8. package/dist/build/prerender-server-pool.js +194 -0
  9. package/dist/build/prerender.js +86 -22
  10. package/dist/build/run-prerender.js +2 -1
  11. package/dist/check.d.ts +4 -6
  12. package/dist/check.js +8 -9
  13. package/dist/cli.js +1 -1
  14. package/dist/client/vinext-next-data.d.ts +4 -1
  15. package/dist/config/config-matchers.js +33 -11
  16. package/dist/config/next-config.d.ts +36 -6
  17. package/dist/config/next-config.js +16 -2
  18. package/dist/entries/app-browser-entry.js +5 -1
  19. package/dist/entries/app-rsc-entry.d.ts +3 -2
  20. package/dist/entries/app-rsc-entry.js +4 -0
  21. package/dist/entries/pages-client-entry.js +14 -3
  22. package/dist/entries/pages-server-entry.js +18 -4
  23. package/dist/index.js +320 -25
  24. package/dist/packages/cloudflare/src/cache/kv-data-adapter.runtime.js +8 -1
  25. package/dist/packages/cloudflare/src/deploy.js +1 -5
  26. package/dist/packages/cloudflare/src/tpr.js +26 -5
  27. package/dist/plugins/fonts.js +1 -1
  28. package/dist/plugins/ignore-dynamic-requests.js +1 -1
  29. package/dist/plugins/og-assets.js +2 -1
  30. package/dist/plugins/optimize-imports.js +1 -1
  31. package/dist/plugins/rsc-reference-validation-normalizer.d.ts +12 -0
  32. package/dist/plugins/rsc-reference-validation-normalizer.js +48 -0
  33. package/dist/plugins/sass.d.ts +17 -1
  34. package/dist/plugins/sass.js +74 -1
  35. package/dist/plugins/styled-jsx.d.ts +16 -0
  36. package/dist/plugins/styled-jsx.js +149 -0
  37. package/dist/routing/app-route-graph.js +22 -16
  38. package/dist/routing/file-matcher.d.ts +8 -1
  39. package/dist/routing/file-matcher.js +15 -3
  40. package/dist/server/app-browser-entry.js +46 -14
  41. package/dist/server/app-browser-navigation-controller.js +1 -1
  42. package/dist/server/app-browser-server-action-client.js +5 -3
  43. package/dist/server/app-browser-state.d.ts +1 -0
  44. package/dist/server/app-browser-state.js +2 -2
  45. package/dist/server/app-browser-visible-commit.js +1 -1
  46. package/dist/server/app-page-boundary.js +2 -1
  47. package/dist/server/app-page-cache-finalizer.d.ts +1 -0
  48. package/dist/server/app-page-cache-finalizer.js +3 -3
  49. package/dist/server/app-page-cache.js +23 -6
  50. package/dist/server/app-page-dispatch.d.ts +1 -0
  51. package/dist/server/app-page-dispatch.js +25 -8
  52. package/dist/server/app-page-element-builder.js +1 -0
  53. package/dist/server/app-page-execution.js +2 -1
  54. package/dist/server/app-page-render-identity.d.ts +1 -0
  55. package/dist/server/app-page-render-identity.js +2 -1
  56. package/dist/server/app-page-render.d.ts +2 -0
  57. package/dist/server/app-page-render.js +67 -16
  58. package/dist/server/app-page-response.d.ts +7 -0
  59. package/dist/server/app-page-response.js +16 -4
  60. package/dist/server/app-page-stream.d.ts +2 -1
  61. package/dist/server/app-page-stream.js +1 -1
  62. package/dist/server/app-route-handler-execution.js +1 -1
  63. package/dist/server/app-route-tree-prefetch.d.ts +43 -0
  64. package/dist/server/app-route-tree-prefetch.js +187 -0
  65. package/dist/server/app-router-entry.js +1 -1
  66. package/dist/server/app-rsc-cache-busting.d.ts +2 -1
  67. package/dist/server/app-rsc-cache-busting.js +9 -4
  68. package/dist/server/app-rsc-handler.d.ts +6 -0
  69. package/dist/server/app-rsc-handler.js +89 -11
  70. package/dist/server/app-rsc-render-mode.d.ts +3 -2
  71. package/dist/server/app-rsc-render-mode.js +4 -1
  72. package/dist/server/app-rsc-request-normalization.d.ts +3 -4
  73. package/dist/server/app-rsc-request-normalization.js +4 -5
  74. package/dist/server/app-server-action-execution.js +4 -3
  75. package/dist/server/app-ssr-entry.js +3 -3
  76. package/dist/server/cache-control.d.ts +3 -1
  77. package/dist/server/cache-control.js +13 -1
  78. package/dist/server/dev-server.d.ts +11 -1
  79. package/dist/server/dev-server.js +78 -20
  80. package/dist/server/headers.d.ts +6 -2
  81. package/dist/server/headers.js +11 -5
  82. package/dist/server/isr-cache.d.ts +2 -1
  83. package/dist/server/isr-cache.js +7 -3
  84. package/dist/server/pages-data-route.d.ts +4 -2
  85. package/dist/server/pages-data-route.js +18 -4
  86. package/dist/server/pages-dev-module-url.d.ts +2 -1
  87. package/dist/server/pages-dev-module-url.js +6 -3
  88. package/dist/server/pages-node-compat.d.ts +12 -1
  89. package/dist/server/pages-node-compat.js +50 -1
  90. package/dist/server/pages-page-data.d.ts +9 -0
  91. package/dist/server/pages-page-data.js +17 -9
  92. package/dist/server/pages-page-handler.js +19 -7
  93. package/dist/server/pages-page-response.d.ts +1 -0
  94. package/dist/server/pages-page-response.js +3 -2
  95. package/dist/server/pages-request-pipeline.d.ts +8 -6
  96. package/dist/server/pages-request-pipeline.js +30 -6
  97. package/dist/server/pages-router-entry.js +1 -1
  98. package/dist/server/prod-server.d.ts +4 -2
  99. package/dist/server/prod-server.js +38 -22
  100. package/dist/server/request-pipeline.js +1 -1
  101. package/dist/shims/cache-handler.js +8 -1
  102. package/dist/shims/cache.js +3 -0
  103. package/dist/shims/fetch-cache.d.ts +3 -1
  104. package/dist/shims/fetch-cache.js +77 -52
  105. package/dist/shims/form.d.ts +1 -1
  106. package/dist/shims/internal/app-prefetch-fetch-queue.d.ts +10 -0
  107. package/dist/shims/internal/app-prefetch-fetch-queue.js +61 -0
  108. package/dist/shims/internal/hybrid-client-route-owner.d.ts +2 -1
  109. package/dist/shims/internal/hybrid-client-route-owner.js +34 -1
  110. package/dist/shims/internal/pages-data-fetch-dedup.d.ts +2 -1
  111. package/dist/shims/internal/pages-data-fetch-dedup.js +13 -2
  112. package/dist/shims/internal/pages-data-target.d.ts +8 -3
  113. package/dist/shims/internal/pages-data-target.js +9 -4
  114. package/dist/shims/link.d.ts +1 -1
  115. package/dist/shims/link.js +179 -42
  116. package/dist/shims/navigation.d.ts +15 -3
  117. package/dist/shims/navigation.js +202 -39
  118. package/dist/shims/navigation.react-server.js +1 -1
  119. package/dist/shims/request-context.js +18 -0
  120. package/dist/shims/router.js +31 -15
  121. package/dist/shims/unified-request-context.js +1 -0
  122. package/dist/typegen.js +1 -1
  123. package/dist/utils/middleware-request-headers.js +1 -1
  124. package/dist/utils/protocol-headers.d.ts +7 -1
  125. package/dist/utils/protocol-headers.js +7 -1
  126. package/dist/utils/vite-version.d.ts +2 -0
  127. package/dist/utils/vite-version.js +11 -1
  128. package/package.json +2 -2
@@ -306,6 +306,25 @@ const PRERENDER_PORT = 19384;
306
306
  const SERVER_STARTUP_TIMEOUT = 3e4;
307
307
  /** Max concurrent fetch requests during pre-rendering. */
308
308
  const FETCH_CONCURRENCY = 10;
309
+ const NON_CACHEABLE_CACHE_CONTROL_RE = /\b(?:no-store|no-cache|private)\b/i;
310
+ function getTprHeader(headers, name) {
311
+ const normalizedName = name.toLowerCase();
312
+ for (const [key, value] of Object.entries(headers)) if (key.toLowerCase() === normalizedName) return value;
313
+ }
314
+ function hasNonCacheableCacheControl(headers) {
315
+ const cacheControl = getTprHeader(headers, "cache-control");
316
+ return cacheControl ? NON_CACHEABLE_CACHE_CONTROL_RE.test(cacheControl) : false;
317
+ }
318
+ function readTprRevalidateHeader(headers) {
319
+ const revalidateHeader = getTprHeader(headers, VINEXT_REVALIDATE_HEADER);
320
+ if (revalidateHeader === void 0) return void 0;
321
+ return Number(revalidateHeader);
322
+ }
323
+ function isTprCacheable(headers) {
324
+ if (hasNonCacheableCacheControl(headers)) return false;
325
+ const revalidate = readTprRevalidateHeader(headers);
326
+ return revalidate === void 0 || Number.isFinite(revalidate) && revalidate > 0;
327
+ }
309
328
  /**
310
329
  * Start a local production server, fetch each route to produce HTML,
311
330
  * and return the results. Pages that fail to render are skipped.
@@ -333,11 +352,12 @@ async function prerenderRoutes(routes, root, hostDomain) {
333
352
  redirect: "manual"
334
353
  });
335
354
  if (response.status < 400) {
336
- const html = await response.text();
337
355
  const headers = {};
338
356
  response.headers.forEach((value, key) => {
339
357
  if (key === "content-type" || key === "cache-control" || key === "x-vinext-revalidate" || key === "location") headers[key] = value;
340
358
  });
359
+ if (!isTprCacheable(headers)) return;
360
+ const html = await response.text();
341
361
  results.set(routePath, {
342
362
  html,
343
363
  status: response.status,
@@ -427,10 +447,11 @@ function buildTprKVPairs(entries, buildId, defaultRevalidateSeconds) {
427
447
  const now = Date.now();
428
448
  const pairs = [];
429
449
  for (const [routePath, result] of entries) {
430
- const revalidateHeader = result.headers[VINEXT_REVALIDATE_HEADER];
431
- const revalidateSeconds = revalidateHeader && !isNaN(Number(revalidateHeader)) ? Number(revalidateHeader) : defaultRevalidateSeconds;
432
- const revalidateAt = revalidateSeconds > 0 ? now + revalidateSeconds * 1e3 : null;
433
- const kvTtl = revalidateSeconds > 0 ? MAX_KV_TTL_SECONDS : 24 * 3600;
450
+ if (!isTprCacheable(result.headers)) continue;
451
+ const revalidateSeconds = readTprRevalidateHeader(result.headers) ?? defaultRevalidateSeconds;
452
+ if (!Number.isFinite(revalidateSeconds) || revalidateSeconds <= 0) continue;
453
+ const revalidateAt = now + revalidateSeconds * 1e3;
454
+ const kvTtl = MAX_KV_TTL_SECONDS;
434
455
  const tags = buildAppPageCacheTags(routePath, []);
435
456
  const entry = {
436
457
  value: {
@@ -1,11 +1,11 @@
1
1
  import { escapeRegExp } from "../utils/regex.js";
2
+ import { ASSET_PREFIX_URL_DIR } from "../utils/asset-prefix.js";
2
3
  import { lastSignificantChar } from "../utils/has-trailing-comma.js";
3
4
  import { buildFallbackFontFace, getFallbackFontOverrideMetrics } from "../build/google-fonts/fallback-metrics.js";
4
5
  import { validateGoogleFontOptions } from "../build/google-fonts/validate.js";
5
6
  import { getFontAxes } from "../build/google-fonts/get-axes.js";
6
7
  import { buildGoogleFontsUrl } from "../build/google-fonts/build-url.js";
7
8
  import { findFontFilesInCss } from "../build/google-fonts/find-font-files-in-css.js";
8
- import { ASSET_PREFIX_URL_DIR } from "../utils/asset-prefix.js";
9
9
  import { CONTENT_TYPES } from "../server/static-file-cache.js";
10
10
  import fs from "node:fs";
11
11
  import path from "node:path";
@@ -384,7 +384,7 @@ function dynamicImportReplacement() {
384
384
  function transformVeryDynamicRequests(code, id) {
385
385
  if (!code.includes("require") && !mayContainDynamicImport(code)) return null;
386
386
  const extension = path.extname(id.split("?", 1)[0]);
387
- const lang = extension === ".ts" || extension === ".mts" || extension === ".cts" ? "ts" : extension === ".tsx" ? "tsx" : extension === ".jsx" ? "jsx" : "js";
387
+ const lang = extension === ".ts" || extension === ".mts" || extension === ".cts" ? "ts" : extension === ".tsx" ? "tsx" : extension === ".js" || extension === ".jsx" || extension === ".mjs" || extension === ".cjs" ? "jsx" : "js";
388
388
  let ast;
389
389
  try {
390
390
  ast = parseAst(code, { lang });
@@ -1,3 +1,4 @@
1
+ import { normalizePathSeparators } from "../utils/path.js";
1
2
  import { OgAssetOwnership } from "./og-asset-ownership.js";
2
3
  import { createRequire } from "node:module";
3
4
  import fs from "node:fs";
@@ -182,7 +183,7 @@ function createOgAssetsPlugin() {
182
183
  for (const chunk of chunks) {
183
184
  const re = fallbackUrlRegex(base);
184
185
  const chunkDir = path.posix.dirname(chunk.fileName);
185
- const rel = path.posix.relative(chunkDir, emitted);
186
+ const rel = normalizePathSeparators(path.relative(chunkDir, emitted));
186
187
  const ref = rel.startsWith(".") ? rel : `./${rel}`;
187
188
  const s = new MagicString(chunk.code);
188
189
  let edited = false;
@@ -1,5 +1,5 @@
1
- import { escapeRegExp } from "../utils/regex.js";
2
1
  import { normalizePathSeparators } from "../utils/path.js";
2
+ import { escapeRegExp } from "../utils/regex.js";
3
3
  import { VIRTUAL_MODULE_ID_RE } from "../utils/virtual-module.js";
4
4
  import { getAstName } from "./ast-utils.js";
5
5
  import { createRequire } from "node:module";
@@ -0,0 +1,12 @@
1
+ import { Plugin } from "vite";
2
+
3
+ //#region src/plugins/rsc-reference-validation-normalizer.d.ts
4
+ /**
5
+ * @vitejs/plugin-rsc stores dev virtual client-reference keys in Vite's encoded
6
+ * `/@id/__x00__...` form, but React's SSR consumer can ask validation for the
7
+ * decoded `/@id/\0...` form. Treat those as equivalent and fall through to the
8
+ * upstream validator for all other invalid references.
9
+ */
10
+ declare function createRscReferenceValidationNormalizerPlugin(): Plugin;
11
+ //#endregion
12
+ export { createRscReferenceValidationNormalizerPlugin };
@@ -0,0 +1,48 @@
1
+ //#region src/plugins/rsc-reference-validation-normalizer.ts
2
+ const REFERENCE_VALIDATION_ID_PREFIX = "\0virtual:vite-rsc/reference-validation?";
3
+ function parseReferenceValidationQuery(id) {
4
+ const queryStart = id.indexOf("?");
5
+ if (queryStart === -1) return null;
6
+ return Object.fromEntries(new URLSearchParams(id.slice(queryStart + 1)));
7
+ }
8
+ function normalizeReferenceKey(id) {
9
+ return id.replaceAll("\0", "__x00__");
10
+ }
11
+ function hasReference(referenceMetaMap, referenceId) {
12
+ if (!referenceMetaMap || !referenceId) return false;
13
+ const normalizedReferenceId = normalizeReferenceKey(referenceId);
14
+ return Object.values(referenceMetaMap).some((meta) => normalizeReferenceKey(meta.referenceKey) === normalizedReferenceId);
15
+ }
16
+ /**
17
+ * @vitejs/plugin-rsc stores dev virtual client-reference keys in Vite's encoded
18
+ * `/@id/__x00__...` form, but React's SSR consumer can ask validation for the
19
+ * decoded `/@id/\0...` form. Treat those as equivalent and fall through to the
20
+ * upstream validator for all other invalid references.
21
+ */
22
+ function createRscReferenceValidationNormalizerPlugin() {
23
+ let rscApi;
24
+ return {
25
+ name: "vinext:rsc-reference-validation-normalizer",
26
+ enforce: "pre",
27
+ apply(_config, env) {
28
+ return env.command === "serve" && env.isPreview !== true;
29
+ },
30
+ configResolved(config) {
31
+ rscApi = config.plugins.find((plugin) => plugin.name === "rsc:minimal")?.api;
32
+ },
33
+ load: {
34
+ filter: { id: /^\u0000virtual:vite-rsc\/reference-validation\?/ },
35
+ handler(id) {
36
+ if (!id.startsWith(REFERENCE_VALIDATION_ID_PREFIX)) return null;
37
+ const query = parseReferenceValidationQuery(id);
38
+ if (!query) return null;
39
+ const manager = rscApi?.manager;
40
+ if (query.type === "client" && hasReference(manager?.clientReferenceMetaMap, query.id)) return "export {}";
41
+ if (query.type === "server" && hasReference(manager?.serverReferenceMetaMap, query.id)) return "export {}";
42
+ return null;
43
+ }
44
+ }
45
+ };
46
+ }
47
+ //#endregion
48
+ export { createRscReferenceValidationNormalizerPlugin };
@@ -35,6 +35,22 @@ type VitePreprocessorOptions = {
35
35
  declare function createSassTildeImporter(root: string): {
36
36
  findFileUrl(url: string): URL | null;
37
37
  };
38
+ type SassLoadedStylesheet = {
39
+ contents: string;
40
+ syntax: "scss" | "indented" | "css";
41
+ };
42
+ /**
43
+ * Preserve source-asset provenance for `url()` references inside imported Sass
44
+ * partials. Vite's transform hook only sees the entry stylesheet after Sass has
45
+ * flattened its imports, at which point relative URLs from a partial have lost
46
+ * the partial's source filename. Marking the partial before compilation keeps
47
+ * byte-identical assets associated with their original basenames.
48
+ */
49
+ declare function createSassCssUrlAssetImporter(): {
50
+ canonicalize(url: string): URL | null;
51
+ load(canonicalUrl: URL): SassLoadedStylesheet | null;
52
+ rewriteImports(source: string, filename: string): string;
53
+ };
38
54
  declare function buildSassPreprocessorOptions(sassOptions: Record<string, unknown> | null | undefined): VitePreprocessorOptions | undefined;
39
55
  /**
40
56
  * Create a per-build binding of {@link SassAwareFileSystemLoader}.
@@ -59,4 +75,4 @@ declare function createSassAwareFileSystemLoader(): {
59
75
  setResolvedConfig: (config: ResolvedConfig) => void;
60
76
  };
61
77
  //#endregion
62
- export { buildSassPreprocessorOptions, createSassAwareFileSystemLoader, createSassTildeImporter };
78
+ export { buildSassPreprocessorOptions, createSassAwareFileSystemLoader, createSassCssUrlAssetImporter, createSassTildeImporter };
@@ -1,3 +1,4 @@
1
+ import { markCssUrlAssetReferences, rebaseCssUrlAssetReferences } from "../build/css-url-assets.js";
1
2
  import { createRequire } from "node:module";
2
3
  import fs from "node:fs";
3
4
  import path from "node:path";
@@ -75,6 +76,78 @@ function createSassTildeImporter(root) {
75
76
  }
76
77
  } };
77
78
  }
79
+ function sassStylesheetCandidates(importPath) {
80
+ const extension = path.extname(importPath);
81
+ const directory = path.dirname(importPath);
82
+ const basename = path.basename(importPath, extension);
83
+ const candidates = [];
84
+ const extensions = extension ? [extension] : [
85
+ ".scss",
86
+ ".sass",
87
+ ".css"
88
+ ];
89
+ for (const candidateExtension of extensions) {
90
+ const basePath = extension ? importPath : `${importPath}${candidateExtension}`;
91
+ candidates.push(basePath);
92
+ candidates.push(path.join(directory, `_${basename}${candidateExtension}`));
93
+ }
94
+ for (const candidateExtension of extensions) {
95
+ candidates.push(path.join(importPath, `index${candidateExtension}`));
96
+ candidates.push(path.join(importPath, `_index${candidateExtension}`));
97
+ }
98
+ return candidates;
99
+ }
100
+ function deriveSassNamespace(importUrl) {
101
+ const parts = importUrl.replaceAll("\\", "/").replace(/\/$/, "").split("/");
102
+ let basename = parts.pop() ?? "stylesheet";
103
+ basename = basename.replace(/^_/, "").replace(/\..*$/, "");
104
+ if (basename === "index" && parts.length > 0) basename = (parts.pop() ?? basename).replace(/^_/, "").replace(/\..*$/, "");
105
+ return basename;
106
+ }
107
+ /**
108
+ * Preserve source-asset provenance for `url()` references inside imported Sass
109
+ * partials. Vite's transform hook only sees the entry stylesheet after Sass has
110
+ * flattened its imports, at which point relative URLs from a partial have lost
111
+ * the partial's source filename. Marking the partial before compilation keeps
112
+ * byte-identical assets associated with their original basenames.
113
+ */
114
+ function createSassCssUrlAssetImporter() {
115
+ const markedStylesheets = /* @__PURE__ */ new Map();
116
+ const importUrlPrefix = "vinext-css-url-asset:";
117
+ const prepareStylesheet = (filename, entryDirectory) => {
118
+ const contents = fs.readFileSync(filename, "utf8");
119
+ const marked = markCssUrlAssetReferences(rebaseCssUrlAssetReferences(contents, path.dirname(filename), entryDirectory) ?? contents, filename);
120
+ if (marked === null) return null;
121
+ const extension = path.extname(filename).toLowerCase();
122
+ return {
123
+ contents: rewriteImports(marked, filename, entryDirectory),
124
+ syntax: extension === ".sass" ? "indented" : extension === ".css" ? "css" : "scss"
125
+ };
126
+ };
127
+ const rewriteImports = (source, filename, entryDirectory = path.dirname(filename)) => source.replace(/(@(import|use|forward)\s+)(["'])(\.[^"']+)\3([^;]*;?)/g, (match, prefix, rule, quote, importUrl, suffix) => {
128
+ const importPath = path.resolve(path.dirname(filename), importUrl);
129
+ for (const candidate of sassStylesheetCandidates(importPath)) {
130
+ if (!fs.statSync(candidate, { throwIfNoEntry: false })?.isFile()) continue;
131
+ const prepared = prepareStylesheet(candidate, entryDirectory);
132
+ if (prepared === null) return match;
133
+ const canonicalUrl = pathToFileURL(candidate);
134
+ markedStylesheets.set(canonicalUrl.href, prepared);
135
+ const namespace = rule === "use" && !/\bas\s+(?:\*|[-\w]+)/.test(suffix) ? ` as ${deriveSassNamespace(importUrl)}` : "";
136
+ return `${prefix}${quote}${importUrlPrefix}${encodeURIComponent(candidate)}${quote}${namespace}${suffix}`;
137
+ }
138
+ return match;
139
+ });
140
+ return {
141
+ canonicalize(url) {
142
+ if (!url.startsWith(importUrlPrefix)) return null;
143
+ return pathToFileURL(decodeURIComponent(url.slice(21)));
144
+ },
145
+ load(canonicalUrl) {
146
+ return markedStylesheets.get(canonicalUrl.href) ?? null;
147
+ },
148
+ rewriteImports
149
+ };
150
+ }
78
151
  function buildSassPreprocessorOptions(sassOptions) {
79
152
  if (!sassOptions || typeof sassOptions !== "object") return void 0;
80
153
  const { prependData, additionalData, includePaths, loadPaths, ...rest } = sassOptions;
@@ -265,4 +338,4 @@ function createSassAwareFileSystemLoader() {
265
338
  };
266
339
  }
267
340
  //#endregion
268
- export { buildSassPreprocessorOptions, createSassAwareFileSystemLoader, createSassTildeImporter };
341
+ export { buildSassPreprocessorOptions, createSassAwareFileSystemLoader, createSassCssUrlAssetImporter, createSassTildeImporter };
@@ -0,0 +1,16 @@
1
+ import { Plugin } from "vite";
2
+
3
+ //#region src/plugins/styled-jsx.d.ts
4
+ type NextSwcModule = {
5
+ loadBindings(): Promise<unknown>;
6
+ transform(source: string, options: Record<string, unknown>): Promise<{
7
+ code: string;
8
+ map?: string;
9
+ }>;
10
+ };
11
+ type StyledJsxPluginOptions = {
12
+ importModule?: (url: string) => Promise<NextSwcModule>;
13
+ };
14
+ declare function createStyledJsxPlugin(initialProjectRoot: string, options?: StyledJsxPluginOptions): Plugin;
15
+ //#endregion
16
+ export { createStyledJsxPlugin };
@@ -0,0 +1,149 @@
1
+ import { createRequire } from "node:module";
2
+ import path from "node:path";
3
+ import { parseAst } from "vite";
4
+ import { pathToFileURL } from "node:url";
5
+ //#region src/plugins/styled-jsx.ts
6
+ const STYLED_JSX_IMPORT_RE = /^styled-jsx(?:\/.*)?$/;
7
+ const NODE_MODULES_RE = /[\\/]node_modules[\\/]/;
8
+ const STYLED_JSX_SOURCE_RE = /(?:<style\b|from\s+["']styled-jsx\/css["']|require\s*\(\s*["']styled-jsx\/css["']\s*\))/;
9
+ const STYLED_JSX_CSS_RE = /(?:from\s+["']styled-jsx\/css["']|require\s*\(\s*["']styled-jsx\/css["']\s*\))/;
10
+ function hasStyledJsxTag(source, id) {
11
+ const cleanId = id.split("?")[0];
12
+ const extension = path.extname(cleanId);
13
+ const lang = extension === ".ts" || extension === ".mts" || extension === ".cts" ? "ts" : "tsx";
14
+ let ast;
15
+ try {
16
+ ast = parseAst(source, { lang });
17
+ } catch {
18
+ return false;
19
+ }
20
+ const pending = [ast];
21
+ const visited = /* @__PURE__ */ new Set();
22
+ while (pending.length > 0) {
23
+ const value = pending.pop();
24
+ if (!value || typeof value !== "object" || visited.has(value)) continue;
25
+ visited.add(value);
26
+ const node = value;
27
+ if (node.type === "JSXOpeningElement") {
28
+ const name = node.name;
29
+ if (name?.type === "JSXIdentifier" && name.name === "style") {
30
+ if (node.attributes?.some((attribute) => {
31
+ if (attribute.type !== "JSXAttribute") return false;
32
+ const attributeName = attribute.name;
33
+ return attributeName?.type === "JSXIdentifier" && attributeName.name === "jsx";
34
+ })) return true;
35
+ }
36
+ }
37
+ for (const child of Object.values(node)) if (Array.isArray(child)) pending.push(...child);
38
+ else if (child && typeof child === "object") pending.push(child);
39
+ }
40
+ return false;
41
+ }
42
+ function createProjectRequire(projectRoot) {
43
+ return createRequire(path.join(projectRoot, "package.json"));
44
+ }
45
+ function resolveNextRequire(projectRoot) {
46
+ try {
47
+ return createRequire(createProjectRequire(projectRoot).resolve("next/package.json"));
48
+ } catch {
49
+ return null;
50
+ }
51
+ }
52
+ function parserOptions(id) {
53
+ const extension = path.extname(id.split("?")[0]);
54
+ if (extension === ".ts" || extension === ".tsx") return {
55
+ syntax: "typescript",
56
+ tsx: extension === ".tsx",
57
+ decorators: true
58
+ };
59
+ return {
60
+ syntax: "ecmascript",
61
+ jsx: true
62
+ };
63
+ }
64
+ function createStyledJsxPlugin(initialProjectRoot, options = {}) {
65
+ let projectRoot = initialProjectRoot;
66
+ let development = false;
67
+ let nextRequire;
68
+ let compilerPromise = null;
69
+ const importModule = options.importModule ?? ((url) => import(url));
70
+ function getNextRequire() {
71
+ nextRequire ??= resolveNextRequire(projectRoot);
72
+ return nextRequire;
73
+ }
74
+ async function getCompiler() {
75
+ if (!compilerPromise) {
76
+ const requireFromNext = getNextRequire();
77
+ if (!requireFromNext) throw new Error("[vinext] styled-jsx requires an installed next package so vinext can use its matching compiler.");
78
+ compilerPromise = importModule(pathToFileURL(requireFromNext.resolve("next/dist/build/swc")).href).then(async (compiler) => {
79
+ await compiler.loadBindings();
80
+ return compiler;
81
+ });
82
+ }
83
+ return compilerPromise;
84
+ }
85
+ return {
86
+ name: "vinext:styled-jsx",
87
+ enforce: "pre",
88
+ configResolved(config) {
89
+ development = config.command === "serve";
90
+ if (config.root !== projectRoot) {
91
+ projectRoot = config.root;
92
+ nextRequire = void 0;
93
+ compilerPromise = null;
94
+ }
95
+ },
96
+ resolveId: {
97
+ filter: { id: STYLED_JSX_IMPORT_RE },
98
+ handler(source) {
99
+ try {
100
+ return getNextRequire()?.resolve(source) ?? null;
101
+ } catch {}
102
+ try {
103
+ return createProjectRequire(projectRoot).resolve(source);
104
+ } catch {
105
+ return null;
106
+ }
107
+ }
108
+ },
109
+ transform: {
110
+ filter: {
111
+ id: {
112
+ include: /\.[cm]?[jt]sx?(?:\?.*)?$/,
113
+ exclude: NODE_MODULES_RE
114
+ },
115
+ code: STYLED_JSX_SOURCE_RE
116
+ },
117
+ async handler(source, id) {
118
+ if (NODE_MODULES_RE.test(id.split("?")[0])) return null;
119
+ const hasStyledJsxCss = STYLED_JSX_CSS_RE.test(source);
120
+ const hasStyledJsxElement = !hasStyledJsxCss && hasStyledJsxTag(source, id);
121
+ if (!hasStyledJsxCss && !hasStyledJsxElement) return null;
122
+ if (!getNextRequire()) throw new Error("[vinext] styled-jsx requires an installed next package so vinext can use its matching compiler.");
123
+ const result = await (await getCompiler()).transform(source, {
124
+ filename: id.split("?")[0],
125
+ sourceMaps: true,
126
+ module: { type: "es6" },
127
+ styledJsx: { useLightningcss: false },
128
+ jsc: {
129
+ parser: parserOptions(id),
130
+ transform: {
131
+ react: {
132
+ runtime: "automatic",
133
+ development,
134
+ useBuiltins: true
135
+ },
136
+ optimizer: { simplify: false }
137
+ }
138
+ }
139
+ });
140
+ return {
141
+ code: result.code,
142
+ map: result.map ?? null
143
+ };
144
+ }
145
+ }
146
+ };
147
+ }
148
+ //#endregion
149
+ export { createStyledJsxPlugin };
@@ -1,5 +1,5 @@
1
- import { decodeRouteSegment, isInvisibleSegment, sortRoutes } from "./utils.js";
2
1
  import { normalizePathSeparators } from "../utils/path.js";
2
+ import { decodeRouteSegment, isInvisibleSegment, sortRoutes } from "./utils.js";
3
3
  import { findFileWithExts, scanWithExtensions } from "./file-matcher.js";
4
4
  import { validateRoutePatterns } from "./route-validation.js";
5
5
  import { compareStrings } from "../utils/compare.js";
@@ -440,9 +440,9 @@ function validatePageRouteConflicts(routes, appDir) {
440
440
  }
441
441
  }
442
442
  function formatAppFilePath(filePath, appDir) {
443
- const relativePath = path.relative(appDir, filePath).replace(/\\/g, "/");
443
+ const relativePath = normalizePathSeparators(path.relative(appDir, filePath));
444
444
  const parsedPath = path.parse(relativePath);
445
- const withoutExtension = path.join(parsedPath.dir, parsedPath.name).replace(/\\/g, "/");
445
+ const withoutExtension = normalizePathSeparators(path.join(parsedPath.dir, parsedPath.name));
446
446
  return withoutExtension.startsWith("/") ? withoutExtension : `/${withoutExtension}`;
447
447
  }
448
448
  /**
@@ -1180,9 +1180,7 @@ function findSlotRootPage(slotDir, matcher) {
1180
1180
  * default component, intercepting route, or nested page-backed sub-route.
1181
1181
  *
1182
1182
  * `dir` and `appDir` must be forward-slash. The slot directory is built from
1183
- * `dir` with `path.posix.join`, and the owner segments and slot key come from
1184
- * `path.posix.relative(appDir, …)`, which only yields a forward-slash relative
1185
- * path when both operands already are.
1183
+ * `dir` with `path.posix.join`.
1186
1184
  */
1187
1185
  function discoverParallelSlots(dir, appDir, matcher, includeNestedOnlySlots = false) {
1188
1186
  const entries = readDirEntriesCached(dir, matcher);
@@ -1197,12 +1195,12 @@ function discoverParallelSlots(dir, appDir, matcher, includeNestedOnlySlots = fa
1197
1195
  const interceptingRoutes = discoverInterceptingRoutes(slotDir, dir, appDir, matcher);
1198
1196
  const hasNestedPages = includeNestedOnlySlots && findSlotSubPages(slotDir, matcher).length > 0;
1199
1197
  if (!pagePath && !defaultPath && interceptingRoutes.length === 0 && !hasNestedPages) continue;
1200
- const ownerSegments = path.posix.relative(appDir, dir).split("/").filter((segment) => segment.length > 0);
1198
+ const ownerSegments = path.relative(appDir, dir).split(path.sep).filter((segment) => segment.length > 0);
1201
1199
  const ownerTreePath = createAppRouteGraphTreePath(ownerSegments, ownerSegments.length);
1202
1200
  const configLayoutPaths = findSlotConfigLayoutPaths(slotDir, pagePath, matcher);
1203
1201
  slots.push({
1204
1202
  id: createAppRouteGraphSlotId(slotName, ownerTreePath),
1205
- key: `${slotName}@${path.posix.relative(appDir, slotDir)}`,
1203
+ key: `${slotName}@${normalizePathSeparators(path.relative(appDir, slotDir))}`,
1206
1204
  name: slotName,
1207
1205
  ownerDir: slotDir,
1208
1206
  ownerTreePath,
@@ -1262,8 +1260,7 @@ function isInterceptionMarkerDir(name) {
1262
1260
  * They intercept navigation to another route and render within the slot instead.
1263
1261
  *
1264
1262
  * `slotDir`, `routeDir`, and `appDir` must be forward-slash. They are passed
1265
- * down to `path.posix.join` and `path.posix.relative` when building the
1266
- * intercept page paths and target patterns.
1263
+ * down to `path.posix.join` when building the intercept page paths.
1267
1264
  *
1268
1265
  * @param slotDir - The parallel slot directory (e.g. app/feed/@modal)
1269
1266
  * @param routeDir - The directory of the route that owns this slot (e.g. app/feed)
@@ -1283,13 +1280,17 @@ function discoverInterceptingRoutes(slotDir, routeDir, appDir, matcher) {
1283
1280
  * Sibling intercepts use the same conventions and target-computation logic as
1284
1281
  * slot intercepts, but their intercepting page replaces the full page response
1285
1282
  * (not a slot) during soft navigation.
1283
+ *
1284
+ * `appDir` and each route's `pagePath` / `routePath` must be forward-slash. The
1285
+ * owner directories are derived from `pagePath` / `routePath` and matched
1286
+ * against `appDir`-relative paths built with `path.posix.*`.
1286
1287
  */
1287
1288
  function discoverSiblingInterceptingRoutes(routes, appDir, matcher) {
1288
1289
  const routesByDir = /* @__PURE__ */ new Map();
1289
1290
  for (const route of routes) {
1290
1291
  const filePath = route.pagePath ?? route.routePath;
1291
1292
  if (!filePath) continue;
1292
- const routeDir = normalizePathSeparators(path.dirname(filePath));
1293
+ const routeDir = path.posix.dirname(filePath);
1293
1294
  if (!routesByDir.has(routeDir)) routesByDir.set(routeDir, route);
1294
1295
  }
1295
1296
  function walk(dir) {
@@ -1303,7 +1304,7 @@ function discoverSiblingInterceptingRoutes(routes, appDir, matcher) {
1303
1304
  if (!entry.isDirectory()) continue;
1304
1305
  if (entry.name.startsWith("_")) continue;
1305
1306
  if (entry.name.startsWith("@")) continue;
1306
- const childDir = path.join(dir, entry.name);
1307
+ const childDir = path.posix.join(dir, entry.name);
1307
1308
  const marker = matchInterceptConvention(entry.name);
1308
1309
  if (marker) {
1309
1310
  const restOfName = entry.name.slice(marker.prefix.length);
@@ -1370,8 +1371,7 @@ function findOwnerRouteForDir(dir, appDir, routes, routesByDir) {
1370
1371
  * intercepting route directories.
1371
1372
  *
1372
1373
  * `currentDir`, `routeDir`, and `appDir` must be forward-slash. `currentDir`
1373
- * descends with `path.posix.join` and all three reach the `path.posix.join` /
1374
- * `path.posix.relative` calls that build the intercept page paths and patterns.
1374
+ * descends with `path.posix.join` when building the intercept page paths.
1375
1375
  */
1376
1376
  function scanForInterceptingPages(currentDir, routeDir, appDir, results, matcher) {
1377
1377
  if (!fs.existsSync(currentDir)) return;
@@ -1397,6 +1397,12 @@ function matchInterceptConvention(name) {
1397
1397
  /**
1398
1398
  * Collect page.tsx files inside an intercepting route directory tree
1399
1399
  * and compute their target URL patterns.
1400
+ *
1401
+ * `currentDir`, `interceptRoot`, `routeDir`, `appDir`, and `interceptParentDir`
1402
+ * must all be forward-slash. `currentDir` descends with `path.posix.join` and
1403
+ * its `findFile` results become the stored layout/page paths. The others are
1404
+ * relativized against `appDir` (and each other) to derive the intercept page
1405
+ * segments and URL patterns.
1400
1406
  */
1401
1407
  function collectInterceptingPages(currentDir, interceptRoot, convention, interceptSegment, routeDir, appDir, interceptParentDir, results, matcher, parentLayoutPaths = []) {
1402
1408
  const currentLayoutPath = findFile(currentDir, "layout", matcher);
@@ -1407,7 +1413,7 @@ function collectInterceptingPages(currentDir, interceptRoot, convention, interce
1407
1413
  if (targetPattern) {
1408
1414
  const sourceMatchPattern = computeInterceptSourceMatchPattern(interceptParentDir, appDir);
1409
1415
  results.push({
1410
- branchSegments: [interceptSegment, ...normalizePathSeparators(path.relative(interceptRoot, path.dirname(page))).split("/").filter(Boolean)],
1416
+ branchSegments: [interceptSegment, ...path.relative(interceptRoot, path.dirname(page)).split(path.sep).filter(Boolean)],
1411
1417
  convention,
1412
1418
  layoutPaths: [...layoutPaths],
1413
1419
  layoutSegments: layoutPaths.map((layoutPath) => {
@@ -1426,7 +1432,7 @@ function collectInterceptingPages(currentDir, interceptRoot, convention, interce
1426
1432
  for (const entry of entries) {
1427
1433
  if (!entry.isDirectory()) continue;
1428
1434
  if (entry.name.startsWith("_")) continue;
1429
- collectInterceptingPages(path.join(currentDir, entry.name), interceptRoot, convention, interceptSegment, routeDir, appDir, interceptParentDir, results, matcher, layoutPaths);
1435
+ collectInterceptingPages(path.posix.join(currentDir, entry.name), interceptRoot, convention, interceptSegment, routeDir, appDir, interceptParentDir, results, matcher, layoutPaths);
1430
1436
  }
1431
1437
  }
1432
1438
  /**
@@ -39,11 +39,18 @@ declare function findFileWithExts(dir: string, name: string, matcher: ValidFileM
39
39
  * 1. User-configured pageExtensions go first (each prefixed with `.`) so
40
40
  * the user's priority wins. e.g. `.platform.tsx` resolves before `.tsx`.
41
41
  * 2. Vite's defaults follow, with duplicates removed.
42
+ * 3. `.cjs`/`.cts` go last (lowest priority). Neither Vite's defaults nor the
43
+ * user's pageExtensions include them, but `vinext init` renames CJS config
44
+ * files (e.g. `tailwind.config.js` → `tailwind.config.cjs`) when it adds
45
+ * `"type": "module"`, and app code imports those extensionlessly
46
+ * (`import cfg from "../tailwind.config"`). Without these, the bundle fails
47
+ * with "[UNRESOLVED_IMPORT] Could not resolve '../tailwind.config'".
42
48
  *
43
49
  * The user's pageExtensions retain their relative order, which is what
44
50
  * Next.js / Turbopack do via the `resolveExtensions` config option.
45
51
  *
46
- * See: cloudflare/vinext#1502
52
+ * See: cloudflare/vinext#1502 for page-extension ordering, and
53
+ * cloudflare/vinext#2435 for extensionless `.cjs` config imports.
47
54
  */
48
55
  declare function buildViteResolveExtensions(pageExtensions?: readonly string[] | null, viteDefaults?: readonly string[]): string[];
49
56
  /**
@@ -1,5 +1,5 @@
1
- import { escapeRegExp } from "../utils/regex.js";
2
1
  import { normalizePathSeparators } from "../utils/path.js";
2
+ import { escapeRegExp } from "../utils/regex.js";
3
3
  import { existsSync } from "node:fs";
4
4
  import path from "node:path";
5
5
  import { glob } from "node:fs/promises";
@@ -91,11 +91,18 @@ function findFileWithExts(dir, name, matcher) {
91
91
  * 1. User-configured pageExtensions go first (each prefixed with `.`) so
92
92
  * the user's priority wins. e.g. `.platform.tsx` resolves before `.tsx`.
93
93
  * 2. Vite's defaults follow, with duplicates removed.
94
+ * 3. `.cjs`/`.cts` go last (lowest priority). Neither Vite's defaults nor the
95
+ * user's pageExtensions include them, but `vinext init` renames CJS config
96
+ * files (e.g. `tailwind.config.js` → `tailwind.config.cjs`) when it adds
97
+ * `"type": "module"`, and app code imports those extensionlessly
98
+ * (`import cfg from "../tailwind.config"`). Without these, the bundle fails
99
+ * with "[UNRESOLVED_IMPORT] Could not resolve '../tailwind.config'".
94
100
  *
95
101
  * The user's pageExtensions retain their relative order, which is what
96
102
  * Next.js / Turbopack do via the `resolveExtensions` config option.
97
103
  *
98
- * See: cloudflare/vinext#1502
104
+ * See: cloudflare/vinext#1502 for page-extension ordering, and
105
+ * cloudflare/vinext#2435 for extensionless `.cjs` config imports.
99
106
  */
100
107
  function buildViteResolveExtensions(pageExtensions, viteDefaults = [
101
108
  ".mjs",
@@ -109,7 +116,12 @@ function buildViteResolveExtensions(pageExtensions, viteDefaults = [
109
116
  const dotted = normalizePageExtensions(pageExtensions).map((ext) => `.${ext}`);
110
117
  const seen = /* @__PURE__ */ new Set();
111
118
  const result = [];
112
- for (const ext of [...dotted, ...viteDefaults]) {
119
+ for (const ext of [
120
+ ...dotted,
121
+ ...viteDefaults,
122
+ ".cjs",
123
+ ".cts"
124
+ ]) {
113
125
  if (seen.has(ext)) continue;
114
126
  seen.add(ext);
115
127
  result.push(ext);