@solcreek/adapter-creek 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build.js +326 -31
- package/dist/bundler.d.ts +3 -0
- package/dist/bundler.js +873 -27
- package/dist/cache-handler.d.ts +1 -31
- package/dist/cache-handler.js +8 -98
- package/dist/index.js +44 -218
- package/dist/manifest.d.ts +2 -9
- package/dist/manifest.js +7 -0
- package/dist/worker-entry.js +4048 -460
- package/package.json +2 -1
- package/src/shims/fs.js +57 -13
- package/src/shims/http.js +20 -1
package/dist/build.js
CHANGED
|
@@ -131,7 +131,7 @@ export async function handleBuild(ctx) {
|
|
|
131
131
|
// Step 3b: Collect prerender entries for ISR cache seeding.
|
|
132
132
|
// Each prerender with a fallback file gets seeded into the cache at startup.
|
|
133
133
|
const fallbackShellRoutes = await collectFallbackShellRoutes(ctx.distDir);
|
|
134
|
-
const prerenderEntries = await collectPrerenderEntries(ctx.outputs, fallbackShellRoutes);
|
|
134
|
+
const prerenderEntries = await collectPrerenderEntries(ctx.outputs, fallbackShellRoutes, ctx.distDir);
|
|
135
135
|
if (prerenderEntries.length > 0) {
|
|
136
136
|
console.log(` [Creek Adapter] ${prerenderEntries.length} prerender entries for cache seeding`);
|
|
137
137
|
}
|
|
@@ -140,7 +140,7 @@ export async function handleBuild(ctx) {
|
|
|
140
140
|
// requests matching that shell — mirrors Next.js's request-scoped RDC and
|
|
141
141
|
// keeps e.g. /with-suspense/* build-time values out of /without-suspense/*
|
|
142
142
|
// requests that expect fresh runtime renders.
|
|
143
|
-
const composableCacheSeedsByShell = await collectComposableCacheSeeds(ctx.outputs, fallbackShellRoutes);
|
|
143
|
+
const composableCacheSeedsByShell = await collectComposableCacheSeeds(ctx.outputs, fallbackShellRoutes, ctx.distDir);
|
|
144
144
|
const composableCacheSeedEntries = Array.from(composableCacheSeedsByShell.entries());
|
|
145
145
|
const composableCacheSeedCount = composableCacheSeedEntries.reduce((n, [, seeds]) => n + seeds.length, 0);
|
|
146
146
|
if (composableCacheSeedCount > 0) {
|
|
@@ -339,7 +339,7 @@ export async function handleBuild(ctx) {
|
|
|
339
339
|
// handlers may read at runtime via fs.readFileSync. Next.js's adapter API
|
|
340
340
|
// exposes these per-output as `output.assets` (the result of file tracing).
|
|
341
341
|
// We embed them in __USER_FILES so the fs shim can serve them in workerd.
|
|
342
|
-
const userFiles = await collectUserFiles(ctx.outputs);
|
|
342
|
+
const userFiles = await collectUserFiles(ctx.outputs, ctx.distDir, ctx.projectDir);
|
|
343
343
|
if (Object.keys(userFiles).length > 0) {
|
|
344
344
|
console.log(` [Creek Adapter] ${Object.keys(userFiles).length} user data files embedded`);
|
|
345
345
|
}
|
|
@@ -624,6 +624,31 @@ function isStaticHtmlPage(pathname) {
|
|
|
624
624
|
return true;
|
|
625
625
|
return !path.extname(pathname);
|
|
626
626
|
}
|
|
627
|
+
function internalStaticPageAssetPath(pathname) {
|
|
628
|
+
const encoded = encodeURIComponent(pathname).replace(/%/g, "~");
|
|
629
|
+
return path.join("_creek", "static-pages", encoded + ".html");
|
|
630
|
+
}
|
|
631
|
+
const METADATA_ROUTE_NAMES = new Set([
|
|
632
|
+
"sitemap",
|
|
633
|
+
"robots",
|
|
634
|
+
"manifest",
|
|
635
|
+
"opengraph-image",
|
|
636
|
+
"twitter-image",
|
|
637
|
+
"icon",
|
|
638
|
+
"apple-icon",
|
|
639
|
+
"favicon",
|
|
640
|
+
]);
|
|
641
|
+
function isMetadataRoutePath(pathname) {
|
|
642
|
+
const segments = pathname.split("/").filter(Boolean);
|
|
643
|
+
if (segments.length === 0)
|
|
644
|
+
return false;
|
|
645
|
+
const last = segments[segments.length - 1] || "";
|
|
646
|
+
const lastNoExt = last.replace(/\.[a-z0-9]+$/i, "");
|
|
647
|
+
if (METADATA_ROUTE_NAMES.has(lastNoExt))
|
|
648
|
+
return true;
|
|
649
|
+
const secondLast = segments[segments.length - 2] || "";
|
|
650
|
+
return METADATA_ROUTE_NAMES.has(secondLast);
|
|
651
|
+
}
|
|
627
652
|
// Inject `data-dpl-id="<buildId>"` into the `<html>` tag of static HTML
|
|
628
653
|
// files at build time. The Pages Router client reads this attribute on
|
|
629
654
|
// page load to populate `globalThis.NEXT_DEPLOYMENT_ID`, then sends
|
|
@@ -685,6 +710,24 @@ async function safeMkdirForDest(destPath, label) {
|
|
|
685
710
|
async function collectStaticFiles(outputs, assetsDir, projectDir, distDir, buildId) {
|
|
686
711
|
let count = 0;
|
|
687
712
|
const allPathnames = new Set(outputs.staticFiles.map((f) => f.pathname));
|
|
713
|
+
const appRoutePathnames = new Set(outputs.appRoutes.map((route) => route.pathname));
|
|
714
|
+
const copyStaticHtml = async (srcPath, destRelative) => {
|
|
715
|
+
const destPath = path.join(assetsDir, destRelative);
|
|
716
|
+
if (!(await safeMkdirForDest(destPath, destRelative)))
|
|
717
|
+
return false;
|
|
718
|
+
try {
|
|
719
|
+
if (buildId) {
|
|
720
|
+
await copyHtmlWithDplId(srcPath, destPath, buildId);
|
|
721
|
+
}
|
|
722
|
+
else {
|
|
723
|
+
await fs.copyFile(srcPath, destPath);
|
|
724
|
+
}
|
|
725
|
+
return true;
|
|
726
|
+
}
|
|
727
|
+
catch {
|
|
728
|
+
return false;
|
|
729
|
+
}
|
|
730
|
+
};
|
|
688
731
|
for (const file of outputs.staticFiles) {
|
|
689
732
|
let destRelative = file.pathname;
|
|
690
733
|
const isHtml = isStaticHtmlPage(destRelative);
|
|
@@ -706,6 +749,10 @@ async function collectStaticFiles(outputs, assetsDir, projectDir, distDir, build
|
|
|
706
749
|
count++;
|
|
707
750
|
}
|
|
708
751
|
catch { }
|
|
752
|
+
if (isHtml && file.pathname.includes("[")) {
|
|
753
|
+
if (await copyStaticHtml(file.filePath, internalStaticPageAssetPath(file.pathname)))
|
|
754
|
+
count++;
|
|
755
|
+
}
|
|
709
756
|
}
|
|
710
757
|
for (const prerender of outputs.prerenders) {
|
|
711
758
|
if (prerender.fallback?.filePath) {
|
|
@@ -717,6 +764,11 @@ async function collectStaticFiles(outputs, assetsDir, projectDir, distDir, build
|
|
|
717
764
|
// extension over the pathname — \`/opengraph-image\` has no dot but
|
|
718
765
|
// maps to \`opengraph-image.body\`.
|
|
719
766
|
const isBinary = prerender.fallback.filePath.endsWith(".body");
|
|
767
|
+
if (isBinary && !appRoutePathnames.has(prerender.pathname)) {
|
|
768
|
+
// Static App Page .body fallbacks are RSC artifacts; copying them
|
|
769
|
+
// extensionless would block nested paths like /(.)foo/bar.
|
|
770
|
+
continue;
|
|
771
|
+
}
|
|
720
772
|
const isHtml = !isBinary && isStaticHtmlPage(prerender.pathname);
|
|
721
773
|
let destRelative = prerender.pathname;
|
|
722
774
|
if (isHtml) {
|
|
@@ -735,7 +787,104 @@ async function collectStaticFiles(outputs, assetsDir, projectDir, distDir, build
|
|
|
735
787
|
count++;
|
|
736
788
|
}
|
|
737
789
|
catch { }
|
|
790
|
+
if (isHtml && prerender.pathname.includes("[")) {
|
|
791
|
+
if (await copyStaticHtml(prerender.fallback.filePath, internalStaticPageAssetPath(prerender.pathname)))
|
|
792
|
+
count++;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
if (distDir) {
|
|
797
|
+
try {
|
|
798
|
+
const manifestPath = path.join(distDir, "prerender-manifest.json");
|
|
799
|
+
const manifest = JSON.parse(await fs.readFile(manifestPath, "utf8"));
|
|
800
|
+
for (const routePath of Object.keys(manifest.routes || {})) {
|
|
801
|
+
if (allPathnames.has(routePath))
|
|
802
|
+
continue;
|
|
803
|
+
if (!isMetadataRoutePath(routePath))
|
|
804
|
+
continue;
|
|
805
|
+
const relativeRoutePath = routePath.replace(/^\/+/, "");
|
|
806
|
+
const sourceCandidates = [
|
|
807
|
+
path.join(distDir, "server", "app", relativeRoutePath + ".body"),
|
|
808
|
+
path.join(distDir, "server", "app", relativeRoutePath + ".html"),
|
|
809
|
+
path.join(distDir, "server", "app", relativeRoutePath),
|
|
810
|
+
];
|
|
811
|
+
let sourcePath = null;
|
|
812
|
+
for (const candidate of sourceCandidates) {
|
|
813
|
+
try {
|
|
814
|
+
await fs.access(candidate);
|
|
815
|
+
sourcePath = candidate;
|
|
816
|
+
break;
|
|
817
|
+
}
|
|
818
|
+
catch { }
|
|
819
|
+
}
|
|
820
|
+
if (!sourcePath)
|
|
821
|
+
continue;
|
|
822
|
+
const isHtml = sourcePath.endsWith(".html") && isStaticHtmlPage(routePath);
|
|
823
|
+
const destRelative = isHtml
|
|
824
|
+
? path.join(routePath, "index.html")
|
|
825
|
+
: routePath;
|
|
826
|
+
const destPath = path.join(assetsDir, destRelative);
|
|
827
|
+
if (!(await safeMkdirForDest(destPath, destRelative)))
|
|
828
|
+
continue;
|
|
829
|
+
if (isHtml && buildId) {
|
|
830
|
+
await copyHtmlWithDplId(sourcePath, destPath, buildId);
|
|
831
|
+
}
|
|
832
|
+
else {
|
|
833
|
+
await fs.copyFile(sourcePath, destPath);
|
|
834
|
+
}
|
|
835
|
+
allPathnames.add(routePath);
|
|
836
|
+
count++;
|
|
837
|
+
}
|
|
738
838
|
}
|
|
839
|
+
catch { }
|
|
840
|
+
try {
|
|
841
|
+
const manifestPath = path.join(distDir, "prerender-manifest.json");
|
|
842
|
+
const manifest = JSON.parse(await fs.readFile(manifestPath, "utf8"));
|
|
843
|
+
for (const [route, dynamicRoute] of Object.entries(manifest.dynamicRoutes || {})) {
|
|
844
|
+
const fallback = dynamicRoute?.fallback;
|
|
845
|
+
if (typeof fallback !== "string" || !fallback.endsWith(".html"))
|
|
846
|
+
continue;
|
|
847
|
+
const relativeFallback = fallback.replace(/^\/+/, "");
|
|
848
|
+
const sourceCandidates = [
|
|
849
|
+
path.join(distDir, "server", "pages", relativeFallback),
|
|
850
|
+
path.join(distDir, "server", "app", relativeFallback),
|
|
851
|
+
];
|
|
852
|
+
let sourcePath = null;
|
|
853
|
+
for (const candidate of sourceCandidates) {
|
|
854
|
+
try {
|
|
855
|
+
await fs.access(candidate);
|
|
856
|
+
sourcePath = candidate;
|
|
857
|
+
break;
|
|
858
|
+
}
|
|
859
|
+
catch { }
|
|
860
|
+
}
|
|
861
|
+
if (!sourcePath)
|
|
862
|
+
continue;
|
|
863
|
+
const isHtml = isStaticHtmlPage(route);
|
|
864
|
+
const destRelative = isHtml
|
|
865
|
+
? path.join(route, "index.html")
|
|
866
|
+
: route;
|
|
867
|
+
const destPath = path.join(assetsDir, destRelative);
|
|
868
|
+
if (!(await safeMkdirForDest(destPath, destRelative)))
|
|
869
|
+
continue;
|
|
870
|
+
if (buildId) {
|
|
871
|
+
await copyHtmlWithDplId(sourcePath, destPath, buildId);
|
|
872
|
+
}
|
|
873
|
+
else {
|
|
874
|
+
await fs.copyFile(sourcePath, destPath);
|
|
875
|
+
}
|
|
876
|
+
count++;
|
|
877
|
+
if (isHtml && relativeFallback !== destRelative) {
|
|
878
|
+
if (await copyStaticHtml(sourcePath, relativeFallback))
|
|
879
|
+
count++;
|
|
880
|
+
}
|
|
881
|
+
if (isHtml && route.includes("[")) {
|
|
882
|
+
if (await copyStaticHtml(sourcePath, internalStaticPageAssetPath(route)))
|
|
883
|
+
count++;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
catch { }
|
|
739
888
|
}
|
|
740
889
|
// Edge asset bindings (\`.next/server/edge-chunks/asset_*\`). Edge routes
|
|
741
890
|
// that do \`fetch(new URL('../../assets/foo', import.meta.url))\` get
|
|
@@ -913,6 +1062,26 @@ async function collectJsFilesRecursive(dir) {
|
|
|
913
1062
|
}
|
|
914
1063
|
return files;
|
|
915
1064
|
}
|
|
1065
|
+
async function collectFilesRecursive(dir) {
|
|
1066
|
+
let entries;
|
|
1067
|
+
try {
|
|
1068
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
1069
|
+
}
|
|
1070
|
+
catch {
|
|
1071
|
+
return [];
|
|
1072
|
+
}
|
|
1073
|
+
const files = [];
|
|
1074
|
+
for (const entry of entries) {
|
|
1075
|
+
const absPath = path.join(dir, entry.name);
|
|
1076
|
+
if (entry.isDirectory()) {
|
|
1077
|
+
files.push(...await collectFilesRecursive(absPath));
|
|
1078
|
+
continue;
|
|
1079
|
+
}
|
|
1080
|
+
if (entry.isFile())
|
|
1081
|
+
files.push(absPath);
|
|
1082
|
+
}
|
|
1083
|
+
return files;
|
|
1084
|
+
}
|
|
916
1085
|
async function getTotalSize(dir, files) {
|
|
917
1086
|
let total = 0;
|
|
918
1087
|
for (const f of files) {
|
|
@@ -1007,7 +1176,7 @@ async function collectManifests(distDir) {
|
|
|
1007
1176
|
* worker bundle when a project has large data assets — the user can hit it
|
|
1008
1177
|
* explicitly to force a different deployment strategy.
|
|
1009
1178
|
*/
|
|
1010
|
-
async function collectUserFiles(outputs) {
|
|
1179
|
+
async function collectUserFiles(outputs, distDir, projectDir) {
|
|
1011
1180
|
const TEXT_EXTENSIONS = new Set([
|
|
1012
1181
|
".json", ".txt", ".yaml", ".yml", ".md", ".csv", ".xml",
|
|
1013
1182
|
".html", ".htm", ".sql", ".graphql", ".gql", ".env",
|
|
@@ -1029,6 +1198,9 @@ async function collectUserFiles(outputs) {
|
|
|
1029
1198
|
...outputs.pages,
|
|
1030
1199
|
...outputs.pagesApi,
|
|
1031
1200
|
];
|
|
1201
|
+
if (outputs.middleware) {
|
|
1202
|
+
allOutputs.push(outputs.middleware);
|
|
1203
|
+
}
|
|
1032
1204
|
for (const output of allOutputs) {
|
|
1033
1205
|
const assets = output.assets;
|
|
1034
1206
|
if (!assets)
|
|
@@ -1082,6 +1254,55 @@ async function collectUserFiles(outputs) {
|
|
|
1082
1254
|
}
|
|
1083
1255
|
}
|
|
1084
1256
|
}
|
|
1257
|
+
if (distDir) {
|
|
1258
|
+
const serverAssetsDir = path.join(distDir, "server", "assets");
|
|
1259
|
+
const serverAssets = await collectFilesRecursive(serverAssetsDir);
|
|
1260
|
+
for (const sourceFile of serverAssets) {
|
|
1261
|
+
const fileOutputPath = path
|
|
1262
|
+
.relative(path.join(distDir, "server"), sourceFile)
|
|
1263
|
+
.replace(/\\/g, "/");
|
|
1264
|
+
const embeddedPath = "server/" + fileOutputPath;
|
|
1265
|
+
if (files[embeddedPath])
|
|
1266
|
+
continue;
|
|
1267
|
+
const ext = path.extname(sourceFile).toLowerCase();
|
|
1268
|
+
const isDeclarationFile = sourceFile.endsWith(".d.ts");
|
|
1269
|
+
const isText = TEXT_EXTENSIONS.has(ext) || isDeclarationFile;
|
|
1270
|
+
const isBinary = BINARY_EXTENSIONS.has(ext);
|
|
1271
|
+
if (!isText && !isBinary)
|
|
1272
|
+
continue;
|
|
1273
|
+
try {
|
|
1274
|
+
if (isText) {
|
|
1275
|
+
const content = await fs.readFile(sourceFile, "utf-8");
|
|
1276
|
+
if (totalBytes + content.length > MAX_TOTAL_BYTES)
|
|
1277
|
+
continue;
|
|
1278
|
+
files[embeddedPath] = content;
|
|
1279
|
+
totalBytes += content.length;
|
|
1280
|
+
}
|
|
1281
|
+
else {
|
|
1282
|
+
const buffer = await fs.readFile(sourceFile);
|
|
1283
|
+
const encoded = "__CREEK_B64__" + buffer.toString("base64");
|
|
1284
|
+
if (totalBytes + encoded.length > MAX_TOTAL_BYTES)
|
|
1285
|
+
continue;
|
|
1286
|
+
files[embeddedPath] = encoded;
|
|
1287
|
+
totalBytes += encoded.length;
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
catch { }
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
if (projectDir) {
|
|
1294
|
+
const packageJsonPath = path.join(projectDir, "package.json");
|
|
1295
|
+
if (!files["package.json"]) {
|
|
1296
|
+
try {
|
|
1297
|
+
const content = await fs.readFile(packageJsonPath, "utf-8");
|
|
1298
|
+
if (totalBytes + content.length <= MAX_TOTAL_BYTES) {
|
|
1299
|
+
files["package.json"] = content;
|
|
1300
|
+
totalBytes += content.length;
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
catch { }
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1085
1306
|
return files;
|
|
1086
1307
|
}
|
|
1087
1308
|
async function collectFallbackShellRoutes(distDir) {
|
|
@@ -1105,12 +1326,21 @@ async function collectFallbackShellRoutes(distDir) {
|
|
|
1105
1326
|
return null;
|
|
1106
1327
|
}
|
|
1107
1328
|
}
|
|
1329
|
+
async function readAppRouteMeta(distDir, pathname) {
|
|
1330
|
+
try {
|
|
1331
|
+
const metaPath = path.join(distDir, "server", "app", pathname.replace(/^\/+/, "") + ".meta");
|
|
1332
|
+
return JSON.parse(await fs.readFile(metaPath, "utf-8"));
|
|
1333
|
+
}
|
|
1334
|
+
catch {
|
|
1335
|
+
return null;
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1108
1338
|
/**
|
|
1109
1339
|
* Collect prerender entries from build outputs for App Router PPR/cache seeding.
|
|
1110
1340
|
* Pages Router prerenders are served from assets and don't need to be embedded
|
|
1111
1341
|
* in the worker bundle.
|
|
1112
1342
|
*/
|
|
1113
|
-
async function collectPrerenderEntries(outputs, fallbackShellRoutes) {
|
|
1343
|
+
async function collectPrerenderEntries(outputs, fallbackShellRoutes, distDir) {
|
|
1114
1344
|
const entries = [];
|
|
1115
1345
|
// The Next adapter emits one \`prerenders\` entry per output file — including
|
|
1116
1346
|
// \`.rsc\` sidecars and \`.segments/*.segment.rsc\` fragments. Those aren't
|
|
@@ -1121,8 +1351,18 @@ async function collectPrerenderEntries(outputs, fallbackShellRoutes) {
|
|
|
1121
1351
|
const fallback = prerender.fallback;
|
|
1122
1352
|
if (!fallback?.filePath || !fallback.filePath.endsWith(".html"))
|
|
1123
1353
|
continue;
|
|
1124
|
-
const
|
|
1125
|
-
|
|
1354
|
+
const metaPath = fallback.filePath.replace(/\.(html|body)$/, ".meta");
|
|
1355
|
+
const meta = await fs.readFile(metaPath, "utf-8")
|
|
1356
|
+
.then((raw) => JSON.parse(raw))
|
|
1357
|
+
.catch(() => null);
|
|
1358
|
+
const postponedState = typeof fallback.postponedState === "string" &&
|
|
1359
|
+
fallback.postponedState.length > 0
|
|
1360
|
+
? fallback.postponedState
|
|
1361
|
+
: typeof meta?.postponed === "string" && meta.postponed.length > 0
|
|
1362
|
+
? meta.postponed
|
|
1363
|
+
: undefined;
|
|
1364
|
+
const hasPostponedState = typeof postponedState === "string" &&
|
|
1365
|
+
postponedState.length > 0;
|
|
1126
1366
|
const hasPprHeaders = !!prerender.pprChain?.headers;
|
|
1127
1367
|
const isPprChain = hasPostponedState || hasPprHeaders;
|
|
1128
1368
|
// Skip bracket-form fallback shells — they're handled via the
|
|
@@ -1131,10 +1371,6 @@ async function collectPrerenderEntries(outputs, fallbackShellRoutes) {
|
|
|
1131
1371
|
continue;
|
|
1132
1372
|
try {
|
|
1133
1373
|
const stat = await fs.stat(fallback.filePath).catch(() => null);
|
|
1134
|
-
const metaPath = fallback.filePath.replace(/\.(html|body)$/, ".meta");
|
|
1135
|
-
const meta = await fs.readFile(metaPath, "utf-8")
|
|
1136
|
-
.then((raw) => JSON.parse(raw))
|
|
1137
|
-
.catch(() => null);
|
|
1138
1374
|
// Never inline HTML into the worker bundle — it can be multi-MB
|
|
1139
1375
|
// (e.g. memory-pressure pages are ~2MB each). \`__creekSeededAppPageEntry\`
|
|
1140
1376
|
// fetches HTML and RSC from the assets bucket at request time. The seed
|
|
@@ -1142,10 +1378,11 @@ async function collectPrerenderEntries(outputs, fallbackShellRoutes) {
|
|
|
1142
1378
|
entries.push({
|
|
1143
1379
|
pathname: prerender.pathname,
|
|
1144
1380
|
html: "",
|
|
1145
|
-
postponedState
|
|
1146
|
-
allowsFallbackShellResume:
|
|
1147
|
-
|
|
1148
|
-
|
|
1381
|
+
postponedState,
|
|
1382
|
+
allowsFallbackShellResume: prerender.config?.partialFallback === true &&
|
|
1383
|
+
(fallbackShellRoutes
|
|
1384
|
+
? fallbackShellRoutes.has(prerender.pathname)
|
|
1385
|
+
: true),
|
|
1149
1386
|
initialRevalidate: fallback.initialRevalidate,
|
|
1150
1387
|
initialStatus: fallback.initialStatus,
|
|
1151
1388
|
initialHeaders: fallback.initialHeaders,
|
|
@@ -1162,6 +1399,34 @@ async function collectPrerenderEntries(outputs, fallbackShellRoutes) {
|
|
|
1162
1399
|
// Skip prerenders whose fallback file can't be read
|
|
1163
1400
|
}
|
|
1164
1401
|
}
|
|
1402
|
+
// Some App Router fallback shells (notably cacheComponents routes without
|
|
1403
|
+
// generateStaticParams) are present in prerender-manifest + .meta but are not
|
|
1404
|
+
// surfaced with fallback.postponedState in the adapter outputs. Add minimal
|
|
1405
|
+
// entries from .meta so the worker can still inject requestMeta.postponed.
|
|
1406
|
+
if (fallbackShellRoutes) {
|
|
1407
|
+
const existing = new Set(entries.map((entry) => entry.pathname));
|
|
1408
|
+
for (const pathname of fallbackShellRoutes) {
|
|
1409
|
+
if (existing.has(pathname))
|
|
1410
|
+
continue;
|
|
1411
|
+
const meta = await readAppRouteMeta(distDir, pathname);
|
|
1412
|
+
if (typeof meta?.postponed !== "string" || meta.postponed.length === 0)
|
|
1413
|
+
continue;
|
|
1414
|
+
entries.push({
|
|
1415
|
+
pathname,
|
|
1416
|
+
html: "",
|
|
1417
|
+
postponedState: meta.postponed,
|
|
1418
|
+
allowsFallbackShellResume: true,
|
|
1419
|
+
initialStatus: typeof meta.status === "number" ? meta.status : undefined,
|
|
1420
|
+
initialHeaders: meta.headers && typeof meta.headers === "object"
|
|
1421
|
+
? meta.headers
|
|
1422
|
+
: undefined,
|
|
1423
|
+
segmentPaths: Array.isArray(meta.segmentPaths) ? meta.segmentPaths : undefined,
|
|
1424
|
+
metaHeaders: meta.headers && typeof meta.headers === "object"
|
|
1425
|
+
? meta.headers
|
|
1426
|
+
: undefined,
|
|
1427
|
+
});
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1165
1430
|
return entries;
|
|
1166
1431
|
}
|
|
1167
1432
|
/**
|
|
@@ -1178,37 +1443,29 @@ async function collectPrerenderEntries(outputs, fallbackShellRoutes) {
|
|
|
1178
1443
|
* naturally by Map key — the last seen wins, which is fine since they're
|
|
1179
1444
|
* semantically identical.
|
|
1180
1445
|
*/
|
|
1181
|
-
async function collectComposableCacheSeeds(outputs, fallbackShellRoutes) {
|
|
1446
|
+
async function collectComposableCacheSeeds(outputs, fallbackShellRoutes, distDir) {
|
|
1182
1447
|
const zlib = await import("node:zlib");
|
|
1183
1448
|
// Map bracket-form pathname → its cache entries. Gating by shell prevents
|
|
1184
1449
|
// seeds from one prerender's request-scoped RDC from bleeding into
|
|
1185
1450
|
// unrelated requests (e.g. \`/with-suspense/*\`'s build-time "buildtime"
|
|
1186
1451
|
// leaking into \`/without-suspense/*\` where the test expects "runtime").
|
|
1187
1452
|
const byShell = new Map();
|
|
1188
|
-
|
|
1189
|
-
if (prerender.pathname.includes("[") &&
|
|
1190
|
-
fallbackShellRoutes &&
|
|
1191
|
-
!fallbackShellRoutes.has(prerender.pathname)) {
|
|
1192
|
-
continue;
|
|
1193
|
-
}
|
|
1194
|
-
const postponed = prerender.fallback?.postponedState;
|
|
1195
|
-
if (typeof postponed !== "string" || postponed.length === 0)
|
|
1196
|
-
continue;
|
|
1453
|
+
const extractSeedsFromPostponed = (postponed) => {
|
|
1197
1454
|
const m = postponed.match(/^(\d+):/);
|
|
1198
1455
|
if (!m)
|
|
1199
|
-
|
|
1456
|
+
return null;
|
|
1200
1457
|
const prefixLen = m[0].length;
|
|
1201
1458
|
const postponedLen = parseInt(m[1], 10);
|
|
1202
1459
|
const cacheBlob = postponed.slice(prefixLen + postponedLen);
|
|
1203
1460
|
if (!cacheBlob || cacheBlob === "null")
|
|
1204
|
-
|
|
1461
|
+
return null;
|
|
1205
1462
|
try {
|
|
1206
1463
|
const buf = Buffer.from(cacheBlob, "base64");
|
|
1207
1464
|
const inflated = zlib.inflateSync(buf, { maxOutputLength: 200 * 1024 * 1024 });
|
|
1208
1465
|
const json = JSON.parse(inflated.toString("utf-8"));
|
|
1209
1466
|
const cacheStore = json?.store?.cache;
|
|
1210
1467
|
if (!cacheStore || typeof cacheStore !== "object")
|
|
1211
|
-
|
|
1468
|
+
return null;
|
|
1212
1469
|
const shellSeeds = [];
|
|
1213
1470
|
for (const [key, serialized] of Object.entries(cacheStore)) {
|
|
1214
1471
|
if (!serialized?.entry)
|
|
@@ -1224,10 +1481,48 @@ async function collectComposableCacheSeeds(outputs, fallbackShellRoutes) {
|
|
|
1224
1481
|
revalidate: typeof e.revalidate === "number" ? e.revalidate : Number.MAX_SAFE_INTEGER,
|
|
1225
1482
|
});
|
|
1226
1483
|
}
|
|
1227
|
-
|
|
1228
|
-
|
|
1484
|
+
return shellSeeds.length > 0 ? shellSeeds : null;
|
|
1485
|
+
}
|
|
1486
|
+
catch {
|
|
1487
|
+
return null;
|
|
1488
|
+
}
|
|
1489
|
+
};
|
|
1490
|
+
for (const prerender of outputs.prerenders) {
|
|
1491
|
+
const isPartialFallback = prerender.config?.partialFallback === true;
|
|
1492
|
+
if (prerender.pathname.includes("[") &&
|
|
1493
|
+
(!isPartialFallback ||
|
|
1494
|
+
(fallbackShellRoutes && !fallbackShellRoutes.has(prerender.pathname)))) {
|
|
1495
|
+
continue;
|
|
1496
|
+
}
|
|
1497
|
+
let postponed = prerender.fallback?.postponedState;
|
|
1498
|
+
if ((typeof postponed !== "string" || postponed.length === 0) &&
|
|
1499
|
+
prerender.fallback?.filePath) {
|
|
1500
|
+
const metaPath = prerender.fallback.filePath.replace(/\.(html|body)$/, ".meta");
|
|
1501
|
+
const metaPostponed = await fs.readFile(metaPath, "utf-8")
|
|
1502
|
+
.then((raw) => JSON.parse(raw)?.postponed)
|
|
1503
|
+
.catch(() => null);
|
|
1504
|
+
if (typeof metaPostponed === "string" && metaPostponed.length > 0) {
|
|
1505
|
+
postponed = metaPostponed;
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
if (typeof postponed !== "string" || postponed.length === 0)
|
|
1509
|
+
continue;
|
|
1510
|
+
const shellSeeds = extractSeedsFromPostponed(postponed);
|
|
1511
|
+
if (shellSeeds)
|
|
1512
|
+
byShell.set(prerender.pathname, shellSeeds);
|
|
1513
|
+
}
|
|
1514
|
+
if (fallbackShellRoutes) {
|
|
1515
|
+
for (const pathname of fallbackShellRoutes) {
|
|
1516
|
+
if (byShell.has(pathname))
|
|
1517
|
+
continue;
|
|
1518
|
+
const meta = await readAppRouteMeta(distDir, pathname);
|
|
1519
|
+
const postponed = meta?.postponed;
|
|
1520
|
+
if (typeof postponed !== "string" || postponed.length === 0)
|
|
1521
|
+
continue;
|
|
1522
|
+
const shellSeeds = extractSeedsFromPostponed(postponed);
|
|
1523
|
+
if (shellSeeds)
|
|
1524
|
+
byShell.set(pathname, shellSeeds);
|
|
1229
1525
|
}
|
|
1230
|
-
catch { }
|
|
1231
1526
|
}
|
|
1232
1527
|
return byShell;
|
|
1233
1528
|
}
|
package/dist/bundler.d.ts
CHANGED
|
@@ -16,5 +16,8 @@ export interface BundleOptions {
|
|
|
16
16
|
repoRoot: string;
|
|
17
17
|
standaloneDir: string;
|
|
18
18
|
}
|
|
19
|
+
export declare function patchUseCachePrerenderDanglingPromiseBailout(workerCode: string): string;
|
|
20
|
+
export declare function patchNullFallbackPartialShellBlocking(workerCode: string): string;
|
|
21
|
+
export declare function patchAppPageRevalidationPostponedState(workerCode: string): string;
|
|
19
22
|
export declare function bundleForWorkers(opts: BundleOptions): Promise<string[]>;
|
|
20
23
|
//# sourceMappingURL=bundler.d.ts.map
|