@vinext/cloudflare 1.0.0-beta.9 → 1.0.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/README.md +165 -37
- package/dist/cache/browser-cache-policy.d.ts +7 -0
- package/dist/cache/browser-cache-policy.js +39 -0
- package/dist/cache/cdn-adapter-config.d.ts +2 -1
- package/dist/cache/cdn-adapter-config.js +13 -5
- package/dist/cache/cdn-adapter.d.ts +5 -56
- package/dist/cache/cdn-adapter.js +3 -61
- package/dist/cache/cdn-adapter.runtime.d.ts +1 -0
- package/dist/cache/cdn-adapter.runtime.js +33 -19
- package/dist/cache/cdn-adapter.worker.d.ts +1 -1
- package/dist/cache/cdn-adapter.worker.js +10 -35
- package/dist/cache/cdn-build-id.d.ts +1 -1
- package/dist/cache/cdn-build-id.js +1 -1
- package/dist/cache/config.d.ts +137 -0
- package/dist/cache/config.js +87 -0
- package/dist/cache/kv-data-adapter.d.ts +1 -1
- package/dist/cache/kv-data-adapter.js +1 -1
- package/dist/cache/kv-data-adapter.runtime.js +17 -15
- package/dist/cache/kv-key.d.ts +7 -4
- package/dist/cache/kv-key.js +17 -6
- package/dist/cache/response-store-adapter.d.ts +9 -6
- package/dist/cache/response-store-adapter.js +20 -1
- package/dist/cache/response-store-adapter.worker.js +121 -48
- package/dist/cache/response-store-cdn.runtime.js +1 -0
- package/dist/cache/response-store-data.runtime.js +53 -31
- package/dist/cache/static-assets-adapter.build.d.ts +6 -0
- package/dist/cache/static-assets-adapter.build.js +102 -0
- package/dist/cache/static-assets-adapter.d.ts +27 -0
- package/dist/cache/static-assets-adapter.js +26 -0
- package/dist/cache/static-assets-adapter.runtime.d.ts +27 -0
- package/dist/cache/static-assets-adapter.runtime.js +94 -0
- package/dist/cache/static-assets-adapter.shared.d.ts +12 -0
- package/dist/cache/static-assets-adapter.shared.js +4 -0
- package/dist/cache/workers-cache-cdn-adapter.d.ts +58 -0
- package/dist/cache/workers-cache-cdn-adapter.js +65 -0
- package/dist/cacheability-artifact.d.ts +7 -1
- package/dist/cacheability-artifact.js +60 -21
- package/dist/cacheability-probe.d.ts +2 -0
- package/dist/cacheability-probe.js +107 -13
- package/dist/cdn-warm.d.ts +1 -0
- package/dist/cdn-warm.js +4 -3
- package/dist/deploy-config.d.ts +3 -8
- package/dist/deploy-config.js +5 -23
- package/dist/deploy-help.js +47 -42
- package/dist/deploy.d.ts +18 -16
- package/dist/deploy.js +355 -216
- package/dist/tpr.d.ts +4 -0
- package/dist/tpr.js +16 -7
- package/dist/version-deploy.d.ts +27 -0
- package/dist/version-deploy.js +121 -3
- package/dist/wrangler-version-metadata.js +1 -1
- package/package.json +16 -5
- package/dist/prerender-kv-populate.d.ts +0 -24
- package/dist/prerender-kv-populate.js +0 -153
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { STATIC_ASSET_CACHE_PATH } from "./static-assets-adapter.shared.js";
|
|
2
|
+
import { DefaultCdnCacheAdapter } from "vinext/shims/cdn-cache";
|
|
3
|
+
//#region src/cache/static-assets-adapter.runtime.ts
|
|
4
|
+
async function cacheAssetId(key) {
|
|
5
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(key));
|
|
6
|
+
return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
7
|
+
}
|
|
8
|
+
function isMetadata(value) {
|
|
9
|
+
return value !== null && typeof value === "object" && "kind" in value && (value.kind === "html" || value.kind === "rsc" || value.kind === "route" || value.kind === "pages" || value.kind === "redirect" || value.kind === "not-found") && "lastModified" in value && typeof value.lastModified === "number";
|
|
10
|
+
}
|
|
11
|
+
var StaticAssetsCacheAdapter = class {
|
|
12
|
+
assets;
|
|
13
|
+
hasPrerenderedPages = true;
|
|
14
|
+
ownsBackgroundRevalidation = false;
|
|
15
|
+
indexPromise;
|
|
16
|
+
constructor(assets) {
|
|
17
|
+
this.assets = assets;
|
|
18
|
+
}
|
|
19
|
+
loadIndex() {
|
|
20
|
+
return this.indexPromise ??= this.assets.fetch(`https://vinext.invalid${STATIC_ASSET_CACHE_PATH}/index.json`).then(async (response) => response.ok ? await response.json() : null).then((value) => value !== null && typeof value === "object" ? value : null);
|
|
21
|
+
}
|
|
22
|
+
async get(key) {
|
|
23
|
+
const id = await cacheAssetId(key);
|
|
24
|
+
const metadata = (await this.loadIndex())?.[id];
|
|
25
|
+
if (!isMetadata(metadata)) return null;
|
|
26
|
+
const extension = metadata.kind;
|
|
27
|
+
const bodyResponse = await this.assets.fetch(`https://vinext.invalid${STATIC_ASSET_CACHE_PATH}/${id}.${extension}`);
|
|
28
|
+
if (!bodyResponse.ok) return null;
|
|
29
|
+
let value;
|
|
30
|
+
if (metadata.kind === "not-found") {
|
|
31
|
+
await bodyResponse.body?.cancel();
|
|
32
|
+
value = null;
|
|
33
|
+
} else if (metadata.kind === "redirect") value = {
|
|
34
|
+
kind: "REDIRECT",
|
|
35
|
+
props: await bodyResponse.json()
|
|
36
|
+
};
|
|
37
|
+
else if (metadata.kind === "pages") {
|
|
38
|
+
const { html, pageData } = await bodyResponse.json();
|
|
39
|
+
value = {
|
|
40
|
+
kind: "PAGES",
|
|
41
|
+
html,
|
|
42
|
+
pageData,
|
|
43
|
+
headers: metadata.headers,
|
|
44
|
+
status: metadata.status
|
|
45
|
+
};
|
|
46
|
+
} else if (metadata.kind === "html") value = {
|
|
47
|
+
kind: "APP_PAGE",
|
|
48
|
+
html: await bodyResponse.text(),
|
|
49
|
+
rscData: void 0,
|
|
50
|
+
headers: metadata.headers,
|
|
51
|
+
postponed: void 0,
|
|
52
|
+
prerendered: true,
|
|
53
|
+
status: metadata.status
|
|
54
|
+
};
|
|
55
|
+
else if (metadata.kind === "rsc") value = {
|
|
56
|
+
kind: "APP_PAGE",
|
|
57
|
+
html: "",
|
|
58
|
+
rscData: await bodyResponse.arrayBuffer(),
|
|
59
|
+
headers: void 0,
|
|
60
|
+
postponed: void 0,
|
|
61
|
+
prerendered: true,
|
|
62
|
+
status: void 0
|
|
63
|
+
};
|
|
64
|
+
else value = {
|
|
65
|
+
kind: "APP_ROUTE",
|
|
66
|
+
body: await bodyResponse.arrayBuffer(),
|
|
67
|
+
headers: metadata.headers ?? {},
|
|
68
|
+
status: metadata.status ?? 200
|
|
69
|
+
};
|
|
70
|
+
return {
|
|
71
|
+
lastModified: metadata.lastModified,
|
|
72
|
+
...metadata.cacheControl ? { cacheControl: metadata.cacheControl } : {},
|
|
73
|
+
value
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
async set(_key, _data, _ctx) {}
|
|
77
|
+
buildResponseHeaders(input) {
|
|
78
|
+
return { "Cache-Control": input.pendingDynamicCheck ? "no-store, must-revalidate" : input.cacheControl };
|
|
79
|
+
}
|
|
80
|
+
buildResponseIdentityHeaders() {
|
|
81
|
+
const buildId = process.env.__VINEXT_RSC_BUILD_IDENTITY || process.env.__VINEXT_BUILD_ID;
|
|
82
|
+
return buildId ? { "X-Vinext-Build-Id": buildId } : {};
|
|
83
|
+
}
|
|
84
|
+
async revalidateTag(_tags, _durations) {}
|
|
85
|
+
};
|
|
86
|
+
function createStaticAssetsCacheAdapter({ env, options } = {}) {
|
|
87
|
+
if (process.env.VINEXT_PRERENDER === "1") return new DefaultCdnCacheAdapter(process.env.__VINEXT_RSC_BUILD_IDENTITY || process.env.__VINEXT_BUILD_ID);
|
|
88
|
+
const binding = options?.binding ?? "ASSETS";
|
|
89
|
+
const assets = env?.[binding];
|
|
90
|
+
if (!assets || typeof assets !== "object" || !("fetch" in assets) || typeof assets.fetch !== "function") throw new Error(`[vinext] Static Assets binding \`${binding}\` is not configured.`);
|
|
91
|
+
return new StaticAssetsCacheAdapter(assets);
|
|
92
|
+
}
|
|
93
|
+
//#endregion
|
|
94
|
+
export { StaticAssetsCacheAdapter, createStaticAssetsCacheAdapter as default };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { CacheControlMetadata } from "vinext/shims/cache-handler";
|
|
2
|
+
//#region src/cache/static-assets-adapter.shared.d.ts
|
|
3
|
+
export declare const STATIC_ASSET_CACHE_PATH = "/_vinext/static-cache";
|
|
4
|
+
export type StaticAssetCacheMetadata = {
|
|
5
|
+
kind: "html" | "rsc" | "route" | "pages" | "redirect" | "not-found";
|
|
6
|
+
lastModified: number;
|
|
7
|
+
cacheControl?: CacheControlMetadata;
|
|
8
|
+
headers?: Record<string, string | string[]>;
|
|
9
|
+
status?: number;
|
|
10
|
+
};
|
|
11
|
+
export type StaticAssetCacheIndex = Record<string, StaticAssetCacheMetadata>;
|
|
12
|
+
//#endregion
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
//#region src/cache/workers-cache-cdn-adapter.d.ts
|
|
2
|
+
export declare const DEFAULT_CDN_VERSION_METADATA_BINDING = "CF_VERSION_METADATA";
|
|
3
|
+
/** Options accepted by {@link workersCacheCdnAdapter}, forwarded to the runtime factory. */
|
|
4
|
+
export type CdnAdapterOptions = {
|
|
5
|
+
/** Version metadata binding used to verify version-overridden requests. */
|
|
6
|
+
versionMetadataBinding?: string;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Cloudflare CDN cache adapter - edge-managed page-level ISR backed by the
|
|
10
|
+
* Cloudflare Workers Cache.
|
|
11
|
+
*
|
|
12
|
+
* Unlike the data adapter (which stores cache entries in a durable store and
|
|
13
|
+
* serves HIT/STALE itself), this adapter delegates serving to Workers Cache on
|
|
14
|
+
* a named Worker entrypoint. The default entrypoint always runs middleware and
|
|
15
|
+
* request-time routing, then renders bypass requests inline or dispatches shared
|
|
16
|
+
* requests to the cached response-stage entrypoint.
|
|
17
|
+
*
|
|
18
|
+
* The deployment configuration enables Workers Cache only for that response-stage
|
|
19
|
+
* export, so cache hits do not start the application stage. Legacy Cloudflare
|
|
20
|
+
* builds receive this policy in their generated Wrangler config. Cloudflare Vite
|
|
21
|
+
* plugin v2 builds declare the same entrypoint policy in `cloudflare.config.ts`.
|
|
22
|
+
* Deployment readiness probes still use the uncached response entrypoint to
|
|
23
|
+
* verify that named response entrypoints are available.
|
|
24
|
+
*
|
|
25
|
+
* The adapter adds a transport-only URL digest so distinct response-stage
|
|
26
|
+
* identities cannot collide. Workers Cache owns this key independently of
|
|
27
|
+
* zone Cache Rules.
|
|
28
|
+
*/
|
|
29
|
+
export declare function workersCacheCdnAdapter(options?: CdnAdapterOptions): {
|
|
30
|
+
adapter: string;
|
|
31
|
+
options: CdnAdapterOptions | undefined;
|
|
32
|
+
output: {
|
|
33
|
+
entry: string;
|
|
34
|
+
matchesBuild({ plugins }: {
|
|
35
|
+
plugins: readonly {
|
|
36
|
+
name?: string;
|
|
37
|
+
}[];
|
|
38
|
+
}): boolean;
|
|
39
|
+
transformHostEntry({ code, id }: {
|
|
40
|
+
code: string;
|
|
41
|
+
id: string;
|
|
42
|
+
}): string | null;
|
|
43
|
+
finalizeBuildOutput({ root, outDir, isPrimaryServerOutput }: {
|
|
44
|
+
root: string;
|
|
45
|
+
outDir: string;
|
|
46
|
+
isPrimaryServerOutput: boolean;
|
|
47
|
+
}): Promise<void>;
|
|
48
|
+
type: "multi-stage";
|
|
49
|
+
};
|
|
50
|
+
capabilities: {
|
|
51
|
+
buildIdentity: "response-header";
|
|
52
|
+
isResponsePolicyHeader: (name: string) => boolean;
|
|
53
|
+
requestRouting: "uncached-stage";
|
|
54
|
+
responseVary: "verbatim";
|
|
55
|
+
routeCacheability: "probe-manifest";
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
//#endregion
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { finalizeCdnAdapterBuildOutput } from "./cdn-adapter-config.js";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
//#region src/cache/workers-cache-cdn-adapter.ts
|
|
4
|
+
const DEFAULT_CDN_VERSION_METADATA_BINDING = "CF_VERSION_METADATA";
|
|
5
|
+
const CLOUDFLARE_WORKER_ENTRY_ID = "virtual:cloudflare/worker-entry";
|
|
6
|
+
/**
|
|
7
|
+
* Cloudflare CDN cache adapter - edge-managed page-level ISR backed by the
|
|
8
|
+
* Cloudflare Workers Cache.
|
|
9
|
+
*
|
|
10
|
+
* Unlike the data adapter (which stores cache entries in a durable store and
|
|
11
|
+
* serves HIT/STALE itself), this adapter delegates serving to Workers Cache on
|
|
12
|
+
* a named Worker entrypoint. The default entrypoint always runs middleware and
|
|
13
|
+
* request-time routing, then renders bypass requests inline or dispatches shared
|
|
14
|
+
* requests to the cached response-stage entrypoint.
|
|
15
|
+
*
|
|
16
|
+
* The deployment configuration enables Workers Cache only for that response-stage
|
|
17
|
+
* export, so cache hits do not start the application stage. Legacy Cloudflare
|
|
18
|
+
* builds receive this policy in their generated Wrangler config. Cloudflare Vite
|
|
19
|
+
* plugin v2 builds declare the same entrypoint policy in `cloudflare.config.ts`.
|
|
20
|
+
* Deployment readiness probes still use the uncached response entrypoint to
|
|
21
|
+
* verify that named response entrypoints are available.
|
|
22
|
+
*
|
|
23
|
+
* The adapter adds a transport-only URL digest so distinct response-stage
|
|
24
|
+
* identities cannot collide. Workers Cache owns this key independently of
|
|
25
|
+
* zone Cache Rules.
|
|
26
|
+
*/
|
|
27
|
+
function workersCacheCdnAdapter(options) {
|
|
28
|
+
if (options?.versionMetadataBinding !== void 0 && (typeof options.versionMetadataBinding !== "string" || options.versionMetadataBinding.length === 0)) throw new TypeError("[vinext] workersCacheCdnAdapter({ versionMetadataBinding }) must be a non-empty string binding name.");
|
|
29
|
+
const versionMetadataBinding = options?.versionMetadataBinding ?? "CF_VERSION_METADATA";
|
|
30
|
+
const bindingIsExplicit = options?.versionMetadataBinding !== void 0;
|
|
31
|
+
const workerEntry = fileURLToPath(import.meta.resolve("./cdn-adapter.worker.js"));
|
|
32
|
+
return {
|
|
33
|
+
adapter: fileURLToPath(import.meta.resolve("./cdn-adapter.runtime.js")),
|
|
34
|
+
options,
|
|
35
|
+
output: {
|
|
36
|
+
entry: workerEntry,
|
|
37
|
+
matchesBuild({ plugins }) {
|
|
38
|
+
return plugins.some(({ name }) => name === "vite-plugin-cloudflare" || name?.startsWith("vite-plugin-cloudflare:"));
|
|
39
|
+
},
|
|
40
|
+
transformHostEntry({ code, id }) {
|
|
41
|
+
if ((id.charCodeAt(0) === 0 ? id.slice(1) : id) !== CLOUDFLARE_WORKER_ENTRY_ID) return null;
|
|
42
|
+
return `${code}\nexport { VinextCachedResponse, VinextUncachedResponse } from ${JSON.stringify(workerEntry)};\n`;
|
|
43
|
+
},
|
|
44
|
+
finalizeBuildOutput({ root, outDir, isPrimaryServerOutput }) {
|
|
45
|
+
return finalizeCdnAdapterBuildOutput({
|
|
46
|
+
root,
|
|
47
|
+
outDir,
|
|
48
|
+
isPrimaryServerOutput,
|
|
49
|
+
binding: versionMetadataBinding,
|
|
50
|
+
bindingIsExplicit
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
type: "multi-stage"
|
|
54
|
+
},
|
|
55
|
+
capabilities: {
|
|
56
|
+
buildIdentity: "response-header",
|
|
57
|
+
isResponsePolicyHeader: (name) => name.toLowerCase() === "cdn-cache-control" || name.toLowerCase() === "cloudflare-cdn-cache-control",
|
|
58
|
+
requestRouting: "uncached-stage",
|
|
59
|
+
responseVary: "verbatim",
|
|
60
|
+
routeCacheability: "probe-manifest"
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
//#endregion
|
|
65
|
+
export { DEFAULT_CDN_VERSION_METADATA_BINDING, workersCacheCdnAdapter };
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { CacheabilityManifest } from "vinext/internal/server/cacheability-manifest";
|
|
2
2
|
//#region src/cacheability-artifact.d.ts
|
|
3
|
+
export type CloudflareDeploymentTool = "cf" | "wrangler";
|
|
3
4
|
/**
|
|
4
5
|
* Write the version-specific manifest into the built Worker artifact.
|
|
5
6
|
* The application build already imports this stable module asset, so the
|
|
6
7
|
* completed dist directory remains the exact input to the final upload.
|
|
8
|
+
*
|
|
9
|
+
* App Router builds also emit the request stage's projection module. It
|
|
10
|
+
* carries the App page routes that can admit a query-free entry, so the
|
|
11
|
+
* request stage can strip the query from those dispatches without loading the
|
|
12
|
+
* full manifest.
|
|
7
13
|
*/
|
|
8
|
-
export declare function writeCacheabilityManifestArtifact(root: string, configuredPath: string | undefined, manifest: CacheabilityManifest): string;
|
|
14
|
+
export declare function writeCacheabilityManifestArtifact(root: string, configuredPath: string | undefined, manifest: CacheabilityManifest, deploymentTool?: CloudflareDeploymentTool): string;
|
|
9
15
|
//#endregion
|
|
@@ -2,7 +2,7 @@ import { cacheabilityManifestByteLimitError } from "./cacheability-manifest-limi
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { Buffer } from "node:buffer";
|
|
5
|
-
import { CACHEABILITY_MANIFEST_MODULE } from "vinext/internal/server/cacheability-manifest";
|
|
5
|
+
import { CACHEABILITY_MANIFEST_MODULE, CACHEABILITY_REQUEST_PROJECTION_MODULE, projectCacheabilityManifestForRequestStage } from "vinext/internal/server/cacheability-manifest";
|
|
6
6
|
//#region src/cacheability-artifact.ts
|
|
7
7
|
function tokenizeStaticImports(source) {
|
|
8
8
|
const tokens = [];
|
|
@@ -115,16 +115,12 @@ function hasStaticModuleSpecifier(source, expected) {
|
|
|
115
115
|
}
|
|
116
116
|
return false;
|
|
117
117
|
}
|
|
118
|
-
function
|
|
118
|
+
function resolveGeneratedWranglerArtifact(root, configuredPath) {
|
|
119
119
|
const distDirectory = path.join(root, "dist");
|
|
120
120
|
const configPath = path.resolve(root, configuredPath ?? "dist/server/wrangler.json");
|
|
121
121
|
const relativeConfigPath = path.relative(distDirectory, configPath);
|
|
122
122
|
if (relativeConfigPath.startsWith("..") || path.isAbsolute(relativeConfigPath)) throw new Error("Two-stage CDN warming requires a generated Wrangler config under dist/. Pass --config dist/server/wrangler.json (or another generated dist config).");
|
|
123
123
|
if (!fs.existsSync(configPath) || !fs.lstatSync(configPath).isFile()) throw new Error(`Two-stage CDN warming requires the generated Wrangler config at ${path.relative(root, configPath)}. Rebuild the app before deploying.`);
|
|
124
|
-
return configPath;
|
|
125
|
-
}
|
|
126
|
-
function assertManifestModuleReachable(configPath) {
|
|
127
|
-
const serverDirectory = path.dirname(configPath);
|
|
128
124
|
let config;
|
|
129
125
|
try {
|
|
130
126
|
config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
@@ -133,8 +129,36 @@ function assertManifestModuleReachable(configPath) {
|
|
|
133
129
|
}
|
|
134
130
|
const main = config && typeof config === "object" && !Array.isArray(config) ? config.main : void 0;
|
|
135
131
|
if (typeof main !== "string" || main.length === 0) throw new Error("Two-stage CDN warming requires a generated Wrangler main module.");
|
|
132
|
+
return {
|
|
133
|
+
configPath,
|
|
134
|
+
main,
|
|
135
|
+
serverDirectory: path.dirname(configPath)
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function resolveBuildOutputArtifact(root) {
|
|
139
|
+
const outputDirectory = path.join(root, ".cloudflare", "output");
|
|
140
|
+
const configPaths = fs.existsSync(outputDirectory) ? fs.readdirSync(outputDirectory, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => path.join(outputDirectory, entry.name, "workers", "default", "worker.config.json")).filter((candidate) => fs.existsSync(candidate) && fs.lstatSync(candidate).isFile()) : [];
|
|
141
|
+
if (configPaths.length !== 1) throw new Error("Two-stage CDN warming requires one default Worker Build Output config under .cloudflare/output/. Rebuild the app before deploying.");
|
|
142
|
+
const configPath = configPaths[0];
|
|
143
|
+
let config;
|
|
144
|
+
try {
|
|
145
|
+
config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
146
|
+
} catch (cause) {
|
|
147
|
+
throw new Error("Two-stage CDN warming could not parse the generated Build Output config.", { cause });
|
|
148
|
+
}
|
|
149
|
+
const manifest = config && typeof config === "object" && !Array.isArray(config) ? config.manifest : void 0;
|
|
150
|
+
const main = manifest && typeof manifest === "object" && !Array.isArray(manifest) ? manifest.mainModule : void 0;
|
|
151
|
+
if (typeof main !== "string" || main.length === 0) throw new Error("Two-stage CDN warming requires a generated Build Output main module.");
|
|
152
|
+
return {
|
|
153
|
+
configPath,
|
|
154
|
+
main,
|
|
155
|
+
serverDirectory: path.join(path.dirname(configPath), "bundle")
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function assertModuleReachable(artifact, moduleName) {
|
|
159
|
+
const { main, serverDirectory } = artifact;
|
|
136
160
|
const mainPath = path.resolve(serverDirectory, main);
|
|
137
|
-
if (!fs.existsSync(mainPath) || !fs.lstatSync(mainPath).isFile()) throw new Error("Two-stage CDN warming could not find the generated
|
|
161
|
+
if (!fs.existsSync(mainPath) || !fs.lstatSync(mainPath).isFile()) throw new Error("Two-stage CDN warming could not find the generated Worker main module.");
|
|
138
162
|
const viteManifestPath = path.join(serverDirectory, ".vite", "manifest.json");
|
|
139
163
|
let viteManifest;
|
|
140
164
|
try {
|
|
@@ -155,36 +179,51 @@ function assertManifestModuleReachable(configPath) {
|
|
|
155
179
|
if (!entry || typeof entry.file !== "string") continue;
|
|
156
180
|
const modulePath = path.resolve(serverDirectory, entry.file);
|
|
157
181
|
if (fs.existsSync(modulePath) && fs.lstatSync(modulePath).isFile()) {
|
|
158
|
-
const
|
|
159
|
-
const specifier =
|
|
182
|
+
const relativeModule = path.relative(path.dirname(modulePath), path.join(serverDirectory, moduleName)).split(path.sep).join("/");
|
|
183
|
+
const specifier = relativeModule.startsWith(".") ? relativeModule : `./${relativeModule}`;
|
|
160
184
|
reachable = hasStaticModuleSpecifier(fs.readFileSync(modulePath, "utf8"), specifier);
|
|
161
185
|
}
|
|
162
186
|
for (const references of [entry.imports, entry.dynamicImports]) if (Array.isArray(references)) queue.push(...references.filter((value) => typeof value === "string"));
|
|
163
187
|
}
|
|
164
|
-
if (!reachable) throw new Error(`Two-stage CDN warming requires the generated Worker graph to statically import ${
|
|
188
|
+
if (!reachable) throw new Error(`Two-stage CDN warming requires the generated Worker graph to statically import ${moduleName}.`);
|
|
189
|
+
}
|
|
190
|
+
function writeStringModule(modulePath, value) {
|
|
191
|
+
const source = `export default ${JSON.stringify(value)};\n`;
|
|
192
|
+
const pendingPath = `${modulePath}.${process.pid}.tmp`;
|
|
193
|
+
try {
|
|
194
|
+
fs.writeFileSync(pendingPath, source, "utf8");
|
|
195
|
+
fs.renameSync(pendingPath, modulePath);
|
|
196
|
+
} finally {
|
|
197
|
+
if (fs.existsSync(pendingPath)) fs.unlinkSync(pendingPath);
|
|
198
|
+
}
|
|
165
199
|
}
|
|
166
200
|
/**
|
|
167
201
|
* Write the version-specific manifest into the built Worker artifact.
|
|
168
202
|
* The application build already imports this stable module asset, so the
|
|
169
203
|
* completed dist directory remains the exact input to the final upload.
|
|
204
|
+
*
|
|
205
|
+
* App Router builds also emit the request stage's projection module. It
|
|
206
|
+
* carries the App page routes that can admit a query-free entry, so the
|
|
207
|
+
* request stage can strip the query from those dispatches without loading the
|
|
208
|
+
* full manifest.
|
|
170
209
|
*/
|
|
171
|
-
function writeCacheabilityManifestArtifact(root, configuredPath, manifest) {
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
const serverDirectory =
|
|
210
|
+
function writeCacheabilityManifestArtifact(root, configuredPath, manifest, deploymentTool = "wrangler") {
|
|
211
|
+
const artifact = deploymentTool === "cf" ? resolveBuildOutputArtifact(root) : resolveGeneratedWranglerArtifact(root, configuredPath);
|
|
212
|
+
assertModuleReachable(artifact, CACHEABILITY_MANIFEST_MODULE);
|
|
213
|
+
const { configPath, serverDirectory } = artifact;
|
|
175
214
|
const manifestPath = path.join(serverDirectory, CACHEABILITY_MANIFEST_MODULE);
|
|
176
215
|
if (!fs.existsSync(manifestPath) || !fs.lstatSync(manifestPath).isFile()) throw new Error(`Two-stage CDN warming requires ${CACHEABILITY_MANIFEST_MODULE} in the generated Worker artifact. Rebuild the app before deploying.`);
|
|
177
216
|
const serializedManifest = JSON.stringify(manifest);
|
|
178
217
|
const manifestBytes = Buffer.byteLength(serializedManifest);
|
|
179
218
|
if (manifestBytes > 2097152) throw cacheabilityManifestByteLimitError(manifestBytes);
|
|
180
|
-
const
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
} finally {
|
|
186
|
-
if (fs.existsSync(pendingManifestPath)) fs.unlinkSync(pendingManifestPath);
|
|
219
|
+
const projectionPath = path.join(serverDirectory, CACHEABILITY_REQUEST_PROJECTION_MODULE);
|
|
220
|
+
const hasProjectionModule = fs.existsSync(projectionPath) && fs.lstatSync(projectionPath).isFile();
|
|
221
|
+
if (Object.values(manifest.routes).some((route) => route.kind === "app-page")) {
|
|
222
|
+
if (!hasProjectionModule) throw new Error(`Two-stage CDN warming requires ${CACHEABILITY_REQUEST_PROJECTION_MODULE} in the generated Worker artifact. Rebuild the app before deploying.`);
|
|
223
|
+
assertModuleReachable(artifact, CACHEABILITY_REQUEST_PROJECTION_MODULE);
|
|
187
224
|
}
|
|
225
|
+
writeStringModule(manifestPath, serializedManifest);
|
|
226
|
+
if (hasProjectionModule) writeStringModule(projectionPath, JSON.stringify(projectCacheabilityManifestForRequestStage(manifest)));
|
|
188
227
|
return path.relative(root, configPath);
|
|
189
228
|
}
|
|
190
229
|
//#endregion
|
|
@@ -32,6 +32,8 @@ export declare function probeStagedWorkerCacheability(options: {
|
|
|
32
32
|
expectedResponseBuildId?: string;
|
|
33
33
|
fallbackRoutePatterns?: readonly PrerenderRoutePattern[];
|
|
34
34
|
fetchImpl?: typeof fetch;
|
|
35
|
+
/** App page route patterns with a main-tree loading boundary. */
|
|
36
|
+
loadingBoundaryRoutePatterns?: readonly string[];
|
|
35
37
|
headers?: HeadersInit;
|
|
36
38
|
retries?: number;
|
|
37
39
|
retryDelayMs?: number;
|
|
@@ -30,13 +30,19 @@ function sharedPathPrefix(pathnames) {
|
|
|
30
30
|
}
|
|
31
31
|
/** Remove a shared pathname prefix only when it makes the serialized route smaller. */
|
|
32
32
|
function compactManifestRoutePaths(route) {
|
|
33
|
-
const pathPrefix = sharedPathPrefix([
|
|
33
|
+
const pathPrefix = sharedPathPrefix([
|
|
34
|
+
...route.runtimePaths ?? [],
|
|
35
|
+
...Object.values(route.runtimeRepresentationPaths ?? {}).flatMap((paths) => paths ?? []),
|
|
36
|
+
...Object.values(route.staticPaths ?? {}).flatMap((paths) => paths ?? [])
|
|
37
|
+
]);
|
|
34
38
|
if (!pathPrefix) return route;
|
|
39
|
+
const compactPathLists = (lists) => Object.fromEntries(Object.entries(lists).map(([representation, paths]) => [representation, paths.map((pathname) => pathname.slice(pathPrefix.length))]));
|
|
35
40
|
const compacted = {
|
|
36
41
|
...route,
|
|
37
42
|
pathPrefix,
|
|
38
43
|
...route.runtimePaths ? { runtimePaths: route.runtimePaths.map((pathname) => pathname.slice(pathPrefix.length)) } : {},
|
|
39
|
-
...route.
|
|
44
|
+
...route.runtimeRepresentationPaths ? { runtimeRepresentationPaths: compactPathLists(route.runtimeRepresentationPaths) } : {},
|
|
45
|
+
...route.staticPaths ? { staticPaths: compactPathLists(route.staticPaths) } : {}
|
|
40
46
|
};
|
|
41
47
|
return Buffer.byteLength(JSON.stringify(compacted)) < Buffer.byteLength(JSON.stringify(route)) ? compacted : route;
|
|
42
48
|
}
|
|
@@ -261,6 +267,14 @@ async function probeStagedWorkerCacheability(options) {
|
|
|
261
267
|
group.targets.push(target);
|
|
262
268
|
targetGroups.set(concreteKey, group);
|
|
263
269
|
}
|
|
270
|
+
const buildTimeListing = /* @__PURE__ */ new Map();
|
|
271
|
+
const isListedAt = (route, routePathname) => {
|
|
272
|
+
const key = cacheabilityManifestRouteKey(route.kind, route.pattern);
|
|
273
|
+
const listed = buildTimeListing.get(`${key}\0${routePathname}`);
|
|
274
|
+
if (listed !== void 0) return listed;
|
|
275
|
+
return !/(^|\/):/.test(route.pattern) && normalizeCacheabilityRoutePathname(route.pattern) === routePathname;
|
|
276
|
+
};
|
|
277
|
+
const isListedGroup = (group) => isListedAt(group.pattern.route, group.routePathname);
|
|
264
278
|
const groups = Array.from(targetGroups.values(), (targetGroup) => {
|
|
265
279
|
targetGroup.targets.sort((first, second) => {
|
|
266
280
|
return targetPreference(first) - targetPreference(second) || first.sourcePathname.localeCompare(second.sourcePathname);
|
|
@@ -270,6 +284,7 @@ async function probeStagedWorkerCacheability(options) {
|
|
|
270
284
|
deferred: false,
|
|
271
285
|
primary: targetGroup.targets[0]
|
|
272
286
|
};
|
|
287
|
+
buildTimeListing.set(`${group.pattern.key}\0${group.routePathname}`, group.primary.route.cacheabilityProbe?.unlisted !== true);
|
|
273
288
|
targetGroup.pattern.groups.push(group);
|
|
274
289
|
return group;
|
|
275
290
|
});
|
|
@@ -391,12 +406,24 @@ async function probeStagedWorkerCacheability(options) {
|
|
|
391
406
|
if (result.state === "probe-failed" && result.retryable === true && attemptOptions.deferRetry) return "retry";
|
|
392
407
|
lastProgressAt = Date.now();
|
|
393
408
|
probed += 1;
|
|
394
|
-
if (result.version !== 1 || result.kind !== "app-page" && result.kind !== "app-route" && result.kind !== "pages-page" || typeof result.pattern !== "string" || !result.pattern.startsWith("/") || !isProbeRouteState(result.state) || result.scope !== void 0 && result.scope !== "identity" && result.scope !== "pattern" || result.scope === "pattern" && result.state !== "dynamic" || result.routePathname !== void 0 && (typeof result.routePathname !== "string" || !result.routePathname.startsWith("/") || result.routePathname.includes("?") || result.routePathname.includes("#")) || result.terminal !== void 0 && result.terminal !== true || result.terminal === true && (result.state !== "dynamic" || result.scope !== "identity" || !group.pattern.requestStageMayTerminate) || result.rendererStatic !== void 0 && typeof result.rendererStatic !== "boolean" || result.retryable !== void 0 && result.retryable !== true || result.retryable === true && result.state !== "probe-failed" || !Number.isInteger(result.status) || result.status < 100 || result.status > 599) {
|
|
409
|
+
if (result.version !== 1 || result.kind !== "app-page" && result.kind !== "app-route" && result.kind !== "pages-page" || typeof result.pattern !== "string" || !result.pattern.startsWith("/") || !isProbeRouteState(result.state) || result.scope !== void 0 && result.scope !== "identity" && result.scope !== "pattern" || result.scope === "pattern" && result.state !== "dynamic" || result.routePathname !== void 0 && (typeof result.routePathname !== "string" || !result.routePathname.startsWith("/") || result.routePathname.includes("?") || result.routePathname.includes("#")) || result.terminal !== void 0 && result.terminal !== true || result.terminal === true && (result.state !== "dynamic" || result.scope !== "identity" || !group.pattern.requestStageMayTerminate) || result.rendererStatic !== void 0 && typeof result.rendererStatic !== "boolean" || result.dynamicUsage !== void 0 && result.dynamicUsage !== true || result.explicitConfigCachePolicy !== void 0 && result.explicitConfigCachePolicy !== true || result.retryable !== void 0 && result.retryable !== true || result.retryable === true && result.state !== "probe-failed" || !Number.isInteger(result.status) || result.status < 100 || result.status > 599) {
|
|
395
410
|
failures.push(`${target.label}: ${result.reason ?? "probe returned an invalid envelope"}`);
|
|
396
411
|
completedPathCount += 1;
|
|
397
412
|
reportProgress();
|
|
398
413
|
return "done";
|
|
399
414
|
}
|
|
415
|
+
if (result.state === "probe-failed" && result.kind === "app-page" && result.status >= 500 && result.reason === `route returned HTTP ${result.status}` && target.route && (result.kind === target.route.kind && result.pattern === target.route.pattern || target.route.cacheabilityProbe?.routeMayResolve === true && result.routePathname !== void 0) && !isListedAt({
|
|
416
|
+
kind: result.kind,
|
|
417
|
+
pattern: result.pattern
|
|
418
|
+
}, result.routePathname === void 0 ? group.routePathname : normalizeCacheabilityRoutePathname(result.routePathname))) {
|
|
419
|
+
if (target.route.cacheabilityProbe?.routeMayResolve === true && (result.kind !== target.route.kind || result.pattern !== target.route.pattern || result.routePathname !== void 0 && normalizeCacheabilityRoutePathname(result.routePathname) !== group.routePathname)) deferPairedRepresentationsAtOriginalRoute(group);
|
|
420
|
+
group.dropped = true;
|
|
421
|
+
if (!group.pattern.groups.some((candidate) => candidate !== group && candidate.resultKey === group.resultKey)) group.pattern.resultKeys.delete(group.resultKey);
|
|
422
|
+
skippedPathCount += 1;
|
|
423
|
+
completedPathCount += 1;
|
|
424
|
+
reportProgress();
|
|
425
|
+
return "done";
|
|
426
|
+
}
|
|
400
427
|
if (result.state === "probe-failed") {
|
|
401
428
|
failures.push(`${target.label}: ${result.reason ?? "probe failed"}`);
|
|
402
429
|
completedPathCount += 1;
|
|
@@ -432,6 +459,8 @@ async function probeStagedWorkerCacheability(options) {
|
|
|
432
459
|
});
|
|
433
460
|
if (result.routePathname !== void 0) updateGroupRoutePathname(group, result.routePathname);
|
|
434
461
|
const classification = {
|
|
462
|
+
dynamicUsage: result.dynamicUsage === true,
|
|
463
|
+
explicitConfigCachePolicy: result.explicitConfigCachePolicy === true,
|
|
435
464
|
rendererStatic: result.rendererStatic === true,
|
|
436
465
|
representation: target.kind,
|
|
437
466
|
state: result.state,
|
|
@@ -439,7 +468,12 @@ async function probeStagedWorkerCacheability(options) {
|
|
|
439
468
|
};
|
|
440
469
|
group.result = classification;
|
|
441
470
|
const previousClassification = group.pattern.results.get(group.resultKey);
|
|
442
|
-
|
|
471
|
+
const retainedClassification = !previousClassification || previousClassification.state === "static-candidate" && classification.state === "dynamic" || previousClassification.state === classification.state && previousClassification.rendererStatic && !classification.rendererStatic ? classification : previousClassification;
|
|
472
|
+
group.pattern.results.set(group.resultKey, {
|
|
473
|
+
...retainedClassification,
|
|
474
|
+
dynamicUsage: classification.dynamicUsage || previousClassification?.dynamicUsage === true,
|
|
475
|
+
explicitConfigCachePolicy: classification.explicitConfigCachePolicy || previousClassification?.explicitConfigCachePolicy === true
|
|
476
|
+
});
|
|
443
477
|
if (result.state === "dynamic" && result.scope === "pattern" && group.pattern.canPrune) {
|
|
444
478
|
group.pattern.pruned = true;
|
|
445
479
|
dynamicPathCount += 1;
|
|
@@ -543,6 +577,7 @@ async function probeStagedWorkerCacheability(options) {
|
|
|
543
577
|
classified += 1;
|
|
544
578
|
}
|
|
545
579
|
if (limitFailure) throw limitFailure;
|
|
580
|
+
const loadingBoundaryRoutePatterns = new Set(options.loadingBoundaryRoutePatterns);
|
|
546
581
|
for (const pattern of patterns.values()) {
|
|
547
582
|
if (pattern.pruned) {
|
|
548
583
|
classified += 1;
|
|
@@ -562,12 +597,47 @@ async function probeStagedWorkerCacheability(options) {
|
|
|
562
597
|
}
|
|
563
598
|
continue;
|
|
564
599
|
}
|
|
565
|
-
|
|
600
|
+
const hasStaticFallback = fallbackRoutes.has(pattern.key);
|
|
601
|
+
if (pattern.results.size === 0 && !pattern.groups.some((group) => group.deferred)) {
|
|
602
|
+
if (hasStaticFallback) {
|
|
603
|
+
const route = {
|
|
604
|
+
kind: pattern.route.kind,
|
|
605
|
+
pattern: pattern.route.pattern,
|
|
606
|
+
state: "static-candidate"
|
|
607
|
+
};
|
|
608
|
+
if (!addRouteWithinManifestLimits(pattern.key, route)) break;
|
|
609
|
+
classified += 1;
|
|
610
|
+
}
|
|
611
|
+
continue;
|
|
612
|
+
}
|
|
566
613
|
classified += 1;
|
|
567
614
|
if (Array.from(pattern.results.values()).some((result) => result.state === "dynamic")) dynamic += 1;
|
|
615
|
+
const isAppPage = pattern.route.kind === "app-page";
|
|
616
|
+
const listedResults = pattern.groups.flatMap((group) => {
|
|
617
|
+
const result = !group.deferred && !group.dropped && isListedGroup(group) ? pattern.results.get(group.resultKey) : void 0;
|
|
618
|
+
return result ? [result] : [];
|
|
619
|
+
});
|
|
620
|
+
const hasOnDemandIsr = hasStaticFallback || /(^|\/):/.test(pattern.route.pattern) && listedResults.length > 0 && !listedResults.some((result) => result.dynamicUsage);
|
|
621
|
+
const hasNoState = (group) => {
|
|
622
|
+
const result = pattern.results.get(group.resultKey);
|
|
623
|
+
if (!isAppPage || !result) return false;
|
|
624
|
+
if (result.state === "static-candidate" && !result.rendererStatic && result.explicitConfigCachePolicy) return false;
|
|
625
|
+
return result.dynamicUsage || !hasOnDemandIsr && !isListedGroup(group);
|
|
626
|
+
};
|
|
568
627
|
const rendererStaticTargets = /* @__PURE__ */ new Map();
|
|
569
628
|
const runtimePathSet = /* @__PURE__ */ new Set();
|
|
629
|
+
const loadingShellRuntimePathSet = /* @__PURE__ */ new Set();
|
|
570
630
|
for (const group of pattern.groups) {
|
|
631
|
+
if (group.dropped) continue;
|
|
632
|
+
if (!group.deferred && hasNoState(group)) {
|
|
633
|
+
const loadingShellTargets = group.targets.filter((target) => target.kind === "rsc-loading-shell");
|
|
634
|
+
if (loadingShellTargets.length > 0 && pattern.results.get(group.resultKey)?.dynamicUsage && (hasOnDemandIsr || isListedGroup(group))) {
|
|
635
|
+
loadingShellRuntimePathSet.add(group.routePathname);
|
|
636
|
+
cacheableTargets.push(...loadingShellTargets);
|
|
637
|
+
speculativeTargets.push(...loadingShellTargets);
|
|
638
|
+
}
|
|
639
|
+
continue;
|
|
640
|
+
}
|
|
571
641
|
if (group.deferred) {
|
|
572
642
|
runtimePathSet.add(group.routePathname);
|
|
573
643
|
cacheableTargets.push(...group.targets);
|
|
@@ -591,26 +661,48 @@ async function probeStagedWorkerCacheability(options) {
|
|
|
591
661
|
speculativeTargets.push(...pairedTargets);
|
|
592
662
|
}
|
|
593
663
|
}
|
|
664
|
+
const staticRepresentations = (representation) => isAppPage && representation === "html" ? [
|
|
665
|
+
"html",
|
|
666
|
+
"rsc-full",
|
|
667
|
+
...loadingBoundaryRoutePatterns.has(pattern.route.pattern) ? ["rsc-loading-shell"] : []
|
|
668
|
+
] : [representation];
|
|
594
669
|
const staticPaths = {};
|
|
595
670
|
for (const [routePathname, staticTarget] of rendererStaticTargets) {
|
|
596
671
|
if (runtimePathSet.has(routePathname)) continue;
|
|
597
|
-
const
|
|
598
|
-
|
|
599
|
-
|
|
672
|
+
for (const representation of staticRepresentations(staticTarget.kind)) {
|
|
673
|
+
const paths = staticPaths[representation] ?? [];
|
|
674
|
+
paths.push(routePathname);
|
|
675
|
+
staticPaths[representation] = paths;
|
|
676
|
+
}
|
|
600
677
|
}
|
|
601
678
|
for (const paths of Object.values(staticPaths)) paths?.sort();
|
|
679
|
+
const loadingShellRuntimePaths = Array.from(loadingShellRuntimePathSet).filter((routePathname) => !runtimePathSet.has(routePathname) && !staticPaths["rsc-loading-shell"]?.includes(routePathname)).sort();
|
|
680
|
+
const runtimeRepresentationPaths = loadingShellRuntimePaths.length > 0 ? { "rsc-loading-shell": loadingShellRuntimePaths } : void 0;
|
|
602
681
|
const allObservedPathsStaticallyGenerated = pattern.results.size === pattern.resultKeys.size && Array.from(pattern.results.values()).every((result) => result.state === "static-candidate") && Array.from(pattern.results.values()).every((result) => result.rendererStatic);
|
|
603
|
-
const
|
|
682
|
+
const allowsUnknown = isAppPage ? hasOnDemandIsr : allObservedPathsStaticallyGenerated;
|
|
604
683
|
const soleGroup = pattern.groups.length === 1 ? pattern.groups[0] : null;
|
|
605
684
|
const literalPatternNamesSolePath = soleGroup !== null && !/(^|\/):/.test(pattern.route.pattern) && normalizeCacheabilityRoutePathname(pattern.route.pattern) === soleGroup.routePathname;
|
|
606
685
|
let route;
|
|
607
|
-
if (literalPatternNamesSolePath) {
|
|
686
|
+
if (literalPatternNamesSolePath && !soleGroup.deferred && hasNoState(soleGroup)) {
|
|
687
|
+
if (!runtimeRepresentationPaths) continue;
|
|
688
|
+
route = {
|
|
689
|
+
kind: pattern.route.kind,
|
|
690
|
+
pattern: pattern.route.pattern,
|
|
691
|
+
runtimeRepresentation: "rsc-loading-shell",
|
|
692
|
+
state: "runtime-check"
|
|
693
|
+
};
|
|
694
|
+
} else if (literalPatternNamesSolePath) {
|
|
608
695
|
const result = pattern.results.get(soleGroup.resultKey);
|
|
609
696
|
route = result?.state === "static-candidate" ? pattern.route.kind === "app-route" ? {
|
|
610
697
|
kind: pattern.route.kind,
|
|
611
698
|
pattern: pattern.route.pattern,
|
|
612
699
|
state: "static-candidate"
|
|
613
|
-
} : result.rendererStatic ? {
|
|
700
|
+
} : result.rendererStatic && isAppPage && result.representation === "html" ? compactManifestRoutePaths({
|
|
701
|
+
kind: pattern.route.kind,
|
|
702
|
+
pattern: pattern.route.pattern,
|
|
703
|
+
state: "runtime-check",
|
|
704
|
+
staticPaths: Object.fromEntries(staticRepresentations("html").map((representation) => [representation, [soleGroup.routePathname]]))
|
|
705
|
+
}) : result.rendererStatic ? {
|
|
614
706
|
kind: pattern.route.kind,
|
|
615
707
|
pattern: pattern.route.pattern,
|
|
616
708
|
state: "runtime-check",
|
|
@@ -624,15 +716,17 @@ async function probeStagedWorkerCacheability(options) {
|
|
|
624
716
|
pattern: pattern.route.pattern,
|
|
625
717
|
state: "runtime-check"
|
|
626
718
|
};
|
|
627
|
-
} else
|
|
719
|
+
} else if (isAppPage && !hasOnDemandIsr && runtimePathSet.size === 0 && !runtimeRepresentationPaths && Object.keys(staticPaths).length === 0) continue;
|
|
720
|
+
else route = compactManifestRoutePaths({
|
|
628
721
|
kind: pattern.route.kind,
|
|
629
722
|
pattern: pattern.route.pattern,
|
|
630
723
|
state: "runtime-check",
|
|
631
|
-
...hasStaticFallback ||
|
|
724
|
+
...hasStaticFallback || allowsUnknown ? {
|
|
632
725
|
allowUnknown: true,
|
|
633
726
|
unknownState: "static-candidate"
|
|
634
727
|
} : {},
|
|
635
728
|
...runtimePathSet.size > 0 ? { runtimePaths: Array.from(runtimePathSet).sort() } : {},
|
|
729
|
+
...runtimeRepresentationPaths ? { runtimeRepresentationPaths } : {},
|
|
636
730
|
...Object.keys(staticPaths).length > 0 ? { staticPaths } : {}
|
|
637
731
|
});
|
|
638
732
|
if (!addRouteWithinManifestLimits(pattern.key, route)) break;
|
package/dist/cdn-warm.d.ts
CHANGED
|
@@ -80,6 +80,7 @@ export type PrerenderWarmPlan = {
|
|
|
80
80
|
buildIdentity?: string;
|
|
81
81
|
deploymentId?: string;
|
|
82
82
|
fallbackRoutePatterns?: PrerenderRoutePattern[];
|
|
83
|
+
loadingBoundaryRoutePatterns?: string[];
|
|
83
84
|
loadingShellPaths: string[];
|
|
84
85
|
pagesDataPaths?: string[];
|
|
85
86
|
pagesPaths?: string[];
|