vinext 0.2.1 → 1.0.0-beta.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.
Files changed (79) hide show
  1. package/README.md +50 -29
  2. package/dist/build/client-build-config.d.ts +13 -92
  3. package/dist/build/client-build-config.js +17 -95
  4. package/dist/build/prerender-paths.d.ts +23 -0
  5. package/dist/build/prerender-paths.js +296 -0
  6. package/dist/build/prerender.d.ts +12 -1
  7. package/dist/build/prerender.js +1 -1
  8. package/dist/cache/cache-adapters-virtual.d.ts +7 -1
  9. package/dist/cache/cache-adapters-virtual.js +26 -2
  10. package/dist/check.js +5 -0
  11. package/dist/cli.js +33 -53
  12. package/dist/config/next-config.js +1 -2
  13. package/dist/config/prerender.d.ts +11 -1
  14. package/dist/config/prerender.js +19 -1
  15. package/dist/config/tsconfig-paths.js +5 -1
  16. package/dist/image/image-adapters-virtual.js +1 -0
  17. package/dist/index.d.ts +1 -1
  18. package/dist/index.js +248 -146
  19. package/dist/init-cloudflare.js +12 -3
  20. package/dist/init-platform.d.ts +10 -1
  21. package/dist/init-platform.js +78 -12
  22. package/dist/init.d.ts +11 -3
  23. package/dist/init.js +129 -31
  24. package/dist/server/app-browser-entry.js +4 -6
  25. package/dist/server/app-browser-navigation-controller.js +6 -3
  26. package/dist/server/app-browser-state.js +1 -1
  27. package/dist/server/app-browser-visible-commit.js +1 -1
  28. package/dist/server/app-layout-param-observation.d.ts +1 -1
  29. package/dist/server/app-page-cache-render.d.ts +1 -1
  30. package/dist/server/app-page-dispatch.d.ts +1 -1
  31. package/dist/server/app-page-dispatch.js +3 -3
  32. package/dist/server/app-page-render.d.ts +1 -1
  33. package/dist/server/app-page-route-wiring.js +3 -3
  34. package/dist/server/app-route-handler-execution.d.ts +1 -1
  35. package/dist/server/app-route-handler-execution.js +1 -1
  36. package/dist/server/app-rsc-cache-busting.js +0 -1
  37. package/dist/server/app-rsc-handler.js +1 -1
  38. package/dist/server/app-rsc-render-mode.d.ts +2 -5
  39. package/dist/server/app-rsc-render-mode.js +2 -12
  40. package/dist/server/app-server-action-execution.js +4 -4
  41. package/dist/server/app-ssr-entry.js +2 -2
  42. package/dist/server/dev-module-runner.js +1 -1
  43. package/dist/server/dev-origin-check.d.ts +2 -2
  44. package/dist/server/dev-origin-check.js +2 -2
  45. package/dist/server/dev-server.js +4 -4
  46. package/dist/server/image-optimization.d.ts +12 -1
  47. package/dist/server/image-optimization.js +13 -1
  48. package/dist/server/isr-cache.d.ts +12 -4
  49. package/dist/server/isr-cache.js +2 -2
  50. package/dist/server/pages-page-data.js +1 -1
  51. package/dist/server/pages-page-response.js +2 -2
  52. package/dist/server/pages-request-pipeline.js +1 -1
  53. package/dist/server/prerender-manifest.d.ts +15 -1
  54. package/dist/server/prerender-manifest.js +29 -1
  55. package/dist/server/prod-server.d.ts +6 -5
  56. package/dist/server/prod-server.js +6 -7
  57. package/dist/server/static-file-cache.js +1 -1
  58. package/dist/shims/cache.d.ts +1 -1
  59. package/dist/shims/cdn-cache.d.ts +2 -7
  60. package/dist/shims/cdn-cache.js +2 -14
  61. package/dist/shims/form.d.ts +1 -1
  62. package/dist/shims/navigation.react-server.js +1 -1
  63. package/dist/shims/request-state-types.d.ts +2 -2
  64. package/dist/shims/unified-request-context.d.ts +1 -1
  65. package/dist/utils/project.d.ts +1 -1
  66. package/dist/utils/vite-version.d.ts +3 -13
  67. package/dist/utils/vite-version.js +38 -30
  68. package/package.json +31 -13
  69. package/dist/cloudflare/index.d.ts +0 -3
  70. package/dist/cloudflare/index.js +0 -3
  71. package/dist/packages/cloudflare/src/cache/cdn-adapter.runtime.js +0 -102
  72. package/dist/packages/cloudflare/src/cache/kv-data-adapter.runtime.d.ts +0 -126
  73. package/dist/packages/cloudflare/src/cache/kv-data-adapter.runtime.js +0 -442
  74. package/dist/packages/cloudflare/src/deploy-config.js +0 -150
  75. package/dist/packages/cloudflare/src/deploy-help.js +0 -55
  76. package/dist/packages/cloudflare/src/deploy.js +0 -272
  77. package/dist/packages/cloudflare/src/tpr.d.ts +0 -45
  78. package/dist/packages/cloudflare/src/tpr.js +0 -582
  79. package/dist/packages/cloudflare/src/utils/cache-control-metadata.js +0 -20
@@ -0,0 +1,296 @@
1
+ import { findDir } from "../utils/project.js";
2
+ import { normalizePathSeparators } from "../utils/path.js";
3
+ import { apiRouter, pagesRouter } from "../routing/pages-router.js";
4
+ import { VINEXT_PRERENDER_SECRET_HEADER } from "../utils/protocol-headers.js";
5
+ import { classifyAppRoute, classifyPagesRoute, getAppRouteRenderEntryPath, hasNamedExport } from "./report.js";
6
+ import { appRouter } from "../routing/app-router.js";
7
+ import { normalizeStaticPathsEntry } from "../routing/route-pattern.js";
8
+ import { BLOCKED_PAGES, PHASE_PRODUCTION_BUILD } from "../shims/constants.js";
9
+ import { loadNextConfig, resolveNextConfig } from "../config/next-config.js";
10
+ import { readPrerenderSecret } from "./server-manifest.js";
11
+ import { startProdServer } from "../server/prod-server.js";
12
+ import { buildUrlFromParams, resolveParentParams } from "./prerender.js";
13
+ import fs from "node:fs";
14
+ import path from "node:path";
15
+ //#region src/build/prerender-paths.ts
16
+ const PRERENDER_PATH_DISCOVERY_ENV = "__VINEXT_PRERENDER_PATH_DISCOVERY";
17
+ const PRERENDER_PATHS_MANIFEST = "vinext-prerender-paths.json";
18
+ const PATH_DISCOVERY_FETCH_TIMEOUT_MS = 3e4;
19
+ function readBuiltBuildId(serverDir) {
20
+ try {
21
+ const buildId = fs.readFileSync(path.join(serverDir, "BUILD_ID"), "utf-8").trim();
22
+ return buildId.length > 0 ? buildId : null;
23
+ } catch (error) {
24
+ if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") return null;
25
+ throw error;
26
+ }
27
+ }
28
+ function addPath(paths, seen, pathname) {
29
+ if (seen.has(pathname)) return;
30
+ seen.add(pathname);
31
+ paths.push(pathname);
32
+ }
33
+ function warnDiscoveryFailure(route, error) {
34
+ const message = error instanceof Error ? error.message : String(error);
35
+ console.warn(`[vinext] Warning: failed to discover warmup path(s) for ${route}: ${message}`);
36
+ }
37
+ async function fetchDiscoveryEndpoint(url, headers) {
38
+ const controller = new AbortController();
39
+ const timeout = setTimeout(() => controller.abort(), PATH_DISCOVERY_FETCH_TIMEOUT_MS);
40
+ try {
41
+ const res = await fetch(url, {
42
+ headers,
43
+ signal: controller.signal
44
+ });
45
+ const text = await res.text();
46
+ if (!res.ok || text === "null") return null;
47
+ return text;
48
+ } catch (error) {
49
+ if (error instanceof Error && error.name === "AbortError") throw new Error(`path discovery timed out after ${PATH_DISCOVERY_FETCH_TIMEOUT_MS}ms`);
50
+ throw error;
51
+ } finally {
52
+ clearTimeout(timeout);
53
+ }
54
+ }
55
+ function fileHasNamedExport(filePath, name) {
56
+ if (!filePath) return false;
57
+ try {
58
+ return hasNamedExport(fs.readFileSync(filePath, "utf-8"), name);
59
+ } catch {
60
+ return false;
61
+ }
62
+ }
63
+ function resolveConfiguredRouteDirs(root, routeRootConfig) {
64
+ if (!routeRootConfig) return {
65
+ appDir: findDir(root, "app", "src/app"),
66
+ pagesDir: findDir(root, "pages", "src/pages")
67
+ };
68
+ let baseDir;
69
+ if (routeRootConfig.appDir) {
70
+ baseDir = path.isAbsolute(routeRootConfig.appDir) ? routeRootConfig.appDir : path.resolve(root, routeRootConfig.appDir);
71
+ baseDir = normalizePathSeparators(baseDir);
72
+ } else {
73
+ const hasRootApp = fs.existsSync(path.posix.join(root, "app"));
74
+ const hasRootPages = fs.existsSync(path.posix.join(root, "pages"));
75
+ const hasSrcApp = fs.existsSync(path.posix.join(root, "src", "app"));
76
+ const hasSrcPages = fs.existsSync(path.posix.join(root, "src", "pages"));
77
+ baseDir = hasRootApp || hasRootPages ? root : hasSrcApp || hasSrcPages ? path.posix.join(root, "src") : root;
78
+ }
79
+ const appDir = path.posix.join(baseDir, "app");
80
+ const pagesDir = path.posix.join(baseDir, "pages");
81
+ return {
82
+ appDir: !routeRootConfig.disableAppRouter && fs.existsSync(appDir) ? appDir : null,
83
+ pagesDir: fs.existsSync(pagesDir) ? pagesDir : null
84
+ };
85
+ }
86
+ function appRouteMayHaveGenerateStaticParams(route) {
87
+ if (fileHasNamedExport(route.pagePath, "generateStaticParams")) return true;
88
+ return route.layouts.some((layoutPath) => fileHasNamedExport(layoutPath, "generateStaticParams"));
89
+ }
90
+ async function shouldStartPathDiscoveryServer(options) {
91
+ if (options.appDir) {
92
+ if ((await appRouter(options.appDir, options.pageExtensions)).some((route) => route.isDynamic && appRouteMayHaveGenerateStaticParams(route))) return true;
93
+ }
94
+ if (options.pagesDir) {
95
+ if ((await pagesRouter(options.pagesDir, options.pageExtensions)).some((route) => route.isDynamic && fileHasNamedExport(route.filePath, "getStaticPaths"))) return true;
96
+ }
97
+ return false;
98
+ }
99
+ async function withPrerenderEndpoints(fn) {
100
+ const previousPrerenderFlag = process.env.VINEXT_PRERENDER;
101
+ const previousPathDiscoveryFlag = process.env[PRERENDER_PATH_DISCOVERY_ENV];
102
+ process.env.VINEXT_PRERENDER = "1";
103
+ process.env[PRERENDER_PATH_DISCOVERY_ENV] = "1";
104
+ try {
105
+ return await fn();
106
+ } finally {
107
+ if (previousPrerenderFlag === void 0) delete process.env.VINEXT_PRERENDER;
108
+ else process.env.VINEXT_PRERENDER = previousPrerenderFlag;
109
+ if (previousPathDiscoveryFlag === void 0) delete process.env[PRERENDER_PATH_DISCOVERY_ENV];
110
+ else process.env[PRERENDER_PATH_DISCOVERY_ENV] = previousPathDiscoveryFlag;
111
+ }
112
+ }
113
+ async function collectPagesPaths(options) {
114
+ const [pageRoutes, apiRoutes] = await Promise.all([pagesRouter(options.pagesDir, options.pageExtensions), apiRouter(options.pagesDir, options.pageExtensions)]);
115
+ const apiPatterns = new Set(apiRoutes.map((route) => route.pattern));
116
+ const paths = [];
117
+ const seen = /* @__PURE__ */ new Set();
118
+ for (const route of pageRoutes) {
119
+ if (apiPatterns.has(route.pattern)) continue;
120
+ if (BLOCKED_PAGES.includes(route.pattern)) continue;
121
+ if (route.pattern === "/404" || route.pattern === "/500" || route.pattern === "/_error") continue;
122
+ const { type } = classifyPagesRoute(route.filePath);
123
+ if (type === "api" || type === "ssr") continue;
124
+ if (!route.isDynamic) {
125
+ addPath(paths, seen, route.pattern);
126
+ continue;
127
+ }
128
+ if (!fileHasNamedExport(route.filePath, "getStaticPaths")) continue;
129
+ if (!options.baseUrl) continue;
130
+ try {
131
+ const search = new URLSearchParams({ pattern: route.pattern });
132
+ const text = await fetchDiscoveryEndpoint(`${options.baseUrl}/__vinext/prerender/pages-static-paths?${search}`, options.secretHeaders);
133
+ if (text === null) continue;
134
+ const pathsResult = JSON.parse(text);
135
+ for (const item of pathsResult.paths ?? []) {
136
+ const normalized = normalizeStaticPathsEntry(item, route.pattern);
137
+ if ("error" in normalized) throw new Error(normalized.error);
138
+ addPath(paths, seen, buildUrlFromParams(route.pattern, normalized.params));
139
+ }
140
+ } catch (error) {
141
+ warnDiscoveryFailure(route.pattern, error);
142
+ }
143
+ }
144
+ return paths;
145
+ }
146
+ async function collectAppPaths(options) {
147
+ const routes = await appRouter(options.appDir, options.pageExtensions);
148
+ const paths = [];
149
+ const seen = /* @__PURE__ */ new Set();
150
+ const staticParamsCache = /* @__PURE__ */ new Map();
151
+ const staticParamsMap = new Proxy({}, {
152
+ get(_target, pattern) {
153
+ return async ({ params }) => {
154
+ if (!options.baseUrl) return null;
155
+ const cacheKey = `${pattern}\0${JSON.stringify(params)}`;
156
+ const cached = staticParamsCache.get(cacheKey);
157
+ if (cached !== void 0) return cached;
158
+ const request = (async () => {
159
+ const search = new URLSearchParams({ pattern });
160
+ if (Object.keys(params).length > 0) search.set("parentParams", JSON.stringify(params));
161
+ const text = await fetchDiscoveryEndpoint(`${options.baseUrl}/__vinext/prerender/static-params?${search}`, options.secretHeaders);
162
+ if (text === null) return null;
163
+ return JSON.parse(text);
164
+ })();
165
+ request.catch(() => staticParamsCache.delete(cacheKey));
166
+ staticParamsCache.set(cacheKey, request);
167
+ return request;
168
+ };
169
+ },
170
+ has() {
171
+ return false;
172
+ }
173
+ });
174
+ for (const route of routes) {
175
+ const renderEntryPath = getAppRouteRenderEntryPath(route);
176
+ if (!renderEntryPath) continue;
177
+ const { type } = classifyAppRoute(renderEntryPath, route.routePath, route.isDynamic);
178
+ if (type === "api") continue;
179
+ if (type === "ssr" && !route.isDynamic) continue;
180
+ if (!route.isDynamic) {
181
+ addPath(paths, seen, route.pattern);
182
+ continue;
183
+ }
184
+ if (!appRouteMayHaveGenerateStaticParams(route)) continue;
185
+ try {
186
+ const generateStaticParams = staticParamsMap[route.pattern];
187
+ if (typeof generateStaticParams !== "function") continue;
188
+ const parentParamSets = await resolveParentParams(route, staticParamsMap);
189
+ let paramSets;
190
+ if (parentParamSets.length > 0) {
191
+ paramSets = [];
192
+ for (const parentParams of parentParamSets) {
193
+ const childResults = await generateStaticParams({ params: parentParams });
194
+ if (childResults === null) {
195
+ paramSets = null;
196
+ break;
197
+ }
198
+ if (Array.isArray(childResults)) for (const childParams of childResults) paramSets.push({
199
+ ...parentParams,
200
+ ...childParams
201
+ });
202
+ }
203
+ } else {
204
+ const results = await generateStaticParams({ params: {} });
205
+ paramSets = Array.isArray(results) || results === null ? results : [];
206
+ }
207
+ if (!paramSets?.length) continue;
208
+ for (const params of paramSets) {
209
+ if (params === null || params === void 0) continue;
210
+ addPath(paths, seen, buildUrlFromParams(route.pattern, params));
211
+ }
212
+ } catch (error) {
213
+ warnDiscoveryFailure(route.pattern, error);
214
+ }
215
+ }
216
+ return paths;
217
+ }
218
+ async function startPathDiscoveryServer(options) {
219
+ return startProdServer({
220
+ port: 0,
221
+ host: "127.0.0.1",
222
+ outDir: options.pagesBundlePath ? path.dirname(path.dirname(options.pagesBundlePath)) : path.dirname(options.serverDir),
223
+ rscEntryPath: options.rscBundlePath,
224
+ serverEntryPath: options.pagesBundlePath,
225
+ noCompression: true,
226
+ purpose: "prerender"
227
+ });
228
+ }
229
+ async function emitPrerenderPathManifest(options) {
230
+ const { root } = options;
231
+ const configuredRouteDirs = resolveConfiguredRouteDirs(root, options.routeRootConfig);
232
+ const appDir = options.appDir !== void 0 ? options.appDir : configuredRouteDirs.appDir;
233
+ const pagesDir = options.pagesDir !== void 0 ? options.pagesDir : configuredRouteDirs.pagesDir;
234
+ if (!appDir && !pagesDir) return null;
235
+ const defaultRscBundlePath = options.routeRootConfig?.rscOutDir ? path.join(path.resolve(root, options.routeRootConfig.rscOutDir), "index.js") : path.join(root, "dist", "server", "index.js");
236
+ const rscBundlePath = options.rscBundlePath ?? defaultRscBundlePath;
237
+ const pagesBundlePath = options.pagesBundlePath ?? path.join(root, "dist", "server", "entry.js");
238
+ const bundleServerDir = fs.existsSync(rscBundlePath) ? path.dirname(rscBundlePath) : path.dirname(pagesBundlePath);
239
+ const manifestDir = path.join(root, "dist", "server");
240
+ const loadedConfig = await resolveNextConfig(await loadNextConfig(root, PHASE_PRODUCTION_BUILD), root);
241
+ const config = options.nextConfigOverride ? {
242
+ ...loadedConfig,
243
+ ...options.nextConfigOverride
244
+ } : { ...loadedConfig };
245
+ const builtBuildId = readBuiltBuildId(manifestDir) ?? readBuiltBuildId(bundleServerDir);
246
+ if (builtBuildId) config.buildId = builtBuildId;
247
+ const paths = [];
248
+ const seen = /* @__PURE__ */ new Set();
249
+ await withPrerenderEndpoints(async () => {
250
+ let prodServer = null;
251
+ if (await shouldStartPathDiscoveryServer({
252
+ appDir,
253
+ pagesDir,
254
+ pageExtensions: config.pageExtensions
255
+ })) try {
256
+ prodServer = await startPathDiscoveryServer({
257
+ serverDir: bundleServerDir,
258
+ pagesBundlePath: !appDir && pagesDir ? pagesBundlePath : void 0,
259
+ rscBundlePath: appDir ? rscBundlePath : void 0
260
+ });
261
+ } catch (error) {
262
+ const message = error instanceof Error ? error.message : String(error);
263
+ console.warn(`[vinext] Warning: failed to start prerender path discovery server: ${message}`);
264
+ }
265
+ const baseUrl = prodServer ? `http://127.0.0.1:${prodServer.port}` : null;
266
+ const prerenderSecret = readPrerenderSecret(bundleServerDir) ?? readPrerenderSecret(manifestDir);
267
+ const secretHeaders = prerenderSecret ? { [VINEXT_PRERENDER_SECRET_HEADER]: prerenderSecret } : {};
268
+ try {
269
+ if (appDir) for (const pathname of await collectAppPaths({
270
+ appDir,
271
+ baseUrl,
272
+ pageExtensions: config.pageExtensions,
273
+ secretHeaders
274
+ })) addPath(paths, seen, pathname);
275
+ if (pagesDir) for (const pathname of await collectPagesPaths({
276
+ baseUrl,
277
+ pagesDir,
278
+ pageExtensions: config.pageExtensions,
279
+ secretHeaders
280
+ })) addPath(paths, seen, pathname);
281
+ } finally {
282
+ if (prodServer) await new Promise((resolve) => prodServer.server.close(() => resolve()));
283
+ }
284
+ });
285
+ const manifest = {
286
+ buildId: config.buildId,
287
+ trailingSlash: config.trailingSlash,
288
+ paths
289
+ };
290
+ fs.mkdirSync(manifestDir, { recursive: true });
291
+ fs.writeFileSync(path.join(manifestDir, PRERENDER_PATHS_MANIFEST), JSON.stringify(manifest, null, 2) + "\n", "utf-8");
292
+ console.log(` Discovered ${paths.length} CDN warmup path(s).`);
293
+ return manifest;
294
+ }
295
+ //#endregion
296
+ export { PRERENDER_PATHS_MANIFEST, PRERENDER_PATH_DISCOVERY_ENV, emitPrerenderPathManifest };
@@ -135,6 +135,17 @@ type PrerenderAppOptionsInternal = PrerenderAppOptions & {
135
135
  * all '<' and '>' in the embedded JSON, preventing false </script> matches.
136
136
  */
137
137
  declare function extractRscPayloadFromPrerenderedHtml(html: string): Uint8Array | null;
138
+ /**
139
+ * Build a URL path from a route pattern and params.
140
+ * "/posts/:id" + { id: "42" } → "/posts/42"
141
+ * "/docs/:slug+" + { slug: ["a", "b"] } → "/docs/a/b"
142
+ *
143
+ * Throws a descriptive error rather than a cryptic `Cannot read properties of
144
+ * undefined` if `params` itself is missing or required keys are absent — the
145
+ * caller (prerenderPages / prerenderApp) catches this and surfaces it as a
146
+ * per-route error result.
147
+ */
148
+ declare function buildUrlFromParams(pattern: string, params: Record<string, string | string[]> | undefined | null): string;
138
149
  /** Map of route patterns to generateStaticParams functions (or null/undefined). */
139
150
  type StaticParamsMap = Record<string, ((opts: {
140
151
  params: Record<string, string | string[]>;
@@ -207,4 +218,4 @@ declare function writePrerenderIndex(routes: PrerenderRouteResult[], outDir: str
207
218
  trailingSlash?: boolean;
208
219
  }): void;
209
220
  //#endregion
210
- export { PrerenderResult, PrerenderRouteResult, StaticParamsMap, extractRscPayloadFromPrerenderedHtml, prerenderApp, prerenderPages, readPrerenderSecret, resolveParentParams, writePrerenderIndex };
221
+ export { PrerenderResult, PrerenderRouteResult, StaticParamsMap, buildUrlFromParams, extractRscPayloadFromPrerenderedHtml, prerenderApp, prerenderPages, readPrerenderSecret, resolveParentParams, writePrerenderIndex };
@@ -1000,4 +1000,4 @@ function writePrerenderIndex(routes, outDir, options) {
1000
1000
  fs.writeFileSync(path.join(outDir, "vinext-prerender.json"), JSON.stringify(index, null, 2), "utf-8");
1001
1001
  }
1002
1002
  //#endregion
1003
- export { extractRscPayloadFromPrerenderedHtml, prerenderApp, prerenderPages, readPrerenderSecret, resolveParentParams, writePrerenderIndex };
1003
+ export { buildUrlFromParams, extractRscPayloadFromPrerenderedHtml, prerenderApp, prerenderPages, readPrerenderSecret, resolveParentParams, writePrerenderIndex };
@@ -41,10 +41,16 @@ type VinextCacheConfig = {
41
41
  };
42
42
  /** Public virtual module id imported by the server entries. */
43
43
  declare const VIRTUAL_CACHE_ADAPTERS = "virtual:vinext-cache-adapters";
44
+ declare const VINEXT_CACHE_CONFIG_PLUGIN_PROPERTY = "__vinextCacheConfig";
45
+ type ViteConfigLoader = {
46
+ loadConfigFromFile: typeof import("vite").loadConfigFromFile;
47
+ };
48
+ declare function findVinextCacheConfigInPlugins(plugins: import("vite").PluginOption[] | undefined): VinextCacheConfig | null;
49
+ declare function loadVinextCacheConfigFromViteConfig(vite: ViteConfigLoader, root: string): Promise<VinextCacheConfig | null>;
44
50
  /**
45
51
  * Generate the source of the `virtual:vinext-cache-adapters` module for the
46
52
  * given config. Always exports `registerConfiguredCacheAdapters(env)`.
47
53
  */
48
54
  declare function generateCacheAdaptersModule(cache?: VinextCacheConfig): string;
49
55
  //#endregion
50
- export { VIRTUAL_CACHE_ADAPTERS, VinextCacheConfig, generateCacheAdaptersModule };
56
+ export { VINEXT_CACHE_CONFIG_PLUGIN_PROPERTY, VIRTUAL_CACHE_ADAPTERS, VinextCacheConfig, findVinextCacheConfigInPlugins, generateCacheAdaptersModule, loadVinextCacheConfigFromViteConfig };
@@ -1,6 +1,30 @@
1
1
  //#region src/cache/cache-adapters-virtual.ts
2
2
  /** Public virtual module id imported by the server entries. */
3
3
  const VIRTUAL_CACHE_ADAPTERS = "virtual:vinext-cache-adapters";
4
+ const VINEXT_CACHE_CONFIG_PLUGIN_PROPERTY = "__vinextCacheConfig";
5
+ function flattenPluginOptions(value, target) {
6
+ if (Array.isArray(value)) {
7
+ for (const item of value) flattenPluginOptions(item, target);
8
+ return;
9
+ }
10
+ if (value) target.push(value);
11
+ }
12
+ function findVinextCacheConfigInPlugins(plugins) {
13
+ const flattened = [];
14
+ flattenPluginOptions(plugins, flattened);
15
+ for (const plugin of flattened) {
16
+ if (!plugin || typeof plugin !== "object") continue;
17
+ const cacheConfig = plugin[VINEXT_CACHE_CONFIG_PLUGIN_PROPERTY];
18
+ if (cacheConfig) return cacheConfig;
19
+ }
20
+ return null;
21
+ }
22
+ async function loadVinextCacheConfigFromViteConfig(vite, root) {
23
+ return findVinextCacheConfigInPlugins((await vite.loadConfigFromFile({
24
+ command: "build",
25
+ mode: "production"
26
+ }, void 0, root))?.config.plugins);
27
+ }
4
28
  /**
5
29
  * Serialize descriptor options into a JS expression for inlining. Plain JSON is
6
30
  * a valid JS literal; `undefined` when there are no options. Throws a clear
@@ -35,11 +59,11 @@ function generateCacheAdaptersModule(cache) {
35
59
  lines.push(`import __vinextCdnAdapterFactory from ${JSON.stringify(cdn.adapter)};`);
36
60
  lines.push(`import { setCdnCacheAdapter } from "vinext/shims/cdn-cache";`);
37
61
  }
38
- lines.push("", "// A factory that throws (e.g. a missing binding on an incompatible runtime)", "// is logged and skipped so the default handler stays in place.", "function __vinextFormatAdapterError(error) {", " if (error instanceof Error && error.message) return error.message;", " try {", " return String(error);", " } catch {", " return '<unknown error>';", " }", "}", "", "let __vinextCacheAdaptersRegistered = false;", "", "export function registerConfiguredCacheAdapters(env) {", " if (__vinextCacheAdaptersRegistered) return;", " __vinextCacheAdaptersRegistered = true;");
62
+ lines.push("", "// A factory that throws (e.g. a missing binding on an incompatible runtime)", "// is logged and skipped so the default handler stays in place.", "function __vinextFormatAdapterError(error) {", " if (error instanceof Error && error.message) return error.message;", " try {", " return String(error);", " } catch {", " return '<unknown error>';", " }", "}", "", "let __vinextCacheAdaptersRegistered = false;", "", "export function registerConfiguredCacheAdapters(env) {", " if (typeof process !== 'undefined' && process.env?.__VINEXT_PRERENDER_PATH_DISCOVERY === '1') return;", " if (__vinextCacheAdaptersRegistered) return;", " __vinextCacheAdaptersRegistered = true;");
39
63
  if (data?.adapter) lines.push(" try {", ` setDataCacheHandler(__vinextDataAdapterFactory({ env, options: ${inlineOptions(data.adapter, data.options)} }));`, " } catch (error) {", " console.warn(\"[vinext] failed to initialize the configured data cache adapter; using the default handler.\\n\" + __vinextFormatAdapterError(error));", " }");
40
64
  if (cdn?.adapter) lines.push(" try {", ` setCdnCacheAdapter(__vinextCdnAdapterFactory({ env, options: ${inlineOptions(cdn.adapter, cdn.options)} }));`, " } catch (error) {", " console.warn(\"[vinext] failed to initialize the configured CDN cache adapter; using the default adapter.\\n\" + __vinextFormatAdapterError(error));", " }");
41
65
  lines.push("}", "");
42
66
  return lines.join("\n");
43
67
  }
44
68
  //#endregion
45
- export { VIRTUAL_CACHE_ADAPTERS, generateCacheAdaptersModule };
69
+ export { VINEXT_CACHE_CONFIG_PLUGIN_PROPERTY, VIRTUAL_CACHE_ADAPTERS, findVinextCacheConfigInPlugins, generateCacheAdaptersModule, loadVinextCacheConfigFromViteConfig };
package/dist/check.js CHANGED
@@ -215,6 +215,10 @@ const CONFIG_SUPPORT = {
215
215
  status: "supported",
216
216
  detail: "sourcemap-resolved stack traces during prerender"
217
217
  },
218
+ cacheComponents: {
219
+ status: "partial",
220
+ detail: "experimental support; behavior is incomplete"
221
+ },
218
222
  "experimental.ppr": {
219
223
  status: "unsupported",
220
224
  detail: "partial prerendering not yet implemented"
@@ -775,6 +779,7 @@ function analyzeConfig(root) {
775
779
  "output",
776
780
  "transpilePackages",
777
781
  "webpack",
782
+ "cacheComponents",
778
783
  "reactStrictMode",
779
784
  "poweredByHeader",
780
785
  "skipMiddlewareUrlNormalize",
package/dist/cli.js CHANGED
@@ -8,15 +8,14 @@ import { PHASE_PRODUCTION_BUILD } from "./shims/constants.js";
8
8
  import { createRscCompatibilityId, loadNextConfig, resolveNextConfig } from "./config/next-config.js";
9
9
  import { generateRouteTypes } from "./typegen.js";
10
10
  import { clearPagesClientAssetsBuildMetadata } from "./build/pages-client-assets-module.js";
11
- import { findVinextPrerenderConfigInPlugins, formatVinextPrerenderLabel, resolveVinextPrerenderDecision } from "./config/prerender.js";
11
+ import { findVinextPrerenderConfigInPlugins, findVinextRouteRootConfigInPlugins, formatVinextPrerenderLabel, resolveVinextPrerenderDecision } from "./config/prerender.js";
12
12
  import vinext from "./index.js";
13
13
  import { runPrerender } from "./build/run-prerender.js";
14
- import { loadDotenv } from "./config/dotenv.js";
15
- import { deploy, parseDeployArgs } from "./packages/cloudflare/src/deploy.js";
16
- import { printDeployHelp } from "./packages/cloudflare/src/deploy-help.js";
14
+ import { emitPrerenderPathManifest } from "./build/prerender-paths.js";
17
15
  import { getReactUpgradeDeps } from "./utils/react-version.js";
18
16
  import { init } from "./init.js";
19
- import { INIT_PLATFORMS, resolveInitPlatform, resolveInitPrerender } from "./init-platform.js";
17
+ import { resolveInitOptions } from "./init-platform.js";
18
+ import { loadDotenv } from "./config/dotenv.js";
20
19
  import { resolveVinextPackageRoot } from "./utils/vinext-root.js";
21
20
  import { emitStandaloneOutput } from "./build/standalone.js";
22
21
  import { cleanBuildOutput } from "./build/clean-output.js";
@@ -34,7 +33,6 @@ import { execFileSync } from "node:child_process";
34
33
  * vinext dev Start development server (Vite)
35
34
  * vinext build Build for production
36
35
  * vinext start Start production server
37
- * vinext deploy Deprecated Cloudflare deploy shim
38
36
  * vinext typegen Generate App Router route helper types
39
37
  * vinext lint Run linter (delegates to eslint/oxlint)
40
38
  *
@@ -128,7 +126,10 @@ function hasPagesDir() {
128
126
  return fs.existsSync(path.join(process.cwd(), "pages")) || fs.existsSync(path.join(process.cwd(), "src", "pages"));
129
127
  }
130
128
  async function loadBuildViteConfigMetadata(vite, root) {
131
- if (!hasViteConfig(root)) return { prerenderConfig: null };
129
+ if (!hasViteConfig(root)) return {
130
+ prerenderConfig: null,
131
+ routeRootConfig: null
132
+ };
132
133
  const loaded = await vite.loadConfigFromFile({
133
134
  command: "build",
134
135
  mode: "production"
@@ -136,7 +137,8 @@ async function loadBuildViteConfigMetadata(vite, root) {
136
137
  const emptyOutDir = loaded?.config.build?.emptyOutDir;
137
138
  return {
138
139
  emptyOutDir: typeof emptyOutDir === "boolean" ? emptyOutDir : void 0,
139
- prerenderConfig: findVinextPrerenderConfigInPlugins(loaded?.config.plugins)
140
+ prerenderConfig: findVinextPrerenderConfigInPlugins(loaded?.config.plugins),
141
+ routeRootConfig: findVinextRouteRootConfigInPlugins(loaded?.config.plugins)
140
142
  };
141
143
  }
142
144
  /**
@@ -277,8 +279,7 @@ async function buildApp() {
277
279
  });
278
280
  applyViteConfigCompatibility(process.cwd());
279
281
  const vite = await loadVite();
280
- const viteMajorVersion = Number.parseInt(vite.version, 10) || 7;
281
- const withBuildBundlerOptions = (bundlerOptions) => viteMajorVersion >= 8 ? { rolldownOptions: bundlerOptions } : { rollupOptions: bundlerOptions };
282
+ const withBuildBundlerOptions = (bundlerOptions) => ({ rolldownOptions: bundlerOptions });
282
283
  console.log(`\n vinext build (Vite ${getViteVersion()})\n`);
283
284
  const root = process.cwd();
284
285
  const isApp = hasAppDir(normalizePathSeparators(root));
@@ -330,7 +331,7 @@ async function buildApp() {
330
331
  mode: "production",
331
332
  isSsrBuild: true
332
333
  }, void 0, root);
333
- if (loaded?.config.plugins) userTransformPlugins = loaded.config.plugins.flat(Infinity).filter((p) => !!p && typeof p.name === "string" && !p.name.startsWith("vinext:") && !p.name.startsWith("vite:react") && !p.name.startsWith("rsc:") && p.name !== "vite-rsc-load-module-dev-proxy" && p.name !== "vite-tsconfig-paths" && !p.name.startsWith("vite-plugin-cloudflare"));
334
+ if (loaded?.config.plugins) userTransformPlugins = loaded.config.plugins.flat(Infinity).filter((p) => !!p && typeof p.name === "string" && !p.name.startsWith("vinext:") && !p.name.startsWith("vite:react") && !p.name.startsWith("rsc:") && p.name !== "vite-rsc-load-module-dev-proxy" && !p.name.startsWith("vite-plugin-cloudflare"));
334
335
  }
335
336
  await vite.build({
336
337
  root,
@@ -383,6 +384,11 @@ async function buildApp() {
383
384
  root: normalizePathSeparators(process.cwd()),
384
385
  concurrency: parsed.prerenderConcurrency
385
386
  });
387
+ await emitPrerenderPathManifest({
388
+ root: normalizePathSeparators(process.cwd()),
389
+ nextConfigOverride: resolvedNextConfig,
390
+ routeRootConfig: buildConfigMetadata.routeRootConfig
391
+ });
386
392
  }
387
393
  process.stdout.write("\x1B[0m");
388
394
  const { printBuildReport } = await import("./build/report.js");
@@ -452,33 +458,9 @@ async function lint() {
452
458
  process.exit(1);
453
459
  }
454
460
  }
455
- async function deployCommand() {
456
- const parsed = parseDeployArgs(rawArgs);
457
- if (parsed.help) {
458
- printDeployDeprecationWarning();
459
- printDeployHelp();
460
- return;
461
- }
462
- printDeployDeprecationWarning();
463
- await loadVite();
464
- console.log(`\n vinext deploy (Vite ${getViteVersion()})\n`);
465
- await deploy({
466
- root: process.cwd(),
467
- preview: parsed.preview,
468
- env: parsed.env,
469
- skipBuild: parsed.skipBuild,
470
- dryRun: parsed.dryRun,
471
- name: parsed.name,
472
- prerenderAll: parsed.prerenderAll,
473
- prerenderConcurrency: parsed.prerenderConcurrency,
474
- experimentalTPR: parsed.experimentalTPR,
475
- tprCoverage: parsed.tprCoverage,
476
- tprLimit: parsed.tprLimit,
477
- tprWindow: parsed.tprWindow
478
- });
479
- }
480
- function printDeployDeprecationWarning() {
481
- console.log(" ⚠️ Warning: `vinext deploy` has moved to the `@vinext/cloudflare` package.\n Please switch to `npx @vinext/cloudflare deploy` or `vp exec vinext-cloudflare deploy`; this compatibility command will be removed in a future release.\n");
461
+ function failRemovedDeployCommand() {
462
+ console.error("\n Error: `vinext deploy` has moved to the `@vinext/cloudflare` package.\n\n Run `npx @vinext/cloudflare deploy` or `vp exec vinext-cloudflare deploy` instead.\n");
463
+ process.exit(1);
482
464
  }
483
465
  async function check() {
484
466
  if (parseArgs(rawArgs).help) return printHelp("check");
@@ -508,17 +490,13 @@ async function initCommand() {
508
490
  const port = parsed.port ?? 3001;
509
491
  const skipCheck = rawArgs.includes("--skip-check");
510
492
  const force = rawArgs.includes("--force");
511
- const platform = await resolveInitPlatform(rawArgs);
512
- const platformOptions = await INIT_PLATFORMS[platform].options(rawArgs);
513
- const prerender = await resolveInitPrerender(rawArgs);
493
+ const initOptions = await resolveInitOptions(rawArgs);
514
494
  await init({
515
495
  root: process.cwd(),
516
496
  port,
517
497
  skipCheck,
518
498
  force,
519
- platform,
520
- prerender,
521
- cloudflare: platform === "cloudflare" ? platformOptions : void 0
499
+ ...initOptions
522
500
  });
523
501
  }
524
502
  function printHelp(cmd) {
@@ -574,11 +552,7 @@ function printHelp(cmd) {
574
552
  `);
575
553
  return;
576
554
  }
577
- if (cmd === "deploy") {
578
- printDeployDeprecationWarning();
579
- printDeployHelp();
580
- return;
581
- }
555
+ if (cmd === "deploy") failRemovedDeployCommand();
582
556
  if (cmd === "check") {
583
557
  console.log(`
584
558
  vinext check - Scan Next.js app for compatibility
@@ -611,6 +585,11 @@ function printHelp(cmd) {
611
585
  --platform <target> Deployment target: cloudflare or node
612
586
  --prerender Configure vinext build to pre-render all static routes
613
587
  (default: prompt, with No selected by default)
588
+ --experimental-warm-cdn-cache
589
+ Add experimental CDN pre-warming to the Cloudflare deploy script
590
+ (Workers Cache CDN only, default: prompt with No)
591
+ --cdn-cache <type> Cloudflare CDN cache: workers-cache or data-cache
592
+ (default: workers-cache)
614
593
  --data-cache <type> Cloudflare data cache: kv or none (default: kv)
615
594
  --image-optimization <type>
616
595
  Cloudflare image optimization: cloudflare-images or none
@@ -619,11 +598,15 @@ function printHelp(cmd) {
619
598
  Examples:
620
599
  vinext init Prompt for a deployment platform
621
600
  vinext init --platform=cloudflare Configure Cloudflare Workers (default)
601
+ vinext init --platform=cloudflare --cdn-cache=data-cache
602
+ Fall through CDN caching to the data cache
622
603
  vinext init --platform=cloudflare --data-cache=kv
623
604
  Configure the default Cloudflare cache handlers
624
605
  vinext init --platform=cloudflare --image-optimization=none
625
606
  Do not configure Cloudflare Images
626
607
  vinext init --prerender Add prerender: { routes: "*" } to vite.config.ts
608
+ vinext init --experimental-warm-cdn-cache
609
+ Add experimental CDN pre-warming to deploy:vinext
627
610
  vinext init --platform=node Configure a Node deployment
628
611
  vinext init -p 4000 Use port 4000 for dev:vinext
629
612
  vinext init --force Overwrite existing vite.config.ts
@@ -722,10 +705,7 @@ switch (command) {
722
705
  });
723
706
  break;
724
707
  case "deploy":
725
- deployCommand().catch((e) => {
726
- console.error(e);
727
- process.exit(1);
728
- });
708
+ failRemovedDeployCommand();
729
709
  break;
730
710
  case "init":
731
711
  initCommand().catch((e) => {
@@ -4,7 +4,6 @@ import { isUnknownRecord } from "../utils/record.js";
4
4
  import { getHtmlLimitedBotRegex } from "../utils/html-limited-bots.js";
5
5
  import { PHASE_DEVELOPMENT_SERVER, PHASE_PRODUCTION_BUILD } from "../shims/constants.js";
6
6
  import { loadTsconfigResolutionForRoot } from "./tsconfig-paths.js";
7
- import { getViteMajorVersion } from "../utils/vite-version.js";
8
7
  import { loadCommonJsModule, shouldRetryAsCommonJs } from "../utils/commonjs-loader.js";
9
8
  import { createRequire } from "node:module";
10
9
  import fs from "node:fs";
@@ -315,7 +314,7 @@ async function loadNextConfig(root, phase = DEFAULT_PHASE) {
315
314
  const filename = path.basename(configPath);
316
315
  const isTypeScriptConfig = /\.[cm]?ts$/.test(configPath);
317
316
  const tsconfigResolution = loadTsconfigResolutionForRoot(root);
318
- const useNativeTsconfigPaths = !!(isTypeScriptConfig ? tsconfigResolution.baseUrl : null) && getViteMajorVersion() >= 8;
317
+ const useNativeTsconfigPaths = !!(isTypeScriptConfig ? tsconfigResolution.baseUrl : null);
319
318
  const normalizedConfigPath = safeRealpath(path.resolve(configPath));
320
319
  try {
321
320
  const { runnerImport } = await import("vite");
@@ -1,3 +1,4 @@
1
+ import { VINEXT_CACHE_CONFIG_PLUGIN_PROPERTY, VinextCacheConfig, findVinextCacheConfigInPlugins, loadVinextCacheConfigFromViteConfig } from "../cache/cache-adapters-virtual.js";
1
2
  import { PluginOption } from "vite";
2
3
 
3
4
  //#region src/config/prerender.d.ts
@@ -18,12 +19,21 @@ type VinextPrerenderDecision = ResolvedVinextPrerenderConfig & {
18
19
  reason: VinextPrerenderDecisionReason;
19
20
  };
20
21
  declare const VINEXT_PRERENDER_CONFIG_PLUGIN_PROPERTY = "__vinextPrerenderConfig";
22
+ declare const VINEXT_ROUTE_ROOT_CONFIG_PLUGIN_PROPERTY = "__vinextRouteRootConfig";
23
+ type VinextRouteRootConfig = {
24
+ appDir?: string;
25
+ disableAppRouter?: boolean;
26
+ rscOutDir?: string;
27
+ ssrOutDir?: string;
28
+ };
21
29
  type ViteConfigLoader = {
22
30
  loadConfigFromFile: typeof import("vite").loadConfigFromFile;
23
31
  };
24
32
  declare function normalizeVinextPrerenderConfig(config: VinextPrerenderConfig | undefined): ResolvedVinextPrerenderConfig | null;
25
33
  declare function findVinextPrerenderConfigInPlugins(plugins: PluginOption[] | undefined): ResolvedVinextPrerenderConfig | null;
34
+ declare function findVinextRouteRootConfigInPlugins(plugins: PluginOption[] | undefined): VinextRouteRootConfig | null;
26
35
  declare function loadVinextPrerenderConfigFromViteConfig(vite: ViteConfigLoader, root: string): Promise<ResolvedVinextPrerenderConfig | null>;
36
+ declare function loadVinextRouteRootConfigFromViteConfig(vite: ViteConfigLoader, root: string): Promise<VinextRouteRootConfig | null>;
27
37
  declare function resolveVinextPrerenderDecision(options: {
28
38
  prerenderAllFlag?: boolean;
29
39
  vinextPrerenderConfig?: ResolvedVinextPrerenderConfig | null;
@@ -31,4 +41,4 @@ declare function resolveVinextPrerenderDecision(options: {
31
41
  }): VinextPrerenderDecision | null;
32
42
  declare function formatVinextPrerenderLabel(decision: VinextPrerenderDecision): string;
33
43
  //#endregion
34
- export { ResolvedVinextPrerenderConfig, VINEXT_PRERENDER_CONFIG_PLUGIN_PROPERTY, VinextPrerenderConfig, VinextPrerenderDecision, VinextPrerenderDecisionReason, findVinextPrerenderConfigInPlugins, formatVinextPrerenderLabel, loadVinextPrerenderConfigFromViteConfig, normalizeVinextPrerenderConfig, resolveVinextPrerenderDecision };
44
+ export { ResolvedVinextPrerenderConfig, VINEXT_CACHE_CONFIG_PLUGIN_PROPERTY, VINEXT_PRERENDER_CONFIG_PLUGIN_PROPERTY, VINEXT_ROUTE_ROOT_CONFIG_PLUGIN_PROPERTY, type VinextCacheConfig, VinextPrerenderConfig, VinextPrerenderDecision, VinextPrerenderDecisionReason, VinextRouteRootConfig, findVinextCacheConfigInPlugins, findVinextPrerenderConfigInPlugins, findVinextRouteRootConfigInPlugins, formatVinextPrerenderLabel, loadVinextCacheConfigFromViteConfig, loadVinextPrerenderConfigFromViteConfig, loadVinextRouteRootConfigFromViteConfig, normalizeVinextPrerenderConfig, resolveVinextPrerenderDecision };