vinext 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -1
- package/THIRD_PARTY_LICENSES.md +26 -0
- package/dist/build/app-page-static-eligibility.d.ts +15 -0
- package/dist/build/app-page-static-eligibility.js +82 -0
- package/dist/build/lifecycle.js +10 -10
- package/dist/build/pages-client-assets-module.js +6 -1
- package/dist/build/precompress.js +1 -1
- package/dist/build/prerender-paths.js +6 -85
- package/dist/build/prerender.d.ts +8 -1
- package/dist/build/prerender.js +167 -46
- package/dist/build/run-prerender.js +4 -3
- package/dist/build/server-entry.d.ts +2 -0
- package/dist/build/server-entry.js +7 -1
- package/dist/build/server-manifest.d.ts +7 -0
- package/dist/build/server-manifest.js +11 -1
- package/dist/cache/cache-adapters-virtual.d.ts +7 -1
- package/dist/cache/cache-adapters-virtual.js +3 -3
- package/dist/client/browser-globals.d.ts +4 -0
- package/dist/client/browser-globals.js +5 -0
- package/dist/client/navigation-runtime.d.ts +11 -1
- package/dist/client/navigation-runtime.js +3 -3
- package/dist/config/config-matchers.js +3 -1
- package/dist/config/dotenv.js +14 -3
- package/dist/config/next-config.d.ts +7 -0
- package/dist/config/next-config.js +18 -3
- package/dist/entries/app-rsc-entry.d.ts +2 -0
- package/dist/entries/app-rsc-entry.js +67 -45
- package/dist/index.d.ts +4 -1
- package/dist/index.js +200 -100
- package/dist/init-cloudflare.d.ts +1 -1
- package/dist/init-cloudflare.js +6 -6
- package/dist/init-platform.js +1 -1
- package/dist/init.js +4 -2
- package/dist/plugins/cache-flight-codec.d.ts +19 -0
- package/dist/plugins/cache-flight-codec.js +68 -0
- package/dist/plugins/commonjs-esm-facade.d.ts +35 -0
- package/dist/plugins/commonjs-esm-facade.js +80 -0
- package/dist/plugins/commonjs-syntax.d.ts +16 -0
- package/dist/plugins/commonjs-syntax.js +22 -0
- package/dist/plugins/fonts.d.ts +8 -11
- package/dist/plugins/fonts.js +71 -34
- package/dist/plugins/ignore-dynamic-requests.d.ts +11 -0
- package/dist/plugins/ignore-dynamic-requests.js +54 -3
- package/dist/plugins/import-meta-url.js +6 -6
- package/dist/plugins/og-asset-ownership.d.ts +2 -0
- package/dist/plugins/og-asset-ownership.js +24 -13
- package/dist/plugins/og-assets.d.ts +18 -0
- package/dist/plugins/og-assets.js +50 -8
- package/dist/plugins/og-harfbuzz.js +38 -27
- package/dist/plugins/optimize-imports.js +2 -1
- package/dist/plugins/require-context.d.ts +1 -0
- package/dist/plugins/require-context.js +28 -1
- package/dist/plugins/scan-build-css.d.ts +12 -0
- package/dist/plugins/scan-build-css.js +39 -0
- package/dist/plugins/strip-server-exports.d.ts +2 -0
- package/dist/plugins/strip-server-exports.js +3 -1
- package/dist/plugins/transform-result.d.ts +18 -0
- package/dist/plugins/transform-result.js +23 -2
- package/dist/plugins/typeof-window.d.ts +10 -0
- package/dist/plugins/typeof-window.js +33 -9
- package/dist/routing/app-route-graph.d.ts +0 -1
- package/dist/routing/app-route-graph.js +2 -4
- package/dist/routing/app-route-ids.d.ts +3 -0
- package/dist/routing/app-route-ids.js +6 -0
- package/dist/routing/file-matcher.d.ts +1 -0
- package/dist/routing/file-matcher.js +1 -1
- package/dist/server/api-handler.js +17 -7
- package/dist/server/app-bfcache-id.d.ts +1 -0
- package/dist/server/app-bfcache-id.js +7 -1
- package/dist/server/app-bfcache-identity.js +1 -2
- package/dist/server/app-browser-entry.js +15 -9
- package/dist/server/app-browser-history-controller.d.ts +2 -0
- package/dist/server/app-browser-history-controller.js +8 -0
- package/dist/server/app-browser-hydration.d.ts +1 -1
- package/dist/server/app-browser-hydration.js +2 -2
- package/dist/server/app-browser-interception-context.js +26 -2
- package/dist/server/app-browser-navigation-controller.d.ts +7 -1
- package/dist/server/app-browser-navigation-controller.js +94 -11
- package/dist/server/app-browser-state.js +1 -1
- package/dist/server/app-browser-stream.d.ts +1 -1
- package/dist/server/app-fallback-renderer.d.ts +2 -0
- package/dist/server/app-fallback-renderer.js +1 -0
- package/dist/server/app-history-state.d.ts +3 -1
- package/dist/server/app-history-state.js +9 -5
- package/dist/server/app-middleware.js +2 -1
- package/dist/server/app-optimistic-routing.js +1 -1
- package/dist/server/app-page-boundary-render.d.ts +5 -0
- package/dist/server/app-page-boundary-render.js +6 -4
- package/dist/server/app-page-cache-finalizer.d.ts +11 -0
- package/dist/server/app-page-cache-finalizer.js +41 -7
- package/dist/server/app-page-cache-render.d.ts +18 -0
- package/dist/server/app-page-cache-render.js +65 -44
- package/dist/server/app-page-cache.d.ts +5 -0
- package/dist/server/app-page-cache.js +27 -7
- package/dist/server/app-page-dispatch.d.ts +9 -1
- package/dist/server/app-page-dispatch.js +47 -30
- package/dist/server/app-page-element-builder.d.ts +7 -0
- package/dist/server/app-page-element-builder.js +27 -42
- package/dist/server/app-page-execution.d.ts +15 -12
- package/dist/server/app-page-execution.js +30 -40
- package/dist/server/app-page-head.d.ts +5 -0
- package/dist/server/app-page-head.js +9 -3
- package/dist/server/app-page-probe.d.ts +0 -109
- package/dist/server/app-page-probe.js +3 -136
- package/dist/server/app-page-render-identity.d.ts +0 -1
- package/dist/server/app-page-render-identity.js +1 -1
- package/dist/server/app-page-render-observation.js +2 -2
- package/dist/server/app-page-render.d.ts +13 -5
- package/dist/server/app-page-render.js +219 -118
- package/dist/server/app-page-request.d.ts +0 -1
- package/dist/server/app-page-request.js +1 -2
- package/dist/server/app-page-response.d.ts +4 -3
- package/dist/server/app-page-response.js +4 -3
- package/dist/server/app-page-route-wiring.d.ts +17 -5
- package/dist/server/app-page-route-wiring.js +55 -33
- package/dist/server/app-page-rsc-render-status.d.ts +48 -0
- package/dist/server/app-page-rsc-render-status.js +54 -0
- package/dist/server/app-page-search-params-observation.js +1 -1
- package/dist/server/app-page-stream.d.ts +15 -8
- package/dist/server/app-page-stream.js +7 -1
- package/dist/server/app-page-tracing.js +2 -1
- package/dist/server/app-pages-bridge.js +3 -2
- package/dist/server/app-render-abort-error.d.ts +3 -0
- package/dist/server/app-render-abort-error.js +8 -0
- package/dist/server/app-request-stage-dispatch.js +1 -1
- package/dist/server/app-request-stage-independent-entry.js +8 -11
- package/dist/server/app-response-header-provenance.d.ts +2 -0
- package/dist/server/app-response-header-provenance.js +10 -4
- package/dist/server/app-route-handler-cache.d.ts +2 -1
- package/dist/server/app-route-handler-cache.js +11 -7
- package/dist/server/app-route-handler-dispatch.js +11 -4
- package/dist/server/app-route-handler-execution.d.ts +9 -1
- package/dist/server/app-route-handler-execution.js +28 -13
- package/dist/server/app-route-handler-policy.js +2 -2
- package/dist/server/app-route-handler-response.d.ts +1 -1
- package/dist/server/app-route-handler-response.js +14 -5
- package/dist/server/app-router-entry.d.ts +2 -1
- package/dist/server/app-router-entry.js +8 -12
- package/dist/server/app-rsc-errors.d.ts +0 -1
- package/dist/server/app-rsc-errors.js +2 -6
- package/dist/server/app-rsc-handler.d.ts +5 -0
- package/dist/server/app-rsc-handler.js +229 -102
- package/dist/server/app-rsc-request-normalization.d.ts +4 -2
- package/dist/server/app-rsc-request-normalization.js +7 -4
- package/dist/server/app-rsc-response-finalizer.js +4 -1
- package/dist/server/app-rsc-response-stage.d.ts +1 -1
- package/dist/server/app-rsc-response-stage.js +6 -4
- package/dist/server/app-rsc-route-matching.d.ts +9 -1
- package/dist/server/app-rsc-route-matching.js +37 -20
- package/dist/server/app-server-action-execution.d.ts +2 -1
- package/dist/server/app-server-action-execution.js +1 -1
- package/dist/server/app-ssr-entry.d.ts +3 -2
- package/dist/server/app-ssr-entry.js +9 -7
- package/dist/server/app-ssr-search-params-gate.js +1 -1
- package/dist/server/app-ssr-stream.d.ts +6 -1
- package/dist/server/app-ssr-stream.js +35 -16
- package/dist/server/app-worker-stages.d.ts +2 -2
- package/dist/server/app-worker-stages.js +3 -3
- package/dist/server/cache-control.js +3 -11
- package/dist/server/cacheability-request.js +56 -52
- package/dist/server/config-headers.js +3 -1
- package/dist/server/dev-server.js +23 -5
- package/dist/server/headers.d.ts +16 -0
- package/dist/server/headers.js +19 -1
- package/dist/server/http-error-responses.js +1 -0
- package/dist/server/image-optimization.d.ts +16 -0
- package/dist/server/image-optimization.js +26 -6
- package/dist/server/isr-cache.d.ts +2 -0
- package/dist/server/isr-cache.js +8 -3
- package/dist/server/metadata-route-response.js +94 -37
- package/dist/server/middleware-matcher-pattern.js +8 -4
- package/dist/server/middleware-response-headers.js +2 -0
- package/dist/server/multi-stage.d.ts +8 -0
- package/dist/server/navigation-planner.js +1 -1
- package/dist/server/node-response-signal.d.ts +16 -0
- package/dist/server/node-response-signal.js +26 -0
- package/dist/server/open-redirect.d.ts +47 -0
- package/dist/server/open-redirect.js +74 -1
- package/dist/server/pages-api-route.js +8 -2
- package/dist/server/pages-asset-tags.d.ts +1 -0
- package/dist/server/pages-asset-tags.js +73 -22
- package/dist/server/pages-client-assets.d.ts +2 -0
- package/dist/server/pages-page-data.d.ts +6 -1
- package/dist/server/pages-page-data.js +71 -65
- package/dist/server/pages-page-handler.js +50 -15
- package/dist/server/pages-page-response.js +3 -1
- package/dist/server/pages-request-pipeline.d.ts +2 -1
- package/dist/server/pages-request-pipeline.js +20 -5
- package/dist/server/pages-request-stage-entry.js +14 -8
- package/dist/server/prerender-manifest.d.ts +6 -0
- package/dist/server/prerender-manifest.js +1 -1
- package/dist/server/prod-server.d.ts +8 -7
- package/dist/server/prod-server.js +152 -78
- package/dist/server/request-pipeline.d.ts +20 -8
- package/dist/server/request-pipeline.js +21 -7
- package/dist/server/response-compression.d.ts +43 -0
- package/dist/server/response-compression.js +111 -0
- package/dist/server/response-stage-cacheability.js +1 -1
- package/dist/server/seed-cache.js +10 -7
- package/dist/server/socket-error-backstop.d.ts +14 -0
- package/dist/server/socket-error-backstop.js +27 -4
- package/dist/server/worker-revalidation-context.d.ts +2 -1
- package/dist/server/worker-revalidation-context.js +1 -0
- package/dist/server/workers-tracing.d.ts +17 -0
- package/dist/server/workers-tracing.js +48 -8
- package/dist/shims/cache-adapter-instantiate.d.ts +50 -0
- package/dist/shims/cache-adapter-instantiate.js +118 -0
- package/dist/shims/cache-callable-runtime.d.ts +2 -40
- package/dist/shims/cache-callable-runtime.js +50 -168
- package/dist/shims/cache-flight-arguments.d.ts +22 -0
- package/dist/shims/cache-flight-arguments.js +56 -0
- package/dist/shims/cache-request-state.d.ts +18 -14
- package/dist/shims/cache-request-state.js +33 -21
- package/dist/shims/cache-runtime.d.ts +6 -19
- package/dist/shims/cache-runtime.js +168 -226
- package/dist/shims/cache.d.ts +3 -2
- package/dist/shims/cache.js +8 -5
- package/dist/shims/cacheability-classification.d.ts +8 -1
- package/dist/shims/cacheability-classification.js +2 -2
- package/dist/shims/cdn-cache.d.ts +10 -1
- package/dist/shims/cdn-cache.js +16 -9
- package/dist/shims/error-boundary-navigation.d.ts +7 -0
- package/dist/shims/error-boundary-navigation.js +19 -1
- package/dist/shims/error-boundary.d.ts +10 -10
- package/dist/shims/error-boundary.js +26 -7
- package/dist/shims/error.d.ts +4 -18
- package/dist/shims/error.js +17 -10
- package/dist/shims/error.react-server.d.ts +4 -6
- package/dist/shims/error.react-server.js +4 -1
- package/dist/shims/fetch-cache.d.ts +6 -0
- package/dist/shims/fetch-cache.js +23 -4
- package/dist/shims/form.js +1 -1
- package/dist/shims/headers.d.ts +19 -69
- package/dist/shims/headers.js +82 -183
- package/dist/shims/internal/app-page-props-cache-key.d.ts +5 -2
- package/dist/shims/internal/app-page-props-cache-key.js +24 -15
- package/dist/shims/internal/cache-context-accessor.d.ts +16 -0
- package/dist/shims/internal/cache-context-accessor.js +15 -0
- package/dist/shims/internal/headers-state.d.ts +73 -0
- package/dist/shims/internal/headers-state.js +157 -0
- package/dist/shims/metadata.js +3 -3
- package/dist/shims/navigation-errors.d.ts +6 -0
- package/dist/shims/navigation-errors.js +11 -1
- package/dist/shims/navigation.d.ts +7 -0
- package/dist/shims/navigation.js +16 -6
- package/dist/shims/request-context.d.ts +9 -0
- package/dist/shims/request-state-types.d.ts +2 -1
- package/dist/shims/root-params.js +1 -1
- package/dist/shims/server.js +52 -2
- package/dist/shims/thenable-params.d.ts +4 -0
- package/dist/shims/thenable-params.js +25 -3
- package/dist/shims/unified-request-context.d.ts +1 -1
- package/dist/shims/unified-request-context.js +1 -0
- package/dist/utils/encode-cache-tag.d.ts +1 -0
- package/dist/utils/encode-cache-tag.js +11 -1
- package/dist/utils/mdx-scan.d.ts +5 -0
- package/dist/utils/mdx-scan.js +8 -1
- package/dist/utils/regex-safety.d.ts +10 -0
- package/dist/utils/regex-safety.js +70 -18
- package/dist/utils/vite-cli-invocation.js +8 -1
- package/package.json +8 -4
- package/dist/server/metadata-route-cache-policy.d.ts +0 -5
- package/dist/server/metadata-route-cache-policy.js +0 -6
package/dist/init-cloudflare.js
CHANGED
|
@@ -26,7 +26,6 @@ const CACHE_METADATA_EXPORT = {
|
|
|
26
26
|
};
|
|
27
27
|
const CTX_EXPORTS_DEFAULT_DATE = "2025-11-17";
|
|
28
28
|
function validateCloudflarePlatformSetup(context, cloudflare) {
|
|
29
|
-
if (cloudflare.cdnCache === "static-assets" && !context.isAppRouter) throw new Error("The Static Assets cache currently requires an App Router project.");
|
|
30
29
|
if (!cloudflare.legacyWrangler) {
|
|
31
30
|
const existingWrangler = [
|
|
32
31
|
"wrangler.toml",
|
|
@@ -107,7 +106,7 @@ function setupCloudflarePlatform(context, cloudflare) {
|
|
|
107
106
|
generatedViteConfig = true;
|
|
108
107
|
} else skippedViteConfig = true;
|
|
109
108
|
} else {
|
|
110
|
-
const configContent = context.isAppRouter ? generateAppRouterViteConfig(projectInfo, cloudflare, imagesBinding, versionMetadataBinding, false, assetsBinding, assetsDirectory, context.hasCssModules) : generatePagesRouterViteConfig(projectInfo, cloudflare, imagesBinding, versionMetadataBinding, false, context.hasCssModules);
|
|
109
|
+
const configContent = context.isAppRouter ? generateAppRouterViteConfig(projectInfo, cloudflare, imagesBinding, versionMetadataBinding, false, assetsBinding, assetsDirectory, context.hasCssModules) : generatePagesRouterViteConfig(projectInfo, cloudflare, imagesBinding, versionMetadataBinding, false, context.hasCssModules, assetsBinding, assetsDirectory);
|
|
111
110
|
fs.writeFileSync(path.join(context.root, "vite.config.ts"), configContent, "utf-8");
|
|
112
111
|
generatedViteConfig = true;
|
|
113
112
|
}
|
|
@@ -799,7 +798,8 @@ ${plugins.join("\n")}
|
|
|
799
798
|
`;
|
|
800
799
|
}
|
|
801
800
|
/** Generate vite.config.ts for Pages Router */
|
|
802
|
-
function generatePagesRouterViteConfig(info, options = DEFAULT_CLOUDFLARE_INIT_OPTIONS, imagesBinding = "IMAGES", versionMetadataBinding = DEFAULT_VERSION_METADATA_BINDING, serviceBinding = false, hasCssModules = false) {
|
|
801
|
+
function generatePagesRouterViteConfig(info, options = DEFAULT_CLOUDFLARE_INIT_OPTIONS, imagesBinding = "IMAGES", versionMetadataBinding = DEFAULT_VERSION_METADATA_BINDING, serviceBinding = false, hasCssModules = false, assetsBinding = "ASSETS", assetsDirectory = "dist/client") {
|
|
802
|
+
const legacyStaticAssets = options.legacyWrangler && options.cdnCache === "static-assets";
|
|
803
803
|
const imports = [
|
|
804
804
|
`import { defineConfig } from "vite";`,
|
|
805
805
|
`import vinext from "vinext";`,
|
|
@@ -816,9 +816,9 @@ function generatePagesRouterViteConfig(info, options = DEFAULT_CLOUDFLARE_INIT_O
|
|
|
816
816
|
return `${imports.join("\n")}
|
|
817
817
|
|
|
818
818
|
export default defineConfig({
|
|
819
|
-
plugins: [
|
|
820
|
-
${hasCssModules ? " patchCssModules({ exportMode: \"default\" }),\n" : ""} ${vinextExpression(options, "vinext", "imagesOptimizer", imagesBinding, versionMetadataBinding).replace(/\n/g, "\n ")},
|
|
821
|
-
cloudflare(${serviceBinding ? "{ auxiliaryWorkers: [{ config: responseStoreServiceBinding }] }" : ""}),
|
|
819
|
+
${legacyStaticAssets ? ` environments: { client: { build: { outDir: ${JSON.stringify(assetsDirectory)} } }, ssr: { build: { outDir: "dist/server" } } },\n` : ""} plugins: [
|
|
820
|
+
${hasCssModules ? " patchCssModules({ exportMode: \"default\" }),\n" : ""} ${vinextExpression(options, "vinext", "imagesOptimizer", imagesBinding, versionMetadataBinding, "responseStoreAdapter", assetsBinding, assetsDirectory).replace(/\n/g, "\n ")},
|
|
821
|
+
cloudflare(${legacyStaticAssets ? "{ viteEnvironment: { name: \"ssr\" } }" : serviceBinding ? "{ auxiliaryWorkers: [{ config: responseStoreServiceBinding }] }" : ""}),
|
|
822
822
|
],${resolveBlock}${hasCssModules ? cssModulesConfigSource() : ""}
|
|
823
823
|
});
|
|
824
824
|
`;
|
package/dist/init-platform.js
CHANGED
|
@@ -269,7 +269,7 @@ async function resolveCloudflareInitOptions(args, options = {}) {
|
|
|
269
269
|
}
|
|
270
270
|
output.write(" Please answer yes or no.\n");
|
|
271
271
|
}
|
|
272
|
-
const cdnCache = await promptChoice(selectedCdnCache, " Choose a CDN cache:\n 1. Workers Response Store (default)\n 2. Workers Cache\n 3. Data cache\n 4. Static Assets (read-only
|
|
272
|
+
const cdnCache = await promptChoice(selectedCdnCache, " Choose a CDN cache:\n 1. Workers Response Store (default)\n 2. Workers Cache\n 3. Data cache\n 4. Static Assets (read-only)\n CDN cache [1]: ", {
|
|
273
273
|
"1": "response-store",
|
|
274
274
|
"response-store": "response-store",
|
|
275
275
|
"2": "workers-cache",
|
package/dist/init.js
CHANGED
|
@@ -412,8 +412,9 @@ async function init(options) {
|
|
|
412
412
|
existingViteConfigPath = renamedPath;
|
|
413
413
|
}
|
|
414
414
|
const addedTypeModule = ensureESModule(root);
|
|
415
|
+
const deployResponseStore = platform === "cloudflare" && options.cloudflare?.cdnCache === "response-store" && (options.cloudflare.responseStoreMode ?? "service-binding") === "service-binding";
|
|
415
416
|
const addedScripts = addScripts(root, options.port, platform, {
|
|
416
|
-
deployResponseStore
|
|
417
|
+
deployResponseStore,
|
|
417
418
|
warmCdnCache: options.cloudflare?.warmCdnCache ?? false,
|
|
418
419
|
legacyWrangler: options.cloudflare?.legacyWrangler,
|
|
419
420
|
scriptNames: options.scriptNames
|
|
@@ -516,6 +517,7 @@ async function init(options) {
|
|
|
516
517
|
const nextSteps = [...platformSetup.nextSteps];
|
|
517
518
|
if (dependencyInstallNeedsApproval) nextSteps.push("Dependency installation is incomplete because pnpm blocked dependency build scripts:", "1. Review and approve the required build scripts:", " pnpm approve-builds", "2. Finish installing dependencies:", " pnpm install");
|
|
518
519
|
const scriptName = (name) => options.scriptNames === "standard" ? name : `${name}:vinext`;
|
|
520
|
+
const deployResponseStoreCommandStep = deployResponseStore ? ` ${pmName} run deploy:response-store Deploy Workers Response Store before the application\n` : "";
|
|
519
521
|
const deployCommandStep = platform === "cloudflare" ? ` ${pmName} run ${scriptName("deploy")} Deploy to Cloudflare Workers\n` : "";
|
|
520
522
|
const startCommandDescription = platform === "cloudflare" ? !options.cloudflare?.legacyWrangler ? "Preview the built Worker locally" : "Start the built Worker locally with Wrangler" : "Start vinext production server";
|
|
521
523
|
console.log(`
|
|
@@ -524,7 +526,7 @@ ${nextSteps.map((step) => ` ${step}`).join("\n")}${nextSteps.length > 0 ? "\n
|
|
|
524
526
|
${pmName} run ${scriptName("dev")} Start the vinext dev server
|
|
525
527
|
${pmName} run ${scriptName("build")} Build production output
|
|
526
528
|
${pmName} run ${scriptName("start")} ${startCommandDescription}
|
|
527
|
-
${deployCommandStep}${options.scriptNames === "standard" ? "" : ` ${pmName} run dev Start Next.js (still works as before)\n`}
|
|
529
|
+
${deployResponseStoreCommandStep}${deployCommandStep}${options.scriptNames === "standard" ? "" : ` ${pmName} run dev Start Next.js (still works as before)\n`}
|
|
528
530
|
`);
|
|
529
531
|
return {
|
|
530
532
|
installedDeps: [.../* @__PURE__ */ new Set([...dependencyEntriesAdded, ...devDependencyEntriesAdded])],
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { MagicStringTransformResult } from "./transform-result.js";
|
|
2
|
+
import { Plugin } from "vite";
|
|
3
|
+
//#region src/plugins/cache-flight-codec.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Version of the Flight codec package that owns `id`. plugin-rsc resolves the
|
|
6
|
+
* app's own react-server-dom-webpack when installed and its vendored copy
|
|
7
|
+
* otherwise; reading the codec file's own package.json follows that choice.
|
|
8
|
+
*/
|
|
9
|
+
export declare function readFlightCodecVersion(id: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* Backport React 19.3's root-element temporary reference branch to older Flight
|
|
12
|
+
* codecs, so a "use cache" argument like `Promise<ReactElement>` encodes as a
|
|
13
|
+
* temporary reference instead of throwing. Remove it once React 19.3 is the
|
|
14
|
+
* minimum supported version.
|
|
15
|
+
* https://github.com/facebook/react/blob/v19.3.0/packages/react-client/src/ReactFlightReplyClient.js#L486
|
|
16
|
+
*/
|
|
17
|
+
export declare function patchCacheFlightCodec(code: string, id: string, version?: string): MagicStringTransformResult | null;
|
|
18
|
+
export declare function cacheFlightCodecPlugin(): Plugin;
|
|
19
|
+
//#endregion
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import path from "../deps/.pnpm/pathslash@0.1.0/deps/pathslash/dist/index.js";
|
|
2
|
+
import { magicStringTransformResult } from "./transform-result.js";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import MagicString from "magic-string";
|
|
5
|
+
//#region src/plugins/cache-flight-codec.ts
|
|
6
|
+
const CLIENT_CODEC = /\/react-server-dom-(?:webpack|turbopack)-client\.edge\.(?:development|production)\.js$/;
|
|
7
|
+
const ELEMENT_CASE = /case REACT_ELEMENT_TYPE:[\s\S]*?throw Error\(\s*"React Element cannot be passed/;
|
|
8
|
+
/**
|
|
9
|
+
* Version of the Flight codec package that owns `id`. plugin-rsc resolves the
|
|
10
|
+
* app's own react-server-dom-webpack when installed and its vendored copy
|
|
11
|
+
* otherwise; reading the codec file's own package.json follows that choice.
|
|
12
|
+
*/
|
|
13
|
+
function readFlightCodecVersion(id) {
|
|
14
|
+
for (let dir = path.dirname(id);; dir = path.dirname(dir)) {
|
|
15
|
+
const manifest = path.join(dir, "package.json");
|
|
16
|
+
if (fs.existsSync(manifest)) {
|
|
17
|
+
const pkg = JSON.parse(fs.readFileSync(manifest, "utf8"));
|
|
18
|
+
if (/^react-server-dom-(?:webpack|turbopack)$/.test(pkg.name ?? "") && pkg.version) return pkg.version;
|
|
19
|
+
}
|
|
20
|
+
if (path.dirname(dir) === dir) throw new Error(`vinext: cannot find the react-server-dom package version for ${id}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/** React 19.3 ships the branch; 0.0.0-* experimental builds track main. */
|
|
24
|
+
function needsElementTemporaryReferenceBackport(version) {
|
|
25
|
+
const match = /^(\d+)\.(\d+)\./.exec(version);
|
|
26
|
+
if (!match || version.startsWith("0.0.0-")) return false;
|
|
27
|
+
const [major, minor] = [Number(match[1]), Number(match[2])];
|
|
28
|
+
return major < 19 || major === 19 && minor < 3;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Backport React 19.3's root-element temporary reference branch to older Flight
|
|
32
|
+
* codecs, so a "use cache" argument like `Promise<ReactElement>` encodes as a
|
|
33
|
+
* temporary reference instead of throwing. Remove it once React 19.3 is the
|
|
34
|
+
* minimum supported version.
|
|
35
|
+
* https://github.com/facebook/react/blob/v19.3.0/packages/react-client/src/ReactFlightReplyClient.js#L486
|
|
36
|
+
*/
|
|
37
|
+
function patchCacheFlightCodec(code, id, version = readFlightCodecVersion(id)) {
|
|
38
|
+
if (!needsElementTemporaryReferenceBackport(version)) return null;
|
|
39
|
+
const elementCase = ELEMENT_CASE.exec(code);
|
|
40
|
+
if (!elementCase) throw new Error(`vinext: react-server-dom-webpack ${version} (${id}) is not supported. Upgrade react, react-dom, and react-server-dom-webpack to 19.3 or later. If your app does not depend on react-server-dom-webpack directly, upgrade @vitejs/plugin-rsc to 0.5.35 or later instead.`);
|
|
41
|
+
const output = new MagicString(code);
|
|
42
|
+
output.appendLeft(elementCase.index + elementCase[0].lastIndexOf("throw Error("), "if (void 0 !== temporaryReferences && modelRoot === value) return (modelRoot = null), \"$T\";\n");
|
|
43
|
+
return magicStringTransformResult(output, {
|
|
44
|
+
hires: "boundary",
|
|
45
|
+
source: id
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
function cacheFlightCodecPlugin() {
|
|
49
|
+
const transform = {
|
|
50
|
+
filter: { id: CLIENT_CODEC },
|
|
51
|
+
handler: (code, id) => patchCacheFlightCodec(code, id)
|
|
52
|
+
};
|
|
53
|
+
return {
|
|
54
|
+
name: "vinext:cache-flight-codec",
|
|
55
|
+
enforce: "pre",
|
|
56
|
+
applyToEnvironment: (environment) => environment.name === "rsc",
|
|
57
|
+
configEnvironment(name) {
|
|
58
|
+
if (name !== "rsc") return;
|
|
59
|
+
return { optimizeDeps: { rolldownOptions: { plugins: [{
|
|
60
|
+
name: "vinext:cache-flight-codec-optimizer",
|
|
61
|
+
transform
|
|
62
|
+
}] } } };
|
|
63
|
+
},
|
|
64
|
+
transform
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
//#endregion
|
|
68
|
+
export { cacheFlightCodecPlugin, patchCacheFlightCodec, readFlightCodecVersion };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Plugin } from "vite";
|
|
2
|
+
//#region src/plugins/commonjs-esm-facade.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Removes the export facade vite-plugin-commonjs appended to `output` when the
|
|
5
|
+
* module it transformed is ESM. Returns `undefined` when there is nothing to
|
|
6
|
+
* remove.
|
|
7
|
+
*
|
|
8
|
+
* Next.js treats a module with ESM syntax as ESM: `require()` still works, but
|
|
9
|
+
* `module` / `exports` never become its exports. The plugin's analyzer is not
|
|
10
|
+
* scope-aware, so for bundled ESM that inlines CommonJS wrappers
|
|
11
|
+
* (`__commonJS((exports, module) => …)`) it exports the wrappers' assignments
|
|
12
|
+
* too, which duplicates the module's own exports. Its `require()` rewrite and
|
|
13
|
+
* its local `module` / `exports` polyfill stay, so free CommonJS assignments
|
|
14
|
+
* still run without becoming exports.
|
|
15
|
+
*
|
|
16
|
+
* Besides the facade, the plugin only prepends imports and its polyfill and
|
|
17
|
+
* rewrites `require()` calls, so the module's own top-level exports are still
|
|
18
|
+
* in `output`. Classifying the output rather than rereading the source keeps
|
|
19
|
+
* the decision tied to the exact code the plugin transformed.
|
|
20
|
+
*
|
|
21
|
+
* The facade is appended after the module's code (only dynamic-require
|
|
22
|
+
* runtimes, which have no source mappings, may follow it), so removing it
|
|
23
|
+
* leaves the plugin's source map valid.
|
|
24
|
+
*/
|
|
25
|
+
export declare function stripEsmCommonJsExportFacade(output: string): string | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* Applies {@link stripEsmCommonJsExportFacade} in the client dependency
|
|
28
|
+
* optimizer's Rolldown builds (scan and pre-bundle). vite-plugin-commonjs's
|
|
29
|
+
* pre-bundle plugin loads and converts files there without vinext's transform
|
|
30
|
+
* wrapper, so its output reaches this hook as the loaded code. That code is
|
|
31
|
+
* the optimizer's source, so the removal needs its own map for anything the
|
|
32
|
+
* plugin appended after the facade.
|
|
33
|
+
*/
|
|
34
|
+
export declare const commonJsEsmFacadeOptimizeDepsPlugin: Plugin;
|
|
35
|
+
//#endregion
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { parseAst } from "vite";
|
|
2
|
+
import MagicString from "magic-string";
|
|
3
|
+
//#region src/plugins/commonjs-esm-facade.ts
|
|
4
|
+
const EXPORT_FACADE_START = "/* [vite-plugin-commonjs] export-statement-S */";
|
|
5
|
+
const EXPORT_FACADE_END = "/* [vite-plugin-commonjs] export-statement-E */";
|
|
6
|
+
const EXPORT_KEYWORD_RE = /\bexport\b(?!-)/;
|
|
7
|
+
/** Whether `code` has a top-level ESM export statement. */
|
|
8
|
+
function hasEsmExports(code) {
|
|
9
|
+
if (!EXPORT_KEYWORD_RE.test(code)) return false;
|
|
10
|
+
let ast;
|
|
11
|
+
try {
|
|
12
|
+
ast = parseAst(code);
|
|
13
|
+
} catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
return ast.body.some((statement) => statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration" || statement.type === "ExportAllDeclaration");
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Removes the export facade vite-plugin-commonjs appended to `output` when the
|
|
20
|
+
* module it transformed is ESM. Returns `undefined` when there is nothing to
|
|
21
|
+
* remove.
|
|
22
|
+
*
|
|
23
|
+
* Next.js treats a module with ESM syntax as ESM: `require()` still works, but
|
|
24
|
+
* `module` / `exports` never become its exports. The plugin's analyzer is not
|
|
25
|
+
* scope-aware, so for bundled ESM that inlines CommonJS wrappers
|
|
26
|
+
* (`__commonJS((exports, module) => …)`) it exports the wrappers' assignments
|
|
27
|
+
* too, which duplicates the module's own exports. Its `require()` rewrite and
|
|
28
|
+
* its local `module` / `exports` polyfill stay, so free CommonJS assignments
|
|
29
|
+
* still run without becoming exports.
|
|
30
|
+
*
|
|
31
|
+
* Besides the facade, the plugin only prepends imports and its polyfill and
|
|
32
|
+
* rewrites `require()` calls, so the module's own top-level exports are still
|
|
33
|
+
* in `output`. Classifying the output rather than rereading the source keeps
|
|
34
|
+
* the decision tied to the exact code the plugin transformed.
|
|
35
|
+
*
|
|
36
|
+
* The facade is appended after the module's code (only dynamic-require
|
|
37
|
+
* runtimes, which have no source mappings, may follow it), so removing it
|
|
38
|
+
* leaves the plugin's source map valid.
|
|
39
|
+
*/
|
|
40
|
+
function stripEsmCommonJsExportFacade(output) {
|
|
41
|
+
const facade = findEsmCommonJsExportFacade(output);
|
|
42
|
+
return facade && output.slice(0, facade[0]) + output.slice(facade[1]);
|
|
43
|
+
}
|
|
44
|
+
/** The `[start, end)` range of the facade {@link stripEsmCommonJsExportFacade} removes. */
|
|
45
|
+
function findEsmCommonJsExportFacade(output) {
|
|
46
|
+
const start = output.lastIndexOf(EXPORT_FACADE_START);
|
|
47
|
+
if (start === -1) return void 0;
|
|
48
|
+
const end = output.indexOf(EXPORT_FACADE_END, start);
|
|
49
|
+
if (end === -1) return void 0;
|
|
50
|
+
const facadeEnd = end + 47;
|
|
51
|
+
return hasEsmExports(output.slice(0, start) + output.slice(facadeEnd)) ? [start, facadeEnd] : void 0;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Applies {@link stripEsmCommonJsExportFacade} in the client dependency
|
|
55
|
+
* optimizer's Rolldown builds (scan and pre-bundle). vite-plugin-commonjs's
|
|
56
|
+
* pre-bundle plugin loads and converts files there without vinext's transform
|
|
57
|
+
* wrapper, so its output reaches this hook as the loaded code. That code is
|
|
58
|
+
* the optimizer's source, so the removal needs its own map for anything the
|
|
59
|
+
* plugin appended after the facade.
|
|
60
|
+
*/
|
|
61
|
+
const commonJsEsmFacadeOptimizeDepsPlugin = {
|
|
62
|
+
name: "vinext:commonjs-esm-facade:optimize-deps",
|
|
63
|
+
transform: {
|
|
64
|
+
filter: { code: { include: EXPORT_FACADE_START } },
|
|
65
|
+
handler(code, id) {
|
|
66
|
+
const facade = findEsmCommonJsExportFacade(code);
|
|
67
|
+
if (!facade) return null;
|
|
68
|
+
const output = new MagicString(code).remove(facade[0], facade[1]);
|
|
69
|
+
return {
|
|
70
|
+
code: output.toString(),
|
|
71
|
+
map: output.generateMap({
|
|
72
|
+
source: id,
|
|
73
|
+
hires: "boundary"
|
|
74
|
+
})
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
//#endregion
|
|
80
|
+
export { commonJsEsmFacadeOptimizeDepsPlugin, stripEsmCommonJsExportFacade };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region src/plugins/commonjs-syntax.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Conservative source-level superset of what vite-plugin-commonjs rewrites,
|
|
4
|
+
* as a transform `filter.code.include` list (a module matches when any
|
|
5
|
+
* pattern does; strings match as substrings).
|
|
6
|
+
*
|
|
7
|
+
* Its analyzer only acts on calls whose callee is the identifier `require` and
|
|
8
|
+
* on assignments to a member of the identifiers `module` / `exports`, and the
|
|
9
|
+
* transform returns nothing when neither is present. Text inside strings or
|
|
10
|
+
* comments can produce false positives (the module is then analyzed as
|
|
11
|
+
* before), and identifiers written with unicode escapes are never ruled out.
|
|
12
|
+
*/
|
|
13
|
+
export declare const COMMONJS_SYNTAX_CODE_FILTER: (string | RegExp)[];
|
|
14
|
+
/** Whether `code` passes {@link COMMONJS_SYNTAX_CODE_FILTER}. */
|
|
15
|
+
export declare function mayContainCommonJsSyntax(code: string): boolean;
|
|
16
|
+
//#endregion
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conservative source-level superset of what vite-plugin-commonjs rewrites,
|
|
3
|
+
* as a transform `filter.code.include` list (a module matches when any
|
|
4
|
+
* pattern does; strings match as substrings).
|
|
5
|
+
*
|
|
6
|
+
* Its analyzer only acts on calls whose callee is the identifier `require` and
|
|
7
|
+
* on assignments to a member of the identifiers `module` / `exports`, and the
|
|
8
|
+
* transform returns nothing when neither is present. Text inside strings or
|
|
9
|
+
* comments can produce false positives (the module is then analyzed as
|
|
10
|
+
* before), and identifiers written with unicode escapes are never ruled out.
|
|
11
|
+
*/
|
|
12
|
+
const COMMONJS_SYNTAX_CODE_FILTER = [
|
|
13
|
+
/(?<![\w$])require(?![\w$])[\s)]*(?:\/[*/]|\(|\?\.)/,
|
|
14
|
+
"\\u",
|
|
15
|
+
/(?<![\w$]|(?<!\.)\.)(?:module|exports)(?![\w$])[\s)]*(?:\/[*/]|\??\.|\[)/
|
|
16
|
+
];
|
|
17
|
+
/** Whether `code` passes {@link COMMONJS_SYNTAX_CODE_FILTER}. */
|
|
18
|
+
function mayContainCommonJsSyntax(code) {
|
|
19
|
+
return COMMONJS_SYNTAX_CODE_FILTER.some((pattern) => typeof pattern === "string" ? code.includes(pattern) : pattern.test(code));
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
export { COMMONJS_SYNTAX_CODE_FILTER, mayContainCommonJsSyntax };
|
package/dist/plugins/fonts.d.ts
CHANGED
|
@@ -3,9 +3,10 @@ import { Plugin } from "vite";
|
|
|
3
3
|
export declare const VIRTUAL_GOOGLE_FONTS = "virtual:vinext-google-fonts";
|
|
4
4
|
export declare const RESOLVED_VIRTUAL_GOOGLE_FONTS: string;
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Point the `@font-face { src: url(...) }` references of cached Google Fonts
|
|
7
|
+
* CSS at the served URL the plugin's `writeBundle` hook copies the font
|
|
8
|
+
* files to (and the dev middleware serves them from), by replacing
|
|
9
|
+
* `CACHED_FONT_DIR_TOKEN` with `/<assetsDir>/_vinext_fonts`.
|
|
9
10
|
*
|
|
10
11
|
* This is called once per transform, before the CSS string is embedded in
|
|
11
12
|
* the bundle as `_vinext.font.selfHostedCSS`. Every downstream consumer reads
|
|
@@ -14,9 +15,9 @@ export declare const RESOLVED_VIRTUAL_GOOGLE_FONTS: string;
|
|
|
14
15
|
* in `shims/font-google-base.ts`), and the HTTP `Link:` response header
|
|
15
16
|
* (via `buildAppPageFontLinkHeader` in `server/app-page-execution.ts`).
|
|
16
17
|
*
|
|
17
|
-
*
|
|
18
|
-
* (
|
|
19
|
-
*
|
|
18
|
+
* The token is substituted with plain split/join: it is a fixed string
|
|
19
|
+
* vinext wrote itself, so nothing about the checkout's path (spaces, quotes,
|
|
20
|
+
* parentheses, regex metacharacters, Windows separators) is parsed here.
|
|
20
21
|
*
|
|
21
22
|
* `assetsDir` must match whatever Vite has resolved for
|
|
22
23
|
* `build.assetsDir` on the client environment — otherwise the embedded
|
|
@@ -26,12 +27,8 @@ export declare const RESOLVED_VIRTUAL_GOOGLE_FONTS: string;
|
|
|
26
27
|
* passes the resolved value through from plugin state. The default is
|
|
27
28
|
* kept only so the exported helper can be driven directly from unit
|
|
28
29
|
* tests without synthesizing a full plugin context.
|
|
29
|
-
*
|
|
30
|
-
* Uses split/join rather than regex because `cacheDir` is an absolute
|
|
31
|
-
* filesystem path that may contain regex metacharacters on unusual
|
|
32
|
-
* filesystems.
|
|
33
30
|
*/
|
|
34
|
-
export declare function _rewriteCachedFontCssToServedUrls(css: string,
|
|
31
|
+
export declare function _rewriteCachedFontCssToServedUrls(css: string, assetsDir?: string): string;
|
|
35
32
|
/**
|
|
36
33
|
* Safely parse a static JS object literal string into a plain object.
|
|
37
34
|
* Uses Vite's parseAst (Rollup/acorn) so no code is ever evaluated.
|
package/dist/plugins/fonts.js
CHANGED
|
@@ -43,15 +43,15 @@ const GOOGLE_FONT_UTILITY_EXPORTS = /* @__PURE__ */ new Set([
|
|
|
43
43
|
/**
|
|
44
44
|
* Served URL prefix for self-hosted Google Font files.
|
|
45
45
|
*
|
|
46
|
-
* `fetchAndCacheFont()` downloads
|
|
46
|
+
* `fetchAndCacheFont()` downloads font files into `<root>/.vinext/fonts/`
|
|
47
47
|
* and writes an `@font-face` CSS snippet whose `src: url(...)` references
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
48
|
+
* name each file relative to that cache directory, behind
|
|
49
|
+
* `CACHED_FONT_DIR_TOKEN`. Before the CSS is embedded in the bundle as
|
|
50
|
+
* `_vinext.font.selfHostedCSS`, `_rewriteCachedFontCssToServedUrls()`
|
|
51
|
+
* replaces the token with this URL prefix, and the matching `writeBundle`
|
|
52
|
+
* hook in `createGoogleFontsPlugin` copies the font files into
|
|
53
|
+
* `<clientOutDir>/<assetsDir>/_vinext_fonts/` so the URL actually resolves
|
|
54
|
+
* against the origin at request time.
|
|
55
55
|
*
|
|
56
56
|
* The leading `_` keeps the namespace distinct from Vite's content-hashed
|
|
57
57
|
* asset names (which are emitted flat into `<assetsDir>/`) and from any
|
|
@@ -59,6 +59,25 @@ const GOOGLE_FONT_UTILITY_EXPORTS = /* @__PURE__ */ new Set([
|
|
|
59
59
|
*/
|
|
60
60
|
const VINEXT_FONT_URL_NAMESPACE = "_vinext_fonts";
|
|
61
61
|
const MAX_GOOGLE_FONTS_ERROR_BODY_LENGTH = 500;
|
|
62
|
+
/**
|
|
63
|
+
* Stands in for the font cache directory in every `url()` of a cached
|
|
64
|
+
* stylesheet, so the file on disk holds no absolute path and reads the same
|
|
65
|
+
* wherever the checkout's `.vinext/` ends up: a moved or copied project, a
|
|
66
|
+
* CI cache restored into another workspace, or a Docker build that copies
|
|
67
|
+
* in a host's `.vinext/`. Next.js has no such cache (its Google font loader
|
|
68
|
+
* keeps fetched CSS in memory and emits `/_next/static/media` URLs), so the
|
|
69
|
+
* parity target is output that does not depend on where the cache was
|
|
70
|
+
* written.
|
|
71
|
+
*/
|
|
72
|
+
const CACHED_FONT_DIR_TOKEN = "__VINEXT_FONT_CACHE_DIR__";
|
|
73
|
+
/**
|
|
74
|
+
* Cached stylesheet whose `url()` references use `CACHED_FONT_DIR_TOKEN`.
|
|
75
|
+
* The suffix ties the cache to that format: a `style.css` written by an
|
|
76
|
+
* earlier vinext holds the writing checkout's absolute paths, which would
|
|
77
|
+
* 404 once embedded, so it is never read and the font is fetched again.
|
|
78
|
+
* Change the suffix whenever the cached CSS format changes.
|
|
79
|
+
*/
|
|
80
|
+
const CACHED_FONT_CSS_FILE = "style-q7f3kd.css";
|
|
62
81
|
function formatGoogleFontsErrorBody(body) {
|
|
63
82
|
const trimmed = body.trim();
|
|
64
83
|
if (!trimmed) return "(empty response body)";
|
|
@@ -67,9 +86,10 @@ function formatGoogleFontsErrorBody(body) {
|
|
|
67
86
|
return `${trimmed.slice(0, MAX_GOOGLE_FONTS_ERROR_BODY_LENGTH)}\n... (truncated ${omitted} characters)`;
|
|
68
87
|
}
|
|
69
88
|
/**
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
89
|
+
* Point the `@font-face { src: url(...) }` references of cached Google Fonts
|
|
90
|
+
* CSS at the served URL the plugin's `writeBundle` hook copies the font
|
|
91
|
+
* files to (and the dev middleware serves them from), by replacing
|
|
92
|
+
* `CACHED_FONT_DIR_TOKEN` with `/<assetsDir>/_vinext_fonts`.
|
|
73
93
|
*
|
|
74
94
|
* This is called once per transform, before the CSS string is embedded in
|
|
75
95
|
* the bundle as `_vinext.font.selfHostedCSS`. Every downstream consumer reads
|
|
@@ -78,9 +98,9 @@ function formatGoogleFontsErrorBody(body) {
|
|
|
78
98
|
* in `shims/font-google-base.ts`), and the HTTP `Link:` response header
|
|
79
99
|
* (via `buildAppPageFontLinkHeader` in `server/app-page-execution.ts`).
|
|
80
100
|
*
|
|
81
|
-
*
|
|
82
|
-
* (
|
|
83
|
-
*
|
|
101
|
+
* The token is substituted with plain split/join: it is a fixed string
|
|
102
|
+
* vinext wrote itself, so nothing about the checkout's path (spaces, quotes,
|
|
103
|
+
* parentheses, regex metacharacters, Windows separators) is parsed here.
|
|
84
104
|
*
|
|
85
105
|
* `assetsDir` must match whatever Vite has resolved for
|
|
86
106
|
* `build.assetsDir` on the client environment — otherwise the embedded
|
|
@@ -90,16 +110,9 @@ function formatGoogleFontsErrorBody(body) {
|
|
|
90
110
|
* passes the resolved value through from plugin state. The default is
|
|
91
111
|
* kept only so the exported helper can be driven directly from unit
|
|
92
112
|
* tests without synthesizing a full plugin context.
|
|
93
|
-
*
|
|
94
|
-
* Uses split/join rather than regex because `cacheDir` is an absolute
|
|
95
|
-
* filesystem path that may contain regex metacharacters on unusual
|
|
96
|
-
* filesystems.
|
|
97
113
|
*/
|
|
98
|
-
function _rewriteCachedFontCssToServedUrls(css,
|
|
99
|
-
|
|
100
|
-
if (!normalizedCacheDir || !css.includes(normalizedCacheDir)) return css;
|
|
101
|
-
const prefix = assetsDir || DEFAULT_ASSETS_DIR;
|
|
102
|
-
return css.split(normalizedCacheDir).join(`/${prefix}/${VINEXT_FONT_URL_NAMESPACE}`);
|
|
114
|
+
function _rewriteCachedFontCssToServedUrls(css, assetsDir = DEFAULT_ASSETS_DIR) {
|
|
115
|
+
return css.split(CACHED_FONT_DIR_TOKEN).join(`/${assetsDir || DEFAULT_ASSETS_DIR}/${VINEXT_FONT_URL_NAMESPACE}`);
|
|
103
116
|
}
|
|
104
117
|
/**
|
|
105
118
|
* Default `build.assetsDir` — matches vinext's resolved default in
|
|
@@ -274,24 +287,49 @@ function propertyNameToGoogleFontFamily(prop) {
|
|
|
274
287
|
}
|
|
275
288
|
/**
|
|
276
289
|
* Fetch Google Fonts CSS, download .woff2 files, cache locally, and return
|
|
277
|
-
* @font-face CSS
|
|
290
|
+
* @font-face CSS whose `url()` references use `CACHED_FONT_DIR_TOKEN`.
|
|
278
291
|
*
|
|
279
292
|
* Cache dir structure: .vinext/fonts/<family-hash>/
|
|
280
|
-
* - style
|
|
293
|
+
* - style-<suffix>.css (the rewritten @font-face CSS, see `CACHED_FONT_CSS_FILE`)
|
|
281
294
|
* - *.woff2 (downloaded font files)
|
|
282
295
|
*/
|
|
283
296
|
async function fetchAndCacheFont(cssUrl, family, cacheDir) {
|
|
284
297
|
const { createHash } = await import("node:crypto");
|
|
285
298
|
const urlHash = createHash("md5").update(cssUrl).digest("hex").slice(0, 12);
|
|
286
|
-
const
|
|
287
|
-
const
|
|
299
|
+
const fontDirName = `${family.toLowerCase().replace(/\s+/g, "-")}-${urlHash}`;
|
|
300
|
+
const fontDir = path.join(cacheDir, fontDirName);
|
|
301
|
+
const cachedCSSPath = path.join(fontDir, CACHED_FONT_CSS_FILE);
|
|
288
302
|
if (fs.existsSync(cachedCSSPath)) return fs.readFileSync(cachedCSSPath, "utf-8");
|
|
303
|
+
const css = await downloadGoogleFont(cssUrl, family, fontDir, fontDirName);
|
|
304
|
+
try {
|
|
305
|
+
writeFileAtomically(cachedCSSPath, css);
|
|
306
|
+
} catch {}
|
|
307
|
+
return css;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Write-then-rename, so a write that fails midway never leaves a partial
|
|
311
|
+
* file for a later build to trust: both font files and the stylesheet are
|
|
312
|
+
* reused as soon as they exist.
|
|
313
|
+
*/
|
|
314
|
+
function writeFileAtomically(filePath, data) {
|
|
315
|
+
const tempPath = `${filePath}.${process.pid}.tmp`;
|
|
316
|
+
try {
|
|
317
|
+
fs.writeFileSync(tempPath, data);
|
|
318
|
+
fs.renameSync(tempPath, filePath);
|
|
319
|
+
} catch (err) {
|
|
320
|
+
fs.rmSync(tempPath, { force: true });
|
|
321
|
+
throw err;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
async function downloadGoogleFont(cssUrl, family, fontDir, fontDirName) {
|
|
325
|
+
const { createHash } = await import("node:crypto");
|
|
289
326
|
const cssResponse = await fetch(cssUrl, { headers: { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" } });
|
|
290
327
|
if (!cssResponse.ok) {
|
|
291
328
|
const body = await cssResponse.text().catch(() => "");
|
|
292
329
|
throw new GoogleFontsHttpError(cssUrl, cssResponse.status, body);
|
|
293
330
|
}
|
|
294
331
|
let css = await cssResponse.text();
|
|
332
|
+
if (!css.trim()) throw new Error(`Google Fonts returned an empty stylesheet: ${cssUrl}`);
|
|
295
333
|
const urlRe = /url\((https:\/\/fonts\.gstatic\.com\/[^)]+)\)/g;
|
|
296
334
|
const urls = /* @__PURE__ */ new Map();
|
|
297
335
|
let urlMatch;
|
|
@@ -308,14 +346,13 @@ async function fetchAndCacheFont(cssUrl, family, cacheDir) {
|
|
|
308
346
|
const filePath = path.join(fontDir, filename);
|
|
309
347
|
if (!fs.existsSync(filePath)) {
|
|
310
348
|
const fontResponse = await fetch(fontUrl);
|
|
311
|
-
if (fontResponse.ok) {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
349
|
+
if (!fontResponse.ok) throw new Error(`Font file download failed with HTTP ${fontResponse.status}: ${fontUrl}`);
|
|
350
|
+
const buffer = Buffer.from(await fontResponse.arrayBuffer());
|
|
351
|
+
if (buffer.byteLength === 0) throw new Error(`Font file download returned an empty body: ${fontUrl}`);
|
|
352
|
+
writeFileAtomically(filePath, buffer);
|
|
315
353
|
}
|
|
316
|
-
css = css.split(fontUrl).join(
|
|
354
|
+
css = css.split(fontUrl).join(`${CACHED_FONT_DIR_TOKEN}/${fontDirName}/${filename}`);
|
|
317
355
|
}
|
|
318
|
-
fs.writeFileSync(cachedCSSPath, css);
|
|
319
356
|
return css;
|
|
320
357
|
}
|
|
321
358
|
/**
|
|
@@ -546,7 +583,7 @@ function createGoogleFontsPlugin(fontGoogleShimPath, shimsDir) {
|
|
|
546
583
|
if (err instanceof GoogleFontsHttpError) throw new Error(`[vinext:google-fonts] ${id}: Google Fonts returned HTTP ${err.status} for ${err.url}.\n${formatGoogleFontsErrorBody(err.responseBody)}`);
|
|
547
584
|
return;
|
|
548
585
|
}
|
|
549
|
-
const servedCSS = _rewriteCachedFontCssToServedUrls(localCSS,
|
|
586
|
+
const servedCSS = _rewriteCachedFontCssToServedUrls(localCSS, transformAssetsDir);
|
|
550
587
|
const preloadUrls = findFontFilesInCss(servedCSS, validated.preload ? validated.subsets : void 0).filter((file) => file.preloadFontFile).map((file) => file.googleFontFileUrl);
|
|
551
588
|
const fallbackMetrics = validated.adjustFontFallback === false ? void 0 : getFallbackFontOverrideMetrics(family);
|
|
552
589
|
const adjustedFallbackCSS = fallbackMetrics ? buildFallbackFontFace(family, fallbackMetrics) : void 0;
|
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
import { MagicStringTransformResult } from "./transform-result.js";
|
|
2
2
|
import { Plugin } from "vite";
|
|
3
3
|
//#region src/plugins/ignore-dynamic-requests.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Cheap, conservative check run before parsing: whether any `require` or
|
|
6
|
+
* `import` occurrence could be a request this transform rewrites. Occurrences
|
|
7
|
+
* that are part of a longer identifier, member properties, static imports,
|
|
8
|
+
* non-callee positions (`typeof require`, `require,`), and calls with a
|
|
9
|
+
* single-line string literal argument other than "/" are left unchanged, so a
|
|
10
|
+
* module containing only those is skipped. Anything else, including escaped
|
|
11
|
+
* identifiers, falls through to the AST pass.
|
|
12
|
+
*/
|
|
13
|
+
declare function mayContainVeryDynamicRequest(code: string): boolean;
|
|
4
14
|
declare function transformVeryDynamicRequests(code: string, id: string): MagicStringTransformResult | null;
|
|
5
15
|
export declare function createIgnoreDynamicRequestsPlugin(getTranspiledPackages?: () => readonly string[]): Plugin;
|
|
6
16
|
export declare const _transformVeryDynamicRequests: typeof transformVeryDynamicRequests;
|
|
17
|
+
export declare const _mayContainVeryDynamicRequest: typeof mayContainVeryDynamicRequest;
|
|
7
18
|
//#endregion
|