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.
- package/dist/build/css-url-assets.d.ts +3 -1
- package/dist/build/css-url-assets.js +18 -1
- package/dist/build/inject-pregenerated-paths.d.ts +3 -0
- package/dist/build/inject-pregenerated-paths.js +4 -1
- package/dist/build/prerender-server-entry.d.ts +1 -0
- package/dist/build/prerender-server-entry.js +49 -0
- package/dist/build/prerender-server-pool.d.ts +44 -0
- package/dist/build/prerender-server-pool.js +194 -0
- package/dist/build/prerender.js +86 -22
- package/dist/build/run-prerender.js +2 -1
- package/dist/check.d.ts +4 -6
- package/dist/check.js +8 -9
- package/dist/cli.js +1 -1
- package/dist/client/vinext-next-data.d.ts +4 -1
- package/dist/config/config-matchers.js +33 -11
- package/dist/config/next-config.d.ts +36 -6
- package/dist/config/next-config.js +16 -2
- package/dist/entries/app-browser-entry.js +5 -1
- package/dist/entries/app-rsc-entry.d.ts +3 -2
- package/dist/entries/app-rsc-entry.js +4 -0
- package/dist/entries/pages-client-entry.js +14 -3
- package/dist/entries/pages-server-entry.js +18 -4
- package/dist/index.js +320 -25
- package/dist/packages/cloudflare/src/cache/kv-data-adapter.runtime.js +8 -1
- package/dist/packages/cloudflare/src/deploy.js +1 -5
- package/dist/packages/cloudflare/src/tpr.js +26 -5
- package/dist/plugins/fonts.js +1 -1
- package/dist/plugins/ignore-dynamic-requests.js +1 -1
- package/dist/plugins/og-assets.js +2 -1
- package/dist/plugins/optimize-imports.js +1 -1
- package/dist/plugins/rsc-reference-validation-normalizer.d.ts +12 -0
- package/dist/plugins/rsc-reference-validation-normalizer.js +48 -0
- package/dist/plugins/sass.d.ts +17 -1
- package/dist/plugins/sass.js +74 -1
- package/dist/plugins/styled-jsx.d.ts +16 -0
- package/dist/plugins/styled-jsx.js +149 -0
- package/dist/routing/app-route-graph.js +22 -16
- package/dist/routing/file-matcher.d.ts +8 -1
- package/dist/routing/file-matcher.js +15 -3
- package/dist/server/app-browser-entry.js +46 -14
- package/dist/server/app-browser-navigation-controller.js +1 -1
- package/dist/server/app-browser-server-action-client.js +5 -3
- package/dist/server/app-browser-state.d.ts +1 -0
- package/dist/server/app-browser-state.js +2 -2
- package/dist/server/app-browser-visible-commit.js +1 -1
- package/dist/server/app-page-boundary.js +2 -1
- package/dist/server/app-page-cache-finalizer.d.ts +1 -0
- package/dist/server/app-page-cache-finalizer.js +3 -3
- package/dist/server/app-page-cache.js +23 -6
- package/dist/server/app-page-dispatch.d.ts +1 -0
- package/dist/server/app-page-dispatch.js +25 -8
- package/dist/server/app-page-element-builder.js +1 -0
- package/dist/server/app-page-execution.js +2 -1
- package/dist/server/app-page-render-identity.d.ts +1 -0
- package/dist/server/app-page-render-identity.js +2 -1
- package/dist/server/app-page-render.d.ts +2 -0
- package/dist/server/app-page-render.js +67 -16
- package/dist/server/app-page-response.d.ts +7 -0
- package/dist/server/app-page-response.js +16 -4
- package/dist/server/app-page-stream.d.ts +2 -1
- package/dist/server/app-page-stream.js +1 -1
- package/dist/server/app-route-handler-execution.js +1 -1
- package/dist/server/app-route-tree-prefetch.d.ts +43 -0
- package/dist/server/app-route-tree-prefetch.js +187 -0
- package/dist/server/app-router-entry.js +1 -1
- package/dist/server/app-rsc-cache-busting.d.ts +2 -1
- package/dist/server/app-rsc-cache-busting.js +9 -4
- package/dist/server/app-rsc-handler.d.ts +6 -0
- package/dist/server/app-rsc-handler.js +89 -11
- package/dist/server/app-rsc-render-mode.d.ts +3 -2
- package/dist/server/app-rsc-render-mode.js +4 -1
- package/dist/server/app-rsc-request-normalization.d.ts +3 -4
- package/dist/server/app-rsc-request-normalization.js +4 -5
- package/dist/server/app-server-action-execution.js +4 -3
- package/dist/server/app-ssr-entry.js +3 -3
- package/dist/server/cache-control.d.ts +3 -1
- package/dist/server/cache-control.js +13 -1
- package/dist/server/dev-server.d.ts +11 -1
- package/dist/server/dev-server.js +78 -20
- package/dist/server/headers.d.ts +6 -2
- package/dist/server/headers.js +11 -5
- package/dist/server/isr-cache.d.ts +2 -1
- package/dist/server/isr-cache.js +7 -3
- package/dist/server/pages-data-route.d.ts +4 -2
- package/dist/server/pages-data-route.js +18 -4
- package/dist/server/pages-dev-module-url.d.ts +2 -1
- package/dist/server/pages-dev-module-url.js +6 -3
- package/dist/server/pages-node-compat.d.ts +12 -1
- package/dist/server/pages-node-compat.js +50 -1
- package/dist/server/pages-page-data.d.ts +9 -0
- package/dist/server/pages-page-data.js +17 -9
- package/dist/server/pages-page-handler.js +19 -7
- package/dist/server/pages-page-response.d.ts +1 -0
- package/dist/server/pages-page-response.js +3 -2
- package/dist/server/pages-request-pipeline.d.ts +8 -6
- package/dist/server/pages-request-pipeline.js +30 -6
- package/dist/server/pages-router-entry.js +1 -1
- package/dist/server/prod-server.d.ts +4 -2
- package/dist/server/prod-server.js +38 -22
- package/dist/server/request-pipeline.js +1 -1
- package/dist/shims/cache-handler.js +8 -1
- package/dist/shims/cache.js +3 -0
- package/dist/shims/fetch-cache.d.ts +3 -1
- package/dist/shims/fetch-cache.js +77 -52
- package/dist/shims/form.d.ts +1 -1
- package/dist/shims/internal/app-prefetch-fetch-queue.d.ts +10 -0
- package/dist/shims/internal/app-prefetch-fetch-queue.js +61 -0
- package/dist/shims/internal/hybrid-client-route-owner.d.ts +2 -1
- package/dist/shims/internal/hybrid-client-route-owner.js +34 -1
- package/dist/shims/internal/pages-data-fetch-dedup.d.ts +2 -1
- package/dist/shims/internal/pages-data-fetch-dedup.js +13 -2
- package/dist/shims/internal/pages-data-target.d.ts +8 -3
- package/dist/shims/internal/pages-data-target.js +9 -4
- package/dist/shims/link.d.ts +1 -1
- package/dist/shims/link.js +179 -42
- package/dist/shims/navigation.d.ts +15 -3
- package/dist/shims/navigation.js +202 -39
- package/dist/shims/navigation.react-server.js +1 -1
- package/dist/shims/request-context.js +18 -0
- package/dist/shims/router.js +31 -15
- package/dist/shims/unified-request-context.js +1 -0
- package/dist/typegen.js +1 -1
- package/dist/utils/middleware-request-headers.js +1 -1
- package/dist/utils/protocol-headers.d.ts +7 -1
- package/dist/utils/protocol-headers.js +7 -1
- package/dist/utils/vite-version.d.ts +2 -0
- package/dist/utils/vite-version.js +11 -1
- 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
|
-
|
|
431
|
-
const revalidateSeconds =
|
|
432
|
-
|
|
433
|
-
const
|
|
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: {
|
package/dist/plugins/fonts.js
CHANGED
|
@@ -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.
|
|
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 };
|
package/dist/plugins/sass.d.ts
CHANGED
|
@@ -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 };
|
package/dist/plugins/sass.js
CHANGED
|
@@ -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)
|
|
443
|
+
const relativePath = normalizePathSeparators(path.relative(appDir, filePath));
|
|
444
444
|
const parsedPath = path.parse(relativePath);
|
|
445
|
-
const withoutExtension = path.join(parsedPath.dir, parsedPath.name)
|
|
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
|
|
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.
|
|
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.
|
|
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`
|
|
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 =
|
|
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`
|
|
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, ...
|
|
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 [
|
|
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);
|