@solcreek/adapter-creek 0.1.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 -1
- package/dist/cache-handler.js +3 -3
- package/dist/index.js +28 -5
- package/dist/manifest.d.ts +2 -9
- package/dist/manifest.js +7 -0
- package/dist/worker-entry.js +4045 -460
- package/package.json +3 -2
- package/src/shims/fs.js +57 -13
- package/src/shims/http.js +20 -1
package/dist/bundler.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import * as fs from "node:fs/promises";
|
|
11
11
|
import * as path from "node:path";
|
|
12
12
|
import { execSync } from "node:child_process";
|
|
13
|
+
import { builtinModules, createRequire } from "node:module";
|
|
13
14
|
/**
|
|
14
15
|
* Patch Turbopack runtime to inline chunk loading.
|
|
15
16
|
*
|
|
@@ -32,18 +33,31 @@ async function patchTurbopackRuntime(distDir) {
|
|
|
32
33
|
path.join(distDir, "server", "chunks"),
|
|
33
34
|
path.join(distDir, "server", "edge", "chunks"),
|
|
34
35
|
];
|
|
35
|
-
|
|
36
|
+
async function walkRuntimes(dir) {
|
|
37
|
+
let entries;
|
|
36
38
|
try {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
for (const entry of entries) {
|
|
45
|
+
const full = path.join(dir, entry.name);
|
|
46
|
+
if (entry.isDirectory()) {
|
|
47
|
+
await walkRuntimes(full);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (!entry.name.endsWith(".js"))
|
|
51
|
+
continue;
|
|
52
|
+
if (entry.name.includes("[turbopack]_runtime") ||
|
|
53
|
+
(entry.name.startsWith("turbopack-") && entry.name.includes("edge-wrapper")) ||
|
|
54
|
+
entry.name.includes("edge-wrapper")) {
|
|
55
|
+
runtimePaths.push(full);
|
|
44
56
|
}
|
|
45
57
|
}
|
|
46
|
-
|
|
58
|
+
}
|
|
59
|
+
for (const dir of searchDirs) {
|
|
60
|
+
await walkRuntimes(dir);
|
|
47
61
|
}
|
|
48
62
|
if (runtimePaths.length === 0)
|
|
49
63
|
return; // Not Turbopack
|
|
@@ -136,18 +150,501 @@ ${cases.join("\n")}
|
|
|
136
150
|
}
|
|
137
151
|
}
|
|
138
152
|
}
|
|
153
|
+
async function patchNodeExternalRequireConditions(distDir) {
|
|
154
|
+
const serverChunksDir = path.join(distDir, "server", "chunks");
|
|
155
|
+
const projectRoot = path.dirname(distDir);
|
|
156
|
+
const projectRequire = createRequire(path.join(projectRoot, "package.json"));
|
|
157
|
+
const builtins = new Set([
|
|
158
|
+
...builtinModules,
|
|
159
|
+
...builtinModules.map((name) => `node:${name}`),
|
|
160
|
+
]);
|
|
161
|
+
// Narrow scope: only `[externals]*.js` files in `server/chunks/`.
|
|
162
|
+
// Those are Turbopack's dedicated external-require-registration bundles,
|
|
163
|
+
// which contain nothing but `.x("pkg", () => require("pkg"))` lines —
|
|
164
|
+
// rewriting require() to an absolute path there has no module-identity
|
|
165
|
+
// risk. We tried extending this to `server/chunks/ssr/*.js` to fix the
|
|
166
|
+
// styled-jsx-subpath case, but those chunks contain real SSR render
|
|
167
|
+
// code whose require() calls participate in Turbopack's module graph
|
|
168
|
+
// (same package resolved two ways = two React instances). The
|
|
169
|
+
// styled-jsx case is now handled via wrangler `alias` entries instead —
|
|
170
|
+
// see collectSsrLazyRequireAliases() + the wranglerConfig assembly.
|
|
171
|
+
let entries;
|
|
172
|
+
try {
|
|
173
|
+
entries = await fs.readdir(serverChunksDir);
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const chunkFiles = [];
|
|
179
|
+
for (const entry of entries) {
|
|
180
|
+
if (!entry.startsWith("[externals]") || !entry.endsWith(".js"))
|
|
181
|
+
continue;
|
|
182
|
+
chunkFiles.push(path.join(serverChunksDir, entry));
|
|
183
|
+
}
|
|
184
|
+
if (chunkFiles.length === 0)
|
|
185
|
+
return;
|
|
186
|
+
const externalRequirePattern = /(\w+\.x\(\s*(["'])([^"']+)\2\s*,\s*\(\)\s*=>\s*)require\(\s*(["'])\3\4\s*\)(\s*\))/g;
|
|
187
|
+
const stripTurbopackPackageAlias = (specifier) => {
|
|
188
|
+
const parts = specifier.split("/");
|
|
189
|
+
const packageIndex = specifier.startsWith("@") ? 1 : 0;
|
|
190
|
+
const packageName = parts[packageIndex];
|
|
191
|
+
if (!packageName)
|
|
192
|
+
return null;
|
|
193
|
+
const match = /^(.*)-[0-9a-f]{16}$/i.exec(packageName);
|
|
194
|
+
if (!match || !match[1])
|
|
195
|
+
return null;
|
|
196
|
+
parts[packageIndex] = match[1];
|
|
197
|
+
return parts.join("/");
|
|
198
|
+
};
|
|
199
|
+
for (const filePath of chunkFiles) {
|
|
200
|
+
let code;
|
|
201
|
+
try {
|
|
202
|
+
code = await fs.readFile(filePath, "utf-8");
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
if (!code.includes(".x(") || !code.includes("require("))
|
|
208
|
+
continue;
|
|
209
|
+
const patched = code.replace(externalRequirePattern, (match, prefix, _quote, specifier, _requireQuote, suffix) => {
|
|
210
|
+
if (specifier.startsWith(".") ||
|
|
211
|
+
specifier.startsWith("/") ||
|
|
212
|
+
builtins.has(specifier) ||
|
|
213
|
+
specifier === "next" ||
|
|
214
|
+
specifier.startsWith("next/") ||
|
|
215
|
+
specifier.startsWith("@next/")) {
|
|
216
|
+
return match;
|
|
217
|
+
}
|
|
218
|
+
let resolved;
|
|
219
|
+
try {
|
|
220
|
+
resolved = projectRequire.resolve(specifier);
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
const unaliased = stripTurbopackPackageAlias(specifier);
|
|
224
|
+
if (!unaliased)
|
|
225
|
+
return match;
|
|
226
|
+
try {
|
|
227
|
+
resolved = projectRequire.resolve(unaliased);
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
return match;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (resolved.endsWith(".node"))
|
|
234
|
+
return match;
|
|
235
|
+
return `${prefix}require(${JSON.stringify(resolved)})${suffix}`;
|
|
236
|
+
});
|
|
237
|
+
if (patched !== code) {
|
|
238
|
+
await fs.writeFile(filePath, patched);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Collect `.x("pkg", () => require("pkg"))` lazy-require specifiers out of
|
|
244
|
+
* `server/chunks/ssr/*.js` and return a map of specifiers → absolute paths
|
|
245
|
+
* that wrangler's esbuild should alias.
|
|
246
|
+
*
|
|
247
|
+
* Why this exists: Turbopack emits those registrations for SSR chunks when
|
|
248
|
+
* a transitive (indirect) dep like `styled-jsx/style.js` is needed. In
|
|
249
|
+
* pnpm projects the package lives at `.pnpm/styled-jsx@X/node_modules/...`
|
|
250
|
+
* and isn't hoisted to a top-level `node_modules/`. esbuild's walker
|
|
251
|
+
* therefore fails with `Could not resolve "styled-jsx/style.js"`.
|
|
252
|
+
*
|
|
253
|
+
* The previous attempt was to rewrite `require("pkg")` inline to an
|
|
254
|
+
* absolute path, mirroring what we do for `[externals]*.js` chunks — but
|
|
255
|
+
* `ssr/*.js` chunks also contain real SSR render code whose require()
|
|
256
|
+
* calls participate in Turbopack's module graph. Absolute-path rewrites
|
|
257
|
+
* there risk creating two module instances of the same package (once via
|
|
258
|
+
* Turbopack, once via our rewrite), which silently breaks React etc.
|
|
259
|
+
* and surfaced as "uncached or runtime data" prerender failures in
|
|
260
|
+
* `resume-data-cache` on Linux CI only.
|
|
261
|
+
*
|
|
262
|
+
* Returning an alias map instead keeps all source untouched. esbuild's
|
|
263
|
+
* alias is a global resolver override, so every reference to the same
|
|
264
|
+
* specifier goes through the same absolute path — module identity stays
|
|
265
|
+
* intact.
|
|
266
|
+
*/
|
|
267
|
+
async function collectSsrLazyRequireAliases(distDir) {
|
|
268
|
+
const ssrDir = path.join(distDir, "server", "chunks", "ssr");
|
|
269
|
+
const projectRoot = path.dirname(distDir);
|
|
270
|
+
const projectRequire = createRequire(path.join(projectRoot, "package.json"));
|
|
271
|
+
const builtins = new Set([
|
|
272
|
+
...builtinModules,
|
|
273
|
+
...builtinModules.map((name) => `node:${name}`),
|
|
274
|
+
]);
|
|
275
|
+
let entries;
|
|
276
|
+
try {
|
|
277
|
+
entries = await fs.readdir(ssrDir);
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
return {};
|
|
281
|
+
}
|
|
282
|
+
// Same regex as the [externals] patcher: `.x("X", () => require("X"))`
|
|
283
|
+
// where the two strings are identical.
|
|
284
|
+
const lazyRequirePattern = /\w+\.x\(\s*(["'])([^"']+)\1\s*,\s*\(\)\s*=>\s*require\(\s*(["'])\2\3\s*\)\s*\)/g;
|
|
285
|
+
const seen = new Set();
|
|
286
|
+
const aliasMap = {};
|
|
287
|
+
for (const entry of entries) {
|
|
288
|
+
if (!entry.endsWith(".js"))
|
|
289
|
+
continue;
|
|
290
|
+
const filePath = path.join(ssrDir, entry);
|
|
291
|
+
let code;
|
|
292
|
+
try {
|
|
293
|
+
code = await fs.readFile(filePath, "utf-8");
|
|
294
|
+
}
|
|
295
|
+
catch {
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
if (!code.includes(".x(") || !code.includes("require("))
|
|
299
|
+
continue;
|
|
300
|
+
let m;
|
|
301
|
+
while ((m = lazyRequirePattern.exec(code)) !== null) {
|
|
302
|
+
const specifier = m[2];
|
|
303
|
+
if (!specifier || seen.has(specifier))
|
|
304
|
+
continue;
|
|
305
|
+
seen.add(specifier);
|
|
306
|
+
if (specifier.startsWith(".") ||
|
|
307
|
+
specifier.startsWith("/") ||
|
|
308
|
+
builtins.has(specifier) ||
|
|
309
|
+
specifier === "next" ||
|
|
310
|
+
specifier.startsWith("next/") ||
|
|
311
|
+
specifier.startsWith("@next/")) {
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
let resolved = null;
|
|
315
|
+
try {
|
|
316
|
+
resolved = projectRequire.resolve(specifier);
|
|
317
|
+
}
|
|
318
|
+
catch {
|
|
319
|
+
// no-op — specifier will fail esbuild too, nothing we can fix
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
if (!resolved || resolved.endsWith(".node"))
|
|
323
|
+
continue;
|
|
324
|
+
aliasMap[specifier] = resolved;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return aliasMap;
|
|
328
|
+
}
|
|
329
|
+
async function patchAppPageManifestSingletons(distDir) {
|
|
330
|
+
const ssrDir = path.join(distDir, "server", "chunks", "ssr");
|
|
331
|
+
let entries = [];
|
|
332
|
+
try {
|
|
333
|
+
entries = await fs.readdir(ssrDir);
|
|
334
|
+
}
|
|
335
|
+
catch {
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
const manifestProxyPattern = /case"moduleLoading":case"entryCSSFiles":case"entryJSFiles":\{if\(!(\w+)\)throw[\s\S]*?let (\w+)=(\w+)\.get\(\1\.route\);if\(!\2\)throw[\s\S]*?return \2\[(\w+)\]\}/g;
|
|
339
|
+
for (const entry of entries) {
|
|
340
|
+
if (!entry.endsWith(".js"))
|
|
341
|
+
continue;
|
|
342
|
+
const filePath = path.join(ssrDir, entry);
|
|
343
|
+
let code;
|
|
344
|
+
try {
|
|
345
|
+
code = await fs.readFile(filePath, "utf-8");
|
|
346
|
+
}
|
|
347
|
+
catch {
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
if (!code.includes('entryCSSFiles') || !code.includes('without a work store')) {
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
const patched = code.replace(manifestProxyPattern, (_match, workStoreVar, manifestVar, manifestsVar, propVar) => `case"moduleLoading":case"entryCSSFiles":case"entryJSFiles":{if(!${workStoreVar}){for(let a of ${manifestsVar}.values()){let b=a[${propVar}];if(void 0!==b)return b}return}let ${manifestVar}=${manifestsVar}.get(${workStoreVar}.route);if(!${manifestVar}){for(let a of ${manifestsVar}.values()){let b=a[${propVar}];if(void 0!==b)return b}return}return ${manifestVar}[${propVar}]}`);
|
|
354
|
+
if (patched !== code) {
|
|
355
|
+
await fs.writeFile(filePath, patched);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
function patchBundledManifestSingleton(workerCode) {
|
|
360
|
+
const bundledManifestProxyPattern = /case "moduleLoading":\s*case "entryCSSFiles":\s*case "entryJSFiles": \{\s*if \(!(\w+)\) throw[\s\S]*?let (\w+) = (\w+)\.get\(\1\.route\);\s*if \(!\2\) throw[\s\S]*?return \2\[(\w+)\];\s*\}/g;
|
|
361
|
+
const bundledManifestLookupPattern = /if \((\w+)\) \{\s*let (\w+) = (\w+)\.get\(\1\.route\);\s*if \(null == \2 \? void 0 : \2\[(\w+)\]\[(\w+)\]\) return \2\[\4\]\[\5\];\s*\} else for \(let (\w+) of \3\.values\(\)\) \{\s*let (\w+) = \6\[\4\]\[\5\];\s*if \(void 0 !== \7\) return \7;\s*\}/g;
|
|
362
|
+
workerCode = workerCode.replace(bundledManifestProxyPattern, (_match, workStoreVar, manifestVar, manifestsVar, propVar) => `case "moduleLoading":
|
|
363
|
+
case "entryCSSFiles":
|
|
364
|
+
case "entryJSFiles": {
|
|
365
|
+
if (!${workStoreVar}) {
|
|
366
|
+
for (const manifest of ${manifestsVar}.values()) {
|
|
367
|
+
const entry = manifest[${propVar}];
|
|
368
|
+
if (entry !== undefined) {
|
|
369
|
+
return entry;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return undefined;
|
|
373
|
+
}
|
|
374
|
+
let ${manifestVar} = ${manifestsVar}.get(${workStoreVar}.route);
|
|
375
|
+
if (!${manifestVar}) {
|
|
376
|
+
for (const manifest of ${manifestsVar}.values()) {
|
|
377
|
+
const entry = manifest[${propVar}];
|
|
378
|
+
if (entry !== undefined) {
|
|
379
|
+
return entry;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return undefined;
|
|
383
|
+
}
|
|
384
|
+
return ${manifestVar}[${propVar}];
|
|
385
|
+
}`);
|
|
386
|
+
workerCode = workerCode.replace(bundledManifestLookupPattern, (_match, workStoreVar, manifestVar, manifestsVar, propVar, idVar, iterManifestVar, iterEntryVar) => `if (${workStoreVar}) {
|
|
387
|
+
let ${manifestVar} = ${manifestsVar}.get(${workStoreVar}.route);
|
|
388
|
+
let ${iterEntryVar} = null == ${manifestVar} ? void 0 : ${manifestVar}[${propVar}][${idVar}];
|
|
389
|
+
if (void 0 === ${iterEntryVar} && ${propVar} === "edgeSSRModuleMapping") ${iterEntryVar} = null == ${manifestVar} ? void 0 : ${manifestVar}.ssrModuleMapping[${idVar}];
|
|
390
|
+
if (void 0 === ${iterEntryVar} && ${propVar} === "edgeRscModuleMapping") ${iterEntryVar} = null == ${manifestVar} ? void 0 : ${manifestVar}.rscModuleMapping[${idVar}];
|
|
391
|
+
if (typeof process !== "undefined" && process.env.CREEK_DEBUG_MANIFESTS === "1" && (${idVar} === "99807" || ${idVar} === 99807 || String(${workStoreVar}.route || "").includes("basic-edge"))) {
|
|
392
|
+
console.error("[creek:bundled-manifest:route]", JSON.stringify({
|
|
393
|
+
route: ${workStoreVar}.route,
|
|
394
|
+
prop: ${propVar},
|
|
395
|
+
id: ${idVar},
|
|
396
|
+
routeHit: !!${manifestVar},
|
|
397
|
+
entryId: ${iterEntryVar} && typeof ${iterEntryVar} === "object" ? ${iterEntryVar}.id ?? (typeof ${iterEntryVar}["*"] === "object" ? ${iterEntryVar}["*"].id : undefined) : undefined,
|
|
398
|
+
}));
|
|
399
|
+
}
|
|
400
|
+
if (void 0 !== ${iterEntryVar}) return ${iterEntryVar};
|
|
401
|
+
}
|
|
402
|
+
let __creekNodeFallback;
|
|
403
|
+
for (let ${iterManifestVar} of ${manifestsVar}.values()) {
|
|
404
|
+
let ${iterEntryVar} = ${iterManifestVar}[${propVar}][${idVar}];
|
|
405
|
+
if (typeof process !== "undefined" && process.env.CREEK_DEBUG_MANIFESTS === "1" && (${idVar} === "99807" || ${idVar} === 99807) && void 0 !== ${iterEntryVar}) {
|
|
406
|
+
console.error("[creek:bundled-manifest:scan-hit]", JSON.stringify({
|
|
407
|
+
prop: ${propVar},
|
|
408
|
+
id: ${idVar},
|
|
409
|
+
entryId: ${iterEntryVar} && typeof ${iterEntryVar} === "object" ? ${iterEntryVar}.id ?? (typeof ${iterEntryVar}["*"] === "object" ? ${iterEntryVar}["*"].id : undefined) : undefined,
|
|
410
|
+
}));
|
|
411
|
+
}
|
|
412
|
+
if (void 0 !== ${iterEntryVar}) return ${iterEntryVar};
|
|
413
|
+
if (void 0 === __creekNodeFallback && ${propVar} === "edgeSSRModuleMapping") __creekNodeFallback = ${iterManifestVar}.ssrModuleMapping[${idVar}];
|
|
414
|
+
if (void 0 === __creekNodeFallback && ${propVar} === "edgeRscModuleMapping") __creekNodeFallback = ${iterManifestVar}.rscModuleMapping[${idVar}];
|
|
415
|
+
}
|
|
416
|
+
if (typeof process !== "undefined" && process.env.CREEK_DEBUG_MANIFESTS === "1" && (${idVar} === "99807" || ${idVar} === 99807) && void 0 !== __creekNodeFallback) {
|
|
417
|
+
console.error("[creek:bundled-manifest:node-fallback]", JSON.stringify({
|
|
418
|
+
prop: ${propVar},
|
|
419
|
+
id: ${idVar},
|
|
420
|
+
entryId: __creekNodeFallback && typeof __creekNodeFallback === "object" ? __creekNodeFallback.id ?? (typeof __creekNodeFallback["*"] === "object" ? __creekNodeFallback["*"].id : undefined) : undefined,
|
|
421
|
+
}));
|
|
422
|
+
}
|
|
423
|
+
if (void 0 !== __creekNodeFallback) return __creekNodeFallback;`);
|
|
424
|
+
// Cloudflare Workers executes the bundled app through the edge runtime path,
|
|
425
|
+
// but many app pages only populate the node/RSC module maps. Keep true edge
|
|
426
|
+
// routes on the edge maps when they exist, otherwise fall back to the node
|
|
427
|
+
// maps so React Server Consumer Manifest lookups can still resolve.
|
|
428
|
+
workerCode = workerCode.replace(/moduleMap: j2, serverModuleMap:/g, "moduleMap: Object.keys(i2 || {}).length ? i2 : j2, serverModuleMap:");
|
|
429
|
+
return workerCode;
|
|
430
|
+
}
|
|
431
|
+
function stripAsyncLocalStorageSnapshotTernaries(workerCode) {
|
|
432
|
+
const snapshotCall = ".snapshot()";
|
|
433
|
+
let out = "";
|
|
434
|
+
let last = 0;
|
|
435
|
+
let searchFrom = 0;
|
|
436
|
+
const isIdent = (ch) => /[A-Za-z0-9_$]/.test(ch);
|
|
437
|
+
const skipWsBack = (i) => {
|
|
438
|
+
while (i > 0 && /\s/.test(workerCode[i - 1]))
|
|
439
|
+
i--;
|
|
440
|
+
return i;
|
|
441
|
+
};
|
|
442
|
+
const skipWsForward = (i) => {
|
|
443
|
+
while (i < workerCode.length && /\s/.test(workerCode[i]))
|
|
444
|
+
i++;
|
|
445
|
+
return i;
|
|
446
|
+
};
|
|
447
|
+
while (true) {
|
|
448
|
+
const snapshotIndex = workerCode.indexOf(snapshotCall, searchFrom);
|
|
449
|
+
if (snapshotIndex === -1)
|
|
450
|
+
break;
|
|
451
|
+
let rightNameStart = snapshotIndex;
|
|
452
|
+
while (rightNameStart > 0 && isIdent(workerCode[rightNameStart - 1])) {
|
|
453
|
+
rightNameStart--;
|
|
454
|
+
}
|
|
455
|
+
const name = workerCode.slice(rightNameStart, snapshotIndex);
|
|
456
|
+
if (!name) {
|
|
457
|
+
searchFrom = snapshotIndex + snapshotCall.length;
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
let qIndex = skipWsBack(rightNameStart);
|
|
461
|
+
if (workerCode[qIndex - 1] !== "?") {
|
|
462
|
+
searchFrom = snapshotIndex + snapshotCall.length;
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
qIndex--;
|
|
466
|
+
let leftNameEnd = skipWsBack(qIndex);
|
|
467
|
+
let leftNameStart = leftNameEnd;
|
|
468
|
+
while (leftNameStart > 0 && isIdent(workerCode[leftNameStart - 1])) {
|
|
469
|
+
leftNameStart--;
|
|
470
|
+
}
|
|
471
|
+
if (workerCode.slice(leftNameStart, leftNameEnd) !== name) {
|
|
472
|
+
searchFrom = snapshotIndex + snapshotCall.length;
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
let colonIndex = skipWsForward(snapshotIndex + snapshotCall.length);
|
|
476
|
+
if (workerCode[colonIndex] !== ":") {
|
|
477
|
+
searchFrom = snapshotIndex + snapshotCall.length;
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
480
|
+
colonIndex = skipWsForward(colonIndex + 1);
|
|
481
|
+
out += workerCode.slice(last, leftNameStart);
|
|
482
|
+
last = colonIndex;
|
|
483
|
+
searchFrom = colonIndex;
|
|
484
|
+
}
|
|
485
|
+
return out ? out + workerCode.slice(last) : workerCode;
|
|
486
|
+
}
|
|
487
|
+
function patchAsyncLocalStorageBindSnapshot(workerCode) {
|
|
488
|
+
// Next's bindSnapshot() delegates to AsyncLocalStorage.bind() when the
|
|
489
|
+
// runtime provides a native ALS. In workerd that captures request-bound
|
|
490
|
+
// context, and App Page after() callbacks can then stall after response
|
|
491
|
+
// close before the user callback starts. Keep the fake-ALS behavior: return
|
|
492
|
+
// the callback directly.
|
|
493
|
+
workerCode = workerCode.replace(/function\s+bindSnapshot\((\w+)\)\s*\{\s*if\s*\(\s*\w+\s*\)\s*\{\s*return\s+\w+\.bind\(\1\);\s*\}\s*return\s+\w+\.bind\(\1\);\s*\}/g, (_match, fnArg) => `function bindSnapshot(${fnArg}) { return ${fnArg}; }`);
|
|
494
|
+
workerCode = workerCode.replace(/function\s+(\w+)\((\w+)\)\s*\{\s*return\s+\w+\s*\?\s*\w+\.bind\(\2\)\s*:\s*\w+\.bind\(\2\);\s*\}/g, (match, fnName, fnArg, offset, fullCode) => {
|
|
495
|
+
const followingCode = fullCode.slice(offset, offset + 800);
|
|
496
|
+
if (!new RegExp(String.raw `["']bindSnapshot["']\s*,\s*0\s*,\s*${fnName}\b`).test(followingCode)) {
|
|
497
|
+
return match;
|
|
498
|
+
}
|
|
499
|
+
return `function ${fnName}(${fnArg}) { return ${fnArg}; }`;
|
|
500
|
+
});
|
|
501
|
+
workerCode = workerCode.replace(/(server\/app-render\/async-local-storage\.js"[\s\S]{0,1800}?function\s+)(\w+)\((\w+)\)\s*\{\s*return\s+\w+\s*\?\s*\w+\.bind\(\3\)\s*:\s*\w+\.bind\(\3\);\s*\}/g, (_match, prefix, fnName, fnArg) => `${prefix}${fnName}(${fnArg}) { return ${fnArg}; }`);
|
|
502
|
+
return workerCode;
|
|
503
|
+
}
|
|
504
|
+
function patchAfterContextRunCallbacksOnClose(workerCode) {
|
|
505
|
+
// Next's AfterContext normally waits for close by resolving a Promise and
|
|
506
|
+
// then continuing to runCallbacks(). In workerd that continuation can stall
|
|
507
|
+
// for App Page renders even though the close listener fired. Run callbacks
|
|
508
|
+
// directly from the close listener and propagate completion/rejection to
|
|
509
|
+
// waitUntil.
|
|
510
|
+
const marker = "./dist/esm/server/after/after-context.js";
|
|
511
|
+
const pattern = /async\s+runCallbacksOnClose\(\)\s*\{\s*return\s+await\s+new\s+Promise\(\((\w+)\)\s*=>\s*this\.onClose\(\1\)\),\s*this\.runCallbacks\(\);\s*\}/g;
|
|
512
|
+
const replacement = "async runCallbacksOnClose() { return await new Promise((resolve, reject) => this.onClose(() => { Promise.resolve(this.runCallbacks()).then(resolve, reject); })); }";
|
|
513
|
+
let patched = "";
|
|
514
|
+
let last = 0;
|
|
515
|
+
let searchFrom = 0;
|
|
516
|
+
while (true) {
|
|
517
|
+
const markerIndex = workerCode.indexOf(marker, searchFrom);
|
|
518
|
+
if (markerIndex === -1)
|
|
519
|
+
break;
|
|
520
|
+
const windowEnd = Math.min(workerCode.length, markerIndex + 6000);
|
|
521
|
+
const chunk = workerCode.slice(markerIndex, windowEnd);
|
|
522
|
+
const nextChunk = chunk.replace(pattern, replacement);
|
|
523
|
+
if (nextChunk !== chunk) {
|
|
524
|
+
patched += workerCode.slice(last, markerIndex) + nextChunk;
|
|
525
|
+
last = windowEnd;
|
|
526
|
+
}
|
|
527
|
+
searchFrom = windowEnd;
|
|
528
|
+
}
|
|
529
|
+
return patched ? patched + workerCode.slice(last) : workerCode;
|
|
530
|
+
}
|
|
531
|
+
export function patchUseCachePrerenderDanglingPromiseBailout(workerCode) {
|
|
532
|
+
const findHangingPromiseCall = (followingCode, workUnitStoreVar) => {
|
|
533
|
+
const namespaceCallPattern = new RegExp(String.raw `\(0,\s*(\w+)\.makeHangingPromise\)\(\s*${workUnitStoreVar}\.renderSignal\s*,\s*(\w+)\.route`);
|
|
534
|
+
const namespaceCallMatch = namespaceCallPattern.exec(followingCode);
|
|
535
|
+
if (namespaceCallMatch) {
|
|
536
|
+
return {
|
|
537
|
+
callee: `(0, ${namespaceCallMatch[1]}.makeHangingPromise)`,
|
|
538
|
+
workStoreVar: namespaceCallMatch[2],
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
const directCallPattern = new RegExp(String.raw `\b(makeHangingPromise)\(\s*${workUnitStoreVar}\.renderSignal\s*,\s*(\w+)\.route`);
|
|
542
|
+
const directCallMatch = directCallPattern.exec(followingCode);
|
|
543
|
+
if (directCallMatch) {
|
|
544
|
+
return {
|
|
545
|
+
callee: directCallMatch[1],
|
|
546
|
+
workStoreVar: directCallMatch[2],
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
return undefined;
|
|
550
|
+
};
|
|
551
|
+
const danglingBailout = (serializedKeyVar, workUnitStoreVar, hangingCall) => `if (("prerender" === ${workUnitStoreVar}.type || "prerender-runtime" === ${workUnitStoreVar}.type) && "string" == typeof ${serializedKeyVar}) { let __creekDanglingThenableStart = ${serializedKeyVar}.lastIndexOf("$@"); if (__creekDanglingThenableStart !== -1 && ${serializedKeyVar}.indexOf(":", __creekDanglingThenableStart) === -1) return ${hangingCall.callee}(${workUnitStoreVar}.renderSignal, ${hangingCall.workStoreVar}.route, 'dynamic "use cache"'); }`;
|
|
552
|
+
const patchSerializedKeyMatch = (match, declaration, serializedKeyVar, condition, encodedKeyVar, trueExprVar, encodeFnVar, encodeArgVar, rootParamsDecl, rootParamsVar, workUnitStoreVar, offset, fullCode) => {
|
|
553
|
+
if (match.includes("__creekDanglingThenableStart") ||
|
|
554
|
+
trueExprVar !== encodedKeyVar ||
|
|
555
|
+
encodeArgVar !== encodedKeyVar) {
|
|
556
|
+
return match;
|
|
557
|
+
}
|
|
558
|
+
const followingCode = fullCode.slice(offset, offset + 8000);
|
|
559
|
+
const hangingCall = findHangingPromiseCall(followingCode, workUnitStoreVar);
|
|
560
|
+
if (!hangingCall)
|
|
561
|
+
return match;
|
|
562
|
+
return `${declaration}${serializedKeyVar} = ${condition} ? ${trueExprVar} : await ${encodeFnVar}(${encodeArgVar}); ${danglingBailout(serializedKeyVar, workUnitStoreVar, hangingCall)} ${rootParamsDecl} ${rootParamsVar} = ${workUnitStoreVar}.rootParams`;
|
|
563
|
+
};
|
|
564
|
+
const minifiedSerializedKeyPattern = /((?:const|let|var)\s+)(\w+)\s*=\s*((?:(?:"string"|'string')\s*={2,3}\s*typeof\s+(\w+))|(?:typeof\s+(\w+)\s*={2,3}\s*(?:"string"|'string')))\s*\?\s*(\w+)\s*:\s*await\s+(\w+)\(\s*(\w+)\s*\)\s*,\s*(\w+)\s*=\s*(\w+)\.rootParams/g;
|
|
565
|
+
let patchedCode = workerCode.replace(minifiedSerializedKeyPattern, (match, declaration, serializedKeyVar, condition, encodedKeyVarFromStringFirst, encodedKeyVarFromTypeofFirst, trueExprVar, encodeFnVar, encodeArgVar, rootParamsVar, workUnitStoreVar, offset, fullCode) => {
|
|
566
|
+
const encodedKeyVar = encodedKeyVarFromStringFirst ?? encodedKeyVarFromTypeofFirst;
|
|
567
|
+
if (!encodedKeyVar)
|
|
568
|
+
return match;
|
|
569
|
+
return patchSerializedKeyMatch(match, declaration, serializedKeyVar, condition, encodedKeyVar, trueExprVar, encodeFnVar, encodeArgVar, declaration.trim(), rootParamsVar, workUnitStoreVar, offset, fullCode);
|
|
570
|
+
});
|
|
571
|
+
const readableSerializedKeyPattern = /((?:const|let|var)\s+)(\w+)\s*=\s*((?:(?:"string"|'string')\s*={2,3}\s*typeof\s+(\w+))|(?:typeof\s+(\w+)\s*={2,3}\s*(?:"string"|'string')))\s*\?\s*(?:(?:\/\/[^\r\n]*(?:\r?\n))|\/\*[\s\S]*?\*\/|\s)*?(\w+)\s*:\s*await\s+(\w+)\(\s*(\w+)\s*\)\s*;\s*(?:(?:\/\/[^\r\n]*(?:\r?\n))|\/\*[\s\S]*?\*\/|\s)*?(const|let|var)\s+(\w+)\s*=\s*(\w+)\.rootParams/g;
|
|
572
|
+
patchedCode = patchedCode.replace(readableSerializedKeyPattern, (match, declaration, serializedKeyVar, condition, encodedKeyVarFromStringFirst, encodedKeyVarFromTypeofFirst, trueExprVar, encodeFnVar, encodeArgVar, rootParamsDecl, rootParamsVar, workUnitStoreVar, offset, fullCode) => {
|
|
573
|
+
const encodedKeyVar = encodedKeyVarFromStringFirst ?? encodedKeyVarFromTypeofFirst;
|
|
574
|
+
if (!encodedKeyVar)
|
|
575
|
+
return match;
|
|
576
|
+
return patchSerializedKeyMatch(match, declaration, serializedKeyVar, condition, encodedKeyVar, trueExprVar, encodeFnVar, encodeArgVar, rootParamsDecl, rootParamsVar, workUnitStoreVar, offset, fullCode);
|
|
577
|
+
});
|
|
578
|
+
return patchedCode;
|
|
579
|
+
}
|
|
580
|
+
export function patchNullFallbackPartialShellBlocking(workerCode) {
|
|
581
|
+
const minifiedPartialFallbackUpgradePattern = /true\s*!==\s*(\w+)\.experimental\.partialFallbacks\s*\|\|\s*\(null\s*==\s*(\w+)\s*\?\s*void\s*0\s*:\s*\2\.fallback\)\s*!==\s*null\s*\|\|\s*(\w+)\s*\|\|\s*(\w+)\s*\|\|\s*!\((\w+)\.length\s*>\s*0\)\s*\|\|\s*\((\w+)\s*=\s*(\w+)\.FallbackMode\.PRERENDER\)/g;
|
|
582
|
+
workerCode = workerCode.replace(minifiedPartialFallbackUpgradePattern, (_match, nextConfigVar, prerenderInfoVar, omittedFallbackParamVar, unresolvedRootParamsVar, remainingParamsVar, fallbackModeVar, fallbackEnumVar) => `true !== ${nextConfigVar}.experimental.partialFallbacks || (null == ${prerenderInfoVar} ? void 0 : ${prerenderInfoVar}.fallback) !== null || ${omittedFallbackParamVar} || ${unresolvedRootParamsVar} || !(${remainingParamsVar}.length > 0) || (${fallbackModeVar} = ${fallbackEnumVar}.FallbackMode.BLOCKING_STATIC_RENDER)`);
|
|
583
|
+
const minifiedNullFallbackUpgradePattern = /\(null\s*==\s*(\w+)\s*\?\s*void\s*0\s*:\s*\1\.fallback\)\s*!==\s*null\s*\|\|\s*(\w+)\s*\|\|\s*(\w+)\s*\|\|\s*!\((\w+)\.length\s*>\s*0\)\s*\|\|\s*\((\w+)\s*=\s*(\w+)\.FallbackMode\.PRERENDER\)/g;
|
|
584
|
+
workerCode = workerCode.replace(minifiedNullFallbackUpgradePattern, (_match, prerenderInfoVar, omittedFallbackParamVar, unresolvedRootParamsVar, remainingParamsVar, fallbackModeVar, fallbackEnumVar) => `(null == ${prerenderInfoVar} ? void 0 : ${prerenderInfoVar}.fallback) !== null || ${omittedFallbackParamVar} || ${unresolvedRootParamsVar} || !(${remainingParamsVar}.length > 0) || (${fallbackModeVar} = ${fallbackEnumVar}.FallbackMode.BLOCKING_STATIC_RENDER)`);
|
|
585
|
+
const readableNullFallbackUpgradePattern = /(if\s*\(\s*\(\s*(?:(?:null\s*==\s*\w+)|(?:\w+\s*==\s*null))\s*\?\s*void\s*0\s*:\s*\w+\.fallback\s*\)\s*={2,3}\s*null\s*&&\s*!\w+\s*&&\s*!\w+\s*&&\s*\w+\.length\s*>\s*0\s*\)\s*\{[\s\S]{0,1600}?)(\w+)\s*=\s*([\w$]+)\.FallbackMode\.PRERENDER\s*;/g;
|
|
586
|
+
return workerCode.replace(readableNullFallbackUpgradePattern, (_match, prefix, fallbackModeVar, fallbackEnumVar) => `${prefix}${fallbackModeVar} = ${fallbackEnumVar}.FallbackMode.BLOCKING_STATIC_RENDER;`);
|
|
587
|
+
}
|
|
588
|
+
export function patchAppPageRevalidationPostponedState(workerCode) {
|
|
589
|
+
const readableNeedle = ` let postponed =
|
|
590
|
+
!isOnDemandRevalidate && !isRevalidating && minimalPostponed
|
|
591
|
+
? minimalPostponed
|
|
592
|
+
: undefined
|
|
593
|
+
|
|
594
|
+
if (
|
|
595
|
+
// If this is a dynamic RSC request or a server action request, we should
|
|
596
|
+
`;
|
|
597
|
+
const readableReplacement = ` let postponed =
|
|
598
|
+
!isOnDemandRevalidate && !isRevalidating && minimalPostponed
|
|
599
|
+
? minimalPostponed
|
|
600
|
+
: undefined
|
|
601
|
+
|
|
602
|
+
if (
|
|
603
|
+
typeof postponed === 'undefined' &&
|
|
604
|
+
isRevalidating &&
|
|
605
|
+
previousIncrementalCacheEntry?.value?.kind === CachedRouteKind.APP_PAGE &&
|
|
606
|
+
typeof previousIncrementalCacheEntry.value.postponed === 'string'
|
|
607
|
+
) {
|
|
608
|
+
postponed = previousIncrementalCacheEntry.value.postponed
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
if (
|
|
612
|
+
// If this is a dynamic RSC request or a server action request, we should
|
|
613
|
+
`;
|
|
614
|
+
workerCode = workerCode.replace(readableNeedle, readableReplacement);
|
|
615
|
+
const minifiedPattern = /(__name\(async\s*\(\{\s*hasResolved:\s*(\w+),\s*previousCacheEntry:\s*(\w+),\s*isRevalidating:\s*(\w+),\s*span:\s*(\w+),\s*forceStaticRender:\s*(\w+)\s*=\s*false\s*\}\)\s*=>\s*\{[\s\S]{0,6000}?)(let\s+(\w+)\s*=\s*(\w+)\s*\|\|\s*\4\s*\|\|\s*!(\w+)\s*\?\s*void 0\s*:\s*(\w+)\s*;)/g;
|
|
616
|
+
return workerCode.replace(minifiedPattern, (match, prefix, _hasResolvedVar, previousCacheEntryVar, isRevalidatingVar, _spanVar, _forceStaticRenderVar, declaration, postponedVar, _onDemandRevalidateVar, minimalPostponedVar, fallbackPostponedVar) => {
|
|
617
|
+
if (match.includes("__creekRevalidationPostponedState"))
|
|
618
|
+
return match;
|
|
619
|
+
if (minimalPostponedVar !== fallbackPostponedVar)
|
|
620
|
+
return match;
|
|
621
|
+
return `${prefix}${declaration}/* __creekRevalidationPostponedState */if(void 0===${postponedVar}&&${isRevalidatingVar}&&${previousCacheEntryVar}&&${previousCacheEntryVar}.value&&${previousCacheEntryVar}.value.kind==="APP_PAGE"&&"string"==typeof ${previousCacheEntryVar}.value.postponed)${postponedVar}=${previousCacheEntryVar}.value.postponed;`;
|
|
622
|
+
});
|
|
623
|
+
}
|
|
139
624
|
export async function bundleForWorkers(opts) {
|
|
140
625
|
// Patch Turbopack runtime BEFORE wrangler bundles.
|
|
141
626
|
// Turbopack's R.c() dynamically loads chunks from the filesystem.
|
|
142
627
|
// CF Workers has no filesystem, so we replace R.c() with a switch
|
|
143
628
|
// statement that maps chunk paths to static require() calls.
|
|
144
629
|
await patchTurbopackRuntime(opts.distDir);
|
|
630
|
+
await patchNodeExternalRequireConditions(opts.distDir);
|
|
631
|
+
await patchAppPageManifestSingletons(opts.distDir);
|
|
632
|
+
const ssrLazyRequireAliases = await collectSsrLazyRequireAliases(opts.distDir);
|
|
145
633
|
// Write the generated worker entry
|
|
146
634
|
const entryPath = path.join(opts.outputDir, "__entry.mjs");
|
|
147
635
|
await fs.writeFile(entryPath, opts.workerSource);
|
|
148
636
|
if (process.env.CREEK_DEBUG) {
|
|
149
637
|
await fs.writeFile(path.join(opts.outputDir, "__entry_debug.mjs"), opts.workerSource);
|
|
150
638
|
}
|
|
639
|
+
// Copy WASM files alongside the bundle BEFORE wrangler runs. wrangler's
|
|
640
|
+
// bundler needs the files resolvable from the entry `import __wasm_0 from
|
|
641
|
+
// "./wasm_<hex>.wasm"` to apply the CompiledWasm rule — copying later
|
|
642
|
+
// (after the bundle step) is too late.
|
|
643
|
+
for (const [name, absPath] of opts.wasmFiles) {
|
|
644
|
+
const destName = name.endsWith(".wasm") ? name : name + ".wasm";
|
|
645
|
+
const destPath = path.join(opts.outputDir, destName);
|
|
646
|
+
await fs.copyFile(absPath, destPath);
|
|
647
|
+
}
|
|
151
648
|
// Resolve adapter paths
|
|
152
649
|
const adapterDir = path.dirname(path.dirname(new URL(import.meta.url).pathname));
|
|
153
650
|
// Generate wrangler config for the bundle step
|
|
@@ -164,34 +661,67 @@ export async function bundleForWorkers(opts) {
|
|
|
164
661
|
},
|
|
165
662
|
// Mark optional/unavailable deps as external to prevent build errors.
|
|
166
663
|
// These are caught at runtime and handled gracefully.
|
|
664
|
+
//
|
|
665
|
+
// `ssrLazyRequireAliases` is spread FIRST so static entries below take
|
|
666
|
+
// precedence if they collide — our shims (fs, vm, critters, etc.)
|
|
667
|
+
// always win over a happenstance SSR lazy-require of the same name.
|
|
167
668
|
alias: {
|
|
669
|
+
...ssrLazyRequireAliases,
|
|
168
670
|
"@opentelemetry/api": path.join(adapterDir, "src", "shims", "opentelemetry.js"),
|
|
169
671
|
// fs shim — intercept both bare and node: prefixed imports.
|
|
170
672
|
// Turbopack runtime uses require("fs") which wrangler must redirect
|
|
171
673
|
// to our shim that reads from embedded __MANIFESTS.
|
|
172
674
|
"fs": path.join(adapterDir, "src", "shims", "fs.js"),
|
|
173
675
|
"node:fs": path.join(adapterDir, "src", "shims", "fs.js"),
|
|
676
|
+
"fs/promises": path.join(adapterDir, "src", "shims", "fs.js"),
|
|
677
|
+
"node:fs/promises": path.join(adapterDir, "src", "shims", "fs.js"),
|
|
174
678
|
"vm": path.join(adapterDir, "src", "shims", "vm.js"),
|
|
175
679
|
"node:vm": path.join(adapterDir, "src", "shims", "vm.js"),
|
|
176
680
|
// critters is bundled by Next.js for CSS inlining — not needed on Workers.
|
|
177
681
|
"critters": path.join(adapterDir, "src", "shims", "critters.js"),
|
|
178
|
-
// sharp has native .node bindings that workerd can't load.
|
|
682
|
+
// sharp has native .node bindings that workerd can't load. Without this
|
|
683
|
+
// alias, wrangler pulls in ~1MB of sharp's JS wrapper and the module
|
|
684
|
+
// ends up non-callable at runtime — \`@vercel/og\`'s node path then
|
|
685
|
+
// throws \`sharp is not a function\`. Aliasing to a shim whose default
|
|
686
|
+
// is undefined makes \`@vercel/og\` fall back to its resvg.wasm path.
|
|
179
687
|
"sharp": path.join(adapterDir, "src", "shims", "sharp.js"),
|
|
180
|
-
//
|
|
688
|
+
// Some packages intentionally leave unreachable dynamic imports such as
|
|
689
|
+
// `if (Math.random() < 0) import("fail")` in their ESM entry. Turbopack
|
|
690
|
+
// externalizes the package and never resolves that branch, but wrangler
|
|
691
|
+
// follows it once we preload the concrete ESM file. Stub it so the dead
|
|
692
|
+
// branch remains harmless.
|
|
181
693
|
"fail": path.join(adapterDir, "src", "shims", "empty.js"),
|
|
182
|
-
// track-module-loading
|
|
694
|
+
// Replace Next's track-module-loading.{instance,external} with a
|
|
695
|
+
// per-request AsyncLocalStorage version. The original keeps a
|
|
696
|
+
// module-level CacheSignal whose internal setImmediate closure
|
|
697
|
+
// leaks IoContext across requests on workerd — second-and-later
|
|
698
|
+
// requests throw "Cannot perform I/O on behalf of a different
|
|
699
|
+
// request" when CacheSignal.pendingTimeoutCleanup fires
|
|
700
|
+
// clearImmediate on an Immediate from the first request. Repros
|
|
701
|
+
// on any route that does dynamic imports during render (notably
|
|
702
|
+
// \`new ImageResponse(...)\` — every \`@vercel/og\` call triggers
|
|
703
|
+
// trackPendingImport). We alias \`.external\` as well because
|
|
704
|
+
// call sites import from that module (the internal relative
|
|
705
|
+
// \`./track-module-loading.instance\` import never passes through
|
|
706
|
+
// esbuild's bare-specifier alias map). See
|
|
707
|
+
// src/shims/track-module-loading.js.
|
|
183
708
|
"next/dist/server/app-render/module-loading/track-module-loading.external": path.join(adapterDir, "src", "shims", "track-module-loading.js"),
|
|
184
709
|
"next/dist/server/app-render/module-loading/track-module-loading.external.js": path.join(adapterDir, "src", "shims", "track-module-loading.js"),
|
|
185
710
|
"next/dist/server/app-render/module-loading/track-module-loading.instance": path.join(adapterDir, "src", "shims", "track-module-loading.js"),
|
|
186
711
|
"next/dist/server/app-render/module-loading/track-module-loading.instance.js": path.join(adapterDir, "src", "shims", "track-module-loading.js"),
|
|
187
|
-
// fast-set-immediate
|
|
712
|
+
// Next's fast-set-immediate module patches node:timers/promises, whose
|
|
713
|
+
// ESM namespace is frozen in Workers. More importantly, workerd can run
|
|
714
|
+
// the next setTimeout(0) stage before a setImmediate scheduled by the
|
|
715
|
+
// previous cache-components stage. The shim keeps native setImmediate
|
|
716
|
+
// except during Next's explicit fast-immediate capture window.
|
|
188
717
|
"next/dist/server/node-environment-extensions/fast-set-immediate.external": path.join(adapterDir, "src", "shims", "fast-set-immediate.js"),
|
|
189
718
|
"next/dist/server/node-environment-extensions/fast-set-immediate.external.js": path.join(adapterDir, "src", "shims", "fast-set-immediate.js"),
|
|
190
|
-
//
|
|
191
|
-
//
|
|
192
|
-
//
|
|
193
|
-
|
|
194
|
-
|
|
719
|
+
// NOTE: load-manifest shim exists in src/shims/ but is handled by the
|
|
720
|
+
// fs shim (manifest loading), so no alias needed.
|
|
721
|
+
// Keep http/node:http on workerd's nodejs_compat implementation. The
|
|
722
|
+
// worker entry extends the built-in IncomingMessage and ServerResponse;
|
|
723
|
+
// aliasing those imports to our minimal shim makes POST body flushing
|
|
724
|
+
// re-buffer chunks into itself and can OOM server-action requests.
|
|
195
725
|
"https": path.join(adapterDir, "src", "shims", "http.js"),
|
|
196
726
|
"node:https": path.join(adapterDir, "src", "shims", "http.js"),
|
|
197
727
|
// net: Socket class needed by Next.js http bridge.
|
|
@@ -213,18 +743,39 @@ export async function bundleForWorkers(opts) {
|
|
|
213
743
|
// Bundle with wrangler --dry-run
|
|
214
744
|
// Wrangler internally uses esbuild but with Turbopack-aware resolution
|
|
215
745
|
// and proper CJS/ESM interop for CF Workers.
|
|
216
|
-
// Ensure @next/routing is resolvable from the project directory.
|
|
217
|
-
//
|
|
218
|
-
//
|
|
746
|
+
// Ensure @next/routing is resolvable from the project directory. It's a
|
|
747
|
+
// dependency of the adapter, not the user's project, so wrangler can't
|
|
748
|
+
// find it when run from the project's cwd. Resolve via \`createRequire\`
|
|
749
|
+
// rather than guessing \`adapterDir/node_modules/@next/routing\` — pnpm's
|
|
750
|
+
// virtual-store layout means that path often doesn't exist (the real
|
|
751
|
+
// install lives under \`node_modules/.pnpm/@next+routing@X/\`), and the
|
|
752
|
+
// guess only works for the link-protocol install of the adapter.
|
|
219
753
|
const projectNodeModules = path.join(path.dirname(opts.distDir), "node_modules");
|
|
220
754
|
const routingDest = path.join(projectNodeModules, "@next", "routing");
|
|
221
|
-
const
|
|
755
|
+
const adapterRequire = createRequire(path.join(adapterDir, "package.json"));
|
|
756
|
+
let routingSrc;
|
|
757
|
+
try {
|
|
758
|
+
routingSrc = path.dirname(adapterRequire.resolve("@next/routing/package.json"));
|
|
759
|
+
}
|
|
760
|
+
catch {
|
|
761
|
+
// Fallback to the legacy guess so a classic link-install still works.
|
|
762
|
+
routingSrc = path.join(adapterDir, "node_modules", "@next", "routing");
|
|
763
|
+
}
|
|
222
764
|
try {
|
|
223
765
|
await fs.access(routingDest);
|
|
224
766
|
}
|
|
225
767
|
catch {
|
|
226
768
|
await fs.mkdir(path.join(projectNodeModules, "@next"), { recursive: true });
|
|
227
|
-
|
|
769
|
+
try {
|
|
770
|
+
await fs.symlink(routingSrc, routingDest, "junction");
|
|
771
|
+
}
|
|
772
|
+
catch (err) {
|
|
773
|
+
// Racy repeat runs (same project dir rebuilt back-to-back) can leave a
|
|
774
|
+
// dangling symlink that \`access\` reports as missing while \`symlink\`
|
|
775
|
+
// still refuses to overwrite.
|
|
776
|
+
if (err?.code !== "EEXIST")
|
|
777
|
+
throw err;
|
|
778
|
+
}
|
|
228
779
|
}
|
|
229
780
|
const bundleDir = path.join(opts.outputDir, "__bundle");
|
|
230
781
|
// Resolve wrangler binary from the adapter's own node_modules
|
|
@@ -280,13 +831,216 @@ export async function bundleForWorkers(opts) {
|
|
|
280
831
|
// "Dynamic require of ... is not supported" without those codes.
|
|
281
832
|
// Patch the catch to also handle "is not supported" errors.
|
|
282
833
|
workerCode = workerCode.replace(/err\.code !== "ENOENT" && err\.code !== "MODULE_NOT_FOUND" && err\.code !== "ERR_MODULE_NOT_FOUND"/g, 'err.code !== "ENOENT" && err.code !== "MODULE_NOT_FOUND" && err.code !== "ERR_MODULE_NOT_FOUND" && !err.message?.includes("is not supported")');
|
|
834
|
+
// Some libraries hide builtin requires from the bundler, e.g.
|
|
835
|
+
// @typescript/vfs constructs `require("path")` via String.fromCharCode.
|
|
836
|
+
// Wrangler can't statically rewrite that, so esbuild's __require helper
|
|
837
|
+
// throws at runtime even though the node:path wrapper is already present
|
|
838
|
+
// elsewhere in the bundle. Route those builtin dynamic requires back to
|
|
839
|
+
// wrangler's generated require_path() module when available.
|
|
840
|
+
workerCode = workerCode.replace(/throw Error\('Dynamic require of "' \+ x \+ '" is not supported'\);/, 'if ((x === "path" || x === "node:path") && typeof require_path === "function") return require_path();\n throw Error(\'Dynamic require of "\' + x + \'" is not supported\');');
|
|
841
|
+
workerCode = workerCode.replace(/(throw Error\('Dynamic require of "' \+ x \+ '" is not supported'\);\n\}\);)/, '$1\n__require.resolve = (id) => id === "typescript" ? "node_modules/typescript/lib/typescript.js" : id;');
|
|
842
|
+
// `next/dist/server/node-environment.js` imports
|
|
843
|
+
// `./node-environment-extensions/fast-set-immediate.external` by relative
|
|
844
|
+
// path, so Wrangler's bare-specifier alias cannot intercept it. If the
|
|
845
|
+
// original module installs first, our scoped shim stores Next's patched
|
|
846
|
+
// timer functions as its "native" originals and the workerd ordering fix
|
|
847
|
+
// becomes re-entrant. Route that side-effect import to the aliased shim
|
|
848
|
+
// module that Wrangler emitted for Turbopack's external import.
|
|
849
|
+
workerCode = workerCode.replace(/\brequire_fast_set_immediate_external\(\);/g, 'typeof init_fast_set_immediate === "function" ? init_fast_set_immediate() : require_fast_set_immediate_external();');
|
|
850
|
+
// Next.js's \`getInstrumentationModule\` does
|
|
851
|
+
// \`await __require(path.join(projectDir, distDir, "server",
|
|
852
|
+
// \`\${INSTRUMENTATION_HOOK_FILENAME}.js\`))\`. workerd can't resolve
|
|
853
|
+
// dynamic-require paths — the call rejects, the catch above swallows it,
|
|
854
|
+
// and \`instrumentation.register()\` is never invoked. Worker-entry
|
|
855
|
+
// static-imports the user file onto \`globalThis.__CREEK_INSTRUMENTATION\`
|
|
856
|
+
// when present, so prefer that over \`__require\` here. Falls through
|
|
857
|
+
// to the original call when no user instrumentation is registered so
|
|
858
|
+
// the \`module.exports = {}\` placeholder path still works.
|
|
859
|
+
workerCode = workerCode.replace(/(cachedInstrumentationModule\s*=\s*\(0,\s*_interopdefault\.interopDefault\)\s*\()\s*await __require\(/g, "$1globalThis.__CREEK_INSTRUMENTATION || await __require(");
|
|
860
|
+
workerCode = patchBundledManifestSingleton(workerCode);
|
|
861
|
+
// Next's Pages Router error/404 path may dynamically import the configured
|
|
862
|
+
// cacheHandler via `file:/.solcreek-cache-handler.mjs`. workerd cannot load
|
|
863
|
+
// arbitrary file: modules at runtime, and the worker already ships the
|
|
864
|
+
// target-specific CreekCacheHandler inline. Redirect only that generated
|
|
865
|
+
// cache-handler import to the inline class.
|
|
866
|
+
workerCode = workerCode.replace(/__name\(\((\w+)\)\s*=>\s*import\(\1\)\.then\(\((\w+)\)\s*=>\s*\2\.default\s*\|\|\s*\2\),\s*"([^"]+)"\)/g, (_match, specifierVar, moduleVar, functionName) => `__name((${specifierVar}) => String(${specifierVar}).includes(".solcreek-cache-handler.mjs") ` +
|
|
867
|
+
`? Promise.resolve(globalThis.__CREEK_CACHE_HANDLER) ` +
|
|
868
|
+
`: import(${specifierVar}).then((${moduleVar}) => ${moduleVar}.default || ${moduleVar}), ${JSON.stringify(functionName)})`);
|
|
869
|
+
// depd (via raw-body) uses `eval("(function ("+args+") {...})")` to build a
|
|
870
|
+
// deprecation-wrapping thunk. workerd + CF Workers block code generation
|
|
871
|
+
// from strings ("Code generation from strings disallowed for this context"),
|
|
872
|
+
// so any module that pulls in raw-body — notably the Pages Router API
|
|
873
|
+
// body parser — throws at module load time, and the outer try/catch in
|
|
874
|
+
// parse-body.ts surfaces it to the browser as `400 Invalid body`.
|
|
875
|
+
//
|
|
876
|
+
// Replace the minified eval expression with a direct function literal
|
|
877
|
+
// that preserves the runtime semantics (log + call). We lose the
|
|
878
|
+
// `function.length` preservation the eval form achieved, but nothing
|
|
879
|
+
// in the raw-body → parse-body call chain reads it.
|
|
880
|
+
//
|
|
881
|
+
// Fixes middleware-redirects "should redirect to api route with locale"
|
|
882
|
+
// (and the /fr variant) — both fail because their navigation ends at
|
|
883
|
+
// an API route whose parse-body path can't load raw-body.
|
|
884
|
+
workerCode = workerCode.replace(/var\s+(\w+)\s*=\s*eval\("\(function \(".*?return\s+\1\s*;?\s*\}/s, "return function(){log.call(deprecate,message,site);return fn.apply(this,arguments)}}");
|
|
885
|
+
// Route \`externalImport(id)\` through \`globalThis.__CREEK_EXT_MODS\` so
|
|
886
|
+
// we can serve bundled-but-externalized-by-Turbopack modules from
|
|
887
|
+
// our worker-entry static imports. Turbopack emits chunks like
|
|
888
|
+
// \`[externals]_next_dist_compiled_@vercel_og_index_node_...\` that do
|
|
889
|
+
// \`await e.y("next/dist/compiled/@vercel/og/index.node.js")\`; on
|
|
890
|
+
// workerd that \`await import(id)\` path throws "No such module" and
|
|
891
|
+
// the handler 500s. When our entry registers the module in
|
|
892
|
+
// \`__CREEK_EXT_MODS\`, the patched \`externalImport\` returns it
|
|
893
|
+
// directly without going through workerd's external loader.
|
|
894
|
+
// Fixes og-api \`/og-node\` (node runtime) +
|
|
895
|
+
// use-cache-metadata-route-handler opengraph/icon tests.
|
|
896
|
+
workerCode = workerCode.replace(/async function externalImport\((\w+)\)\s*\{\s*let\s+raw;\s*try\s*\{\s*raw\s*=\s*await import\(\1\);/g, (match, idVar) => `async function externalImport(${idVar}) {\n` +
|
|
897
|
+
` let raw;\n` +
|
|
898
|
+
` { const __loaders = globalThis.__CREEK_EXT_LOADERS; if (__loaders && __loaders[${idVar}]) {\n` +
|
|
899
|
+
` const __cached = globalThis.__CREEK_EXT_MODS = globalThis.__CREEK_EXT_MODS || {};\n` +
|
|
900
|
+
` if (${idVar} in __cached) { raw = __cached[${idVar}]; }\n` +
|
|
901
|
+
` else { try { raw = await __loaders[${idVar}](); __cached[${idVar}] = raw; } catch (err) { throw new Error(\`Failed to load external module \${${idVar}}: \${err}\`); } }\n` +
|
|
902
|
+
` if (raw && raw.__esModule && raw.default && "default" in raw.default) { return interopEsm(raw.default, createNS(raw), true); }\n` +
|
|
903
|
+
` return raw;\n` +
|
|
904
|
+
` } }\n` +
|
|
905
|
+
` try {\n` +
|
|
906
|
+
` raw = await import(${idVar});`);
|
|
907
|
+
// \`@vercel/og/index.node.js\` evaluates at module load:
|
|
908
|
+
//
|
|
909
|
+
// var fontData = fs.readFileSync(fileURLToPath(new URL("./Geist-Regular.ttf", import.meta.url)));
|
|
910
|
+
// var resvg_wasm = fs.readFileSync(fileURLToPath(new URL("./resvg.wasm", import.meta.url)));
|
|
911
|
+
//
|
|
912
|
+
// workerd rejects \`new URL("./X", import.meta.url)\` with
|
|
913
|
+
// "Invalid URL string" in the bundled-worker context, so evaluation
|
|
914
|
+
// aborts before any request hits the route. Rewrite these two calls
|
|
915
|
+
// to pass literal paths into fs.readFileSync directly — our fs shim
|
|
916
|
+
// has a basename fallback for .wasm/.ttf, so the embedded bundled
|
|
917
|
+
// bytes resolve regardless of path.
|
|
918
|
+
workerCode = workerCode.replace(/fileURLToPath\(new URL\(("\.\/[^"]+\.(?:wasm|ttf|otf|woff2?|png|jpg|jpeg|gif|webp|svg|ico)")\s*,\s*import\.meta\.url\)\)/g, (_match, filename) => filename.replace(/^"\.\//, '"'));
|
|
919
|
+
// Strip `AsyncLocalStorage.snapshot()` bindings in Next's
|
|
920
|
+
// `server/app-render/async-local-storage.js` and its Turbopack-inlined
|
|
921
|
+
// variants. On workerd, `ALS.snapshot()` captures the CURRENT IoContext
|
|
922
|
+
// and invoking the returned function from a later request throws
|
|
923
|
+
// "Cannot perform I/O on behalf of a different request". Next uses
|
|
924
|
+
// `createSnapshot()` / `runInCleanSnapshot: ALS.snapshot()` for cache-
|
|
925
|
+
// invalidation + edge-action callback propagation — both paths cross
|
|
926
|
+
// request boundaries on workerd. Replace the snapshot binding with a
|
|
927
|
+
// direct passthrough, matching @opennextjs/cloudflare's `patchUseCacheIO`
|
|
928
|
+
// (their comment: "TODO: Find a better fix for this issue.").
|
|
929
|
+
//
|
|
930
|
+
// Four forms exist post-bundling:
|
|
931
|
+
// 1. `function createSnapshot() { if (X) return X.snapshot(); return function(fn,...args){...}; }`
|
|
932
|
+
// — clean esm form from next/dist/esm (one copy embedded by wrangler).
|
|
933
|
+
// 2. `function <name>() { return X ? X.snapshot() : function(a,...b){ return a(...b); }; }`
|
|
934
|
+
// — Turbopack's minified module-level shim (edge-side copies).
|
|
935
|
+
// 3. `a.s(["bindSnapshot", 0, q, "createSnapshot", 0, function(){ return p ? p.snapshot() : ...; }])`
|
|
936
|
+
// — Turbopack's export-binding form in the same shim.
|
|
937
|
+
// 4. `runInCleanSnapshot: X ? X.snapshot() : function(a,...b){ return a(...b); }`
|
|
938
|
+
// — inlined call-site in the bundled work-store constructor.
|
|
939
|
+
// A single regex `(\w+) ? \1.snapshot() : ` stripped from the minified
|
|
940
|
+
// forms covers 2/3/4; form 1 uses an if-guard so handle it separately.
|
|
941
|
+
workerCode = workerCode.replace(/if\s*\(\s*(\w+)\s*\)\s*\{\s*return\s+\1\.snapshot\(\);\s*\}/g, "// Ignored snapshot");
|
|
942
|
+
workerCode = stripAsyncLocalStorageSnapshotTernaries(workerCode);
|
|
943
|
+
workerCode = patchAsyncLocalStorageBindSnapshot(workerCode);
|
|
944
|
+
workerCode = patchAfterContextRunCallbacksOnClose(workerCode);
|
|
945
|
+
// `use cache` arguments that contain an unresolved React thenable marker
|
|
946
|
+
// (`$@`) during a prerender are request-bound values. Next's dynamic
|
|
947
|
+
// access instrumentation usually catches this before cache fill, but an
|
|
948
|
+
// async params transform can hide the access until after serialization.
|
|
949
|
+
// Node eventually trips the 50s cache-fill timeout; under workerd that
|
|
950
|
+
// blocks the whole response. Treat the cache function as a dynamic hole
|
|
951
|
+
// immediately so the PPR fallback shell can resume.
|
|
952
|
+
workerCode = patchUseCachePrerenderDanglingPromiseBailout(workerCode);
|
|
953
|
+
// Null-fallback partial routes don't have a concrete fallback artifact
|
|
954
|
+
// on disk; under workerd the follow-up path that tries to specialize a
|
|
955
|
+
// generic shell on the first request can surface as a static-generation
|
|
956
|
+
// bailout instead of completing the request. Keep these routes on the
|
|
957
|
+
// blocking path and let the concrete request render/cache directly.
|
|
958
|
+
workerCode = patchNullFallbackPartialShellBlocking(workerCode);
|
|
959
|
+
// During App PPR/cacheComponents background revalidation, Next 16 drops
|
|
960
|
+
// the existing APP_PAGE postponed state because it only resumes dynamic
|
|
961
|
+
// RSC and server-action requests. In Workers minimal output we need that
|
|
962
|
+
// postponed state to rerender the PPR shell; otherwise the revalidation
|
|
963
|
+
// becomes a full static render and hits dynamic APIs like connection().
|
|
964
|
+
workerCode = patchAppPageRevalidationPostponedState(workerCode);
|
|
965
|
+
// Unify Next's `*AsyncStorageInstance` singletons across Turbopack
|
|
966
|
+
// chunks via a `globalThis` key. Turbopack emits the `work-unit-async-
|
|
967
|
+
// storage-instance.js` / `work-async-storage-instance.js` / etc.
|
|
968
|
+
// factories INLINE into every chunk that references them (see the
|
|
969
|
+
// `'turbopack-transition': 'next-shared'` import attribute in the
|
|
970
|
+
// next-shared layer); each chunk evaluates its own
|
|
971
|
+
// `createAsyncLocalStorage()` call and gets a fresh ALS instance.
|
|
972
|
+
// On the edge-runtime server-action path this fragments the store:
|
|
973
|
+
// the action handler runs `workUnitAsyncStorage.run(d, fn)` inside
|
|
974
|
+
// chunk A's ALS, the inner `headers()` call reads from chunk B's
|
|
975
|
+
// ALS, finds no store, and throws "headers was called outside a
|
|
976
|
+
// request scope". Node action path survives because its caller +
|
|
977
|
+
// callee happen to resolve to the same chunk's copy.
|
|
978
|
+
//
|
|
979
|
+
// Fix: rewrite the module factory bodies to key the ALS on the
|
|
980
|
+
// exported instance name (`workUnitAsyncStorageInstance`, etc.)
|
|
981
|
+
// under a single `globalThis.__CREEK_ALS` bag, so all chunks share
|
|
982
|
+
// one instance per logical store. Name-keyed is narrower than
|
|
983
|
+
// "dedup every `new AsyncLocalStorage()`" — we don't accidentally
|
|
984
|
+
// merge unrelated per-store ALSes (tracing requestStorage,
|
|
985
|
+
// react-server-dom temporaryReferences, etc.).
|
|
986
|
+
//
|
|
987
|
+
// Target pattern (both `a.i(N)` and `a.r(N)` create-variants
|
|
988
|
+
// exist):
|
|
989
|
+
// let <v> = (0, a.i(43291).createAsyncLocalStorage)();
|
|
990
|
+
// …short gap…
|
|
991
|
+
// a.s(["<Name>AsyncStorageInstance", 0, <v>], …)
|
|
992
|
+
// Replacement keeps the original create call so the per-isolate
|
|
993
|
+
// fallback works if globalThis isn't carried through an unusual
|
|
994
|
+
// loader; the `??=` idempotently promotes the first copy into the
|
|
995
|
+
// shared bag.
|
|
996
|
+
workerCode = workerCode.replace(/let\s+(\w+)\s*=\s*(\(0,\s*a\.[ir]\(\d+\)\.createAsyncLocalStorage\)\(\));([\s\S]{0,400}?a\.s\(\["(\w+AsyncStorageInstance)",\s*0,\s*\1\])/g, (_match, varName, createCall, tail, storeName) => `let ${varName} = ((globalThis.__CREEK_ALS ??= {})["${storeName}"] ??= ${createCall});${tail}`);
|
|
997
|
+
// Same dedup for the CJS-wrapped variants esbuild produces when it
|
|
998
|
+
// bundles Next's non-Turbopack ESM copy. Two observed shapes:
|
|
999
|
+
// a. `var <Name>AsyncStorageInstance = (0, _als.createAsyncLocalStorage)();`
|
|
1000
|
+
// — top-level from `work-unit-async-storage-instance.js` etc.
|
|
1001
|
+
// b. `Object.defineProperty(c, "<Name>AsyncStorageInstance", {…get…return <v>…}); let <v> = (0, a.r(N).createAsyncLocalStorage)();`
|
|
1002
|
+
// — Turbopack-emitted CJS compiled form (edge chunks).
|
|
1003
|
+
workerCode = workerCode.replace(/var\s+(\w+AsyncStorageInstance)\s*=\s*(\(0,\s*\w+\.createAsyncLocalStorage\)\(\));/g, (_match, storeName, createCall) => `var ${storeName} = ((globalThis.__CREEK_ALS ??= {})["${storeName}"] ??= ${createCall});`);
|
|
1004
|
+
workerCode = workerCode.replace(/(Object\.defineProperty\(c,\s*"(\w+AsyncStorageInstance)",\s*\{[^}]*get:\s*(?:\/\*[^*]*\*\/\s*)?(?:__name\()?function\(\)\s*\{\s*return (\w+);[^}]*\}[\s\S]*?\}\);)\s*let\s+\3\s*=\s*(\(0,\s*a\.[ir]\(\d+\)\.createAsyncLocalStorage\)\(\))/g, (_match, prefix, storeName, varName, createCall) => `${prefix}\nlet ${varName} = ((globalThis.__CREEK_ALS ??= {})["${storeName}"] ??= ${createCall})`);
|
|
1005
|
+
// Turbopack's node.js runtime implements top-level-await by assigning
|
|
1006
|
+
// \`module.exports = <Promise>\`. esbuild's \`__toESM\` wraps imports with
|
|
1007
|
+
// \`__create(__getProtoOf(mod))\`, and for a Promise that yields a plain
|
|
1008
|
+
// object whose \`__proto__\` is \`Promise.prototype\`. Awaiting that object
|
|
1009
|
+
// invokes \`Promise.prototype.then\` with a non-Promise receiver — workerd
|
|
1010
|
+
// rejects it as "incompatible receiver" and the route 500s. Detect a
|
|
1011
|
+
// Promise input and return it unchanged so await resolves the real
|
|
1012
|
+
// async-module promise instead. Fixes \`metadata-edge\` and
|
|
1013
|
+
// \`metadata-dynamic-routes-async-deps\` \`opengraph-image\` routes.
|
|
1014
|
+
workerCode = workerCode.replace(/var __toESM = \(mod, isNodeMode, target\) => \(target = mod != null \? __create\(__getProtoOf\(mod\)\) : \{\}, __copyProps\(\s*(?:\/\/[^\n]*\n\s*)*isNodeMode \|\| !mod \|\| !mod\.__esModule \? __defProp\(target, "default", \{ value: mod, enumerable: true \}\) : target,\s*mod\s*\)\);/, `var __toESM = (mod, isNodeMode, target) => {
|
|
1015
|
+
if (mod != null && typeof mod === "object" && typeof mod.then === "function" && __getProtoOf(mod) === Promise.prototype) {
|
|
1016
|
+
return mod;
|
|
1017
|
+
}
|
|
1018
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
1019
|
+
return __copyProps(
|
|
1020
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
1021
|
+
mod
|
|
1022
|
+
);
|
|
1023
|
+
};`);
|
|
283
1024
|
await fs.writeFile(workerPath, workerCode);
|
|
284
1025
|
}
|
|
285
1026
|
catch { }
|
|
286
|
-
//
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
1027
|
+
// Emit wrangler.toml that covers both local dev (workerd via wrangler dev)
|
|
1028
|
+
// and \`wrangler deploy\`. Dev and prod MUST share the same config so
|
|
1029
|
+
// deployment behavior stays predictable — that's what the adapter is
|
|
1030
|
+
// expected to guarantee.
|
|
1031
|
+
//
|
|
1032
|
+
// The file lives alongside worker.js in \`.creek/adapter-output/server/\`.
|
|
1033
|
+
// Users can override specific values by creating a \`wrangler.toml\`
|
|
1034
|
+
// at their project root — the adapter reads that at build time and
|
|
1035
|
+
// merges over our defaults (support added in a follow-up TODO).
|
|
1036
|
+
await emitWranglerConfig({
|
|
1037
|
+
outputDir: opts.outputDir,
|
|
1038
|
+
assetsRelPath: "../assets", // server/ → assets/
|
|
1039
|
+
// Same normalization as the copy loop above — ensure every wasm
|
|
1040
|
+
// filename declared in the rules ends with \`.wasm\` so wrangler's
|
|
1041
|
+
// CompiledWasm rule discovers the file we actually wrote to disk.
|
|
1042
|
+
wasmFilenames: [...opts.wasmFiles.keys()].map((n) => n.endsWith(".wasm") ? n : n + ".wasm"),
|
|
1043
|
+
});
|
|
290
1044
|
// Clean up temp files
|
|
291
1045
|
await fs.rm(entryPath, { force: true });
|
|
292
1046
|
await fs.rm(configPath, { force: true });
|
|
@@ -295,4 +1049,96 @@ export async function bundleForWorkers(opts) {
|
|
|
295
1049
|
const files = await fs.readdir(opts.outputDir);
|
|
296
1050
|
return files.filter(f => !f.startsWith("__"));
|
|
297
1051
|
}
|
|
1052
|
+
/**
|
|
1053
|
+
* Write a wrangler.toml next to worker.js so the same config drives both
|
|
1054
|
+
* \`wrangler dev\` (local dev / test harness) and \`wrangler deploy\` (prod).
|
|
1055
|
+
*
|
|
1056
|
+
* Contents:
|
|
1057
|
+
* - \`name\`, \`main\`, \`compatibility_date\`, \`compatibility_flags\` — runtime
|
|
1058
|
+
* - \`[assets]\` — binds the assets directory with \`run_worker_first\` so our
|
|
1059
|
+
* worker handles routing before workerd's static asset shortcut
|
|
1060
|
+
* - \`[[durable_objects.bindings]]\` + \`[[migrations]]\` — the three DO
|
|
1061
|
+
* classes the worker entry declares (DOQueueHandler, DOShardedTagCache,
|
|
1062
|
+
* BucketCachePurge)
|
|
1063
|
+
* - \`[[rules]] type = "CompiledWasm"\` — declares wasm siblings (next/og
|
|
1064
|
+
* yoga/resvg, sharp, etc.) as module imports for workerd's loader
|
|
1065
|
+
*/
|
|
1066
|
+
async function emitWranglerConfig(opts) {
|
|
1067
|
+
const { outputDir, assetsRelPath, wasmFilenames } = opts;
|
|
1068
|
+
// Turbopack + wrangler both emit wasm siblings (hashed \`wasm_<xxh3>\`
|
|
1069
|
+
// from us, \`<sha1>-yoga.wasm\` / \`<sha1>-resvg.wasm\` from wrangler's
|
|
1070
|
+
// own esbuild pass when the app imports next/og). A wildcard glob
|
|
1071
|
+
// covers every sibling regardless of who emitted it. Listing specific
|
|
1072
|
+
// filenames fails because wrangler normalises import paths with a
|
|
1073
|
+
// leading \`./\` that literal-name globs don't match — the built-in
|
|
1074
|
+
// \`**/*.wasm\` rule then fires and aborts the build with
|
|
1075
|
+
// "ignored because a previous rule ... was not marked as fallthrough".
|
|
1076
|
+
let hasWasm = wasmFilenames.length > 0;
|
|
1077
|
+
if (!hasWasm) {
|
|
1078
|
+
try {
|
|
1079
|
+
const entries = await fs.readdir(outputDir);
|
|
1080
|
+
hasWasm = entries.some((e) => e.endsWith(".wasm"));
|
|
1081
|
+
}
|
|
1082
|
+
catch { }
|
|
1083
|
+
}
|
|
1084
|
+
const wasmRule = hasWasm
|
|
1085
|
+
? [
|
|
1086
|
+
"",
|
|
1087
|
+
"# Declare every wasm sibling as a CompiledWasm module so \`import",
|
|
1088
|
+
"# foo from \"./<hash>.wasm\"\` in the bundle resolves at runtime.",
|
|
1089
|
+
"# Without this, Turbopack's runtime registry returns undefined and",
|
|
1090
|
+
"# throws \"dynamically loading WebAssembly is not supported\".",
|
|
1091
|
+
"[[rules]]",
|
|
1092
|
+
`globs = ["**/*.wasm"]`,
|
|
1093
|
+
`type = "CompiledWasm"`,
|
|
1094
|
+
`fallthrough = false`,
|
|
1095
|
+
].join("\n")
|
|
1096
|
+
: "";
|
|
1097
|
+
const toml = `# Generated by @solcreek/adapter-creek. Hand edits will be overwritten on
|
|
1098
|
+
# the next \`next build\`. To extend (add KV, queues, env vars, etc.), create
|
|
1099
|
+
# a \`wrangler.toml\` at your project root — the adapter merges it on top.
|
|
1100
|
+
|
|
1101
|
+
name = "creek"
|
|
1102
|
+
main = "worker.js"
|
|
1103
|
+
compatibility_date = "2026-03-23"
|
|
1104
|
+
compatibility_flags = ["nodejs_compat"]
|
|
1105
|
+
|
|
1106
|
+
# Static assets from Next.js's \`.next/static\` + \`public/\` live alongside
|
|
1107
|
+
# this server dir under \`../assets/\`. \`run_worker_first = true\` tells
|
|
1108
|
+
# workerd to invoke our worker BEFORE serving static files — the adapter
|
|
1109
|
+
# handles middleware, routing, and cache headers before any asset shortcut.
|
|
1110
|
+
[assets]
|
|
1111
|
+
directory = "${assetsRelPath}"
|
|
1112
|
+
binding = "ASSETS"
|
|
1113
|
+
run_worker_first = true
|
|
1114
|
+
|
|
1115
|
+
# Durable Object classes declared by the adapter's worker-entry. The
|
|
1116
|
+
# binding \`name\` (what runtime code reads via \`env.<NAME>\`) must match
|
|
1117
|
+
# what Creek's control plane injects in production — see
|
|
1118
|
+
# \`packages/control-plane/src/modules/deployments/deploy.ts:129-140\` in
|
|
1119
|
+
# the Creek repo. Do NOT rename these without coordinating with Creek.
|
|
1120
|
+
#
|
|
1121
|
+
# \`class_name\` is our internal export name from worker-entry.ts.
|
|
1122
|
+
# \`new_sqlite_classes\` (not \`new_classes\`) selects SQLite-backed DO
|
|
1123
|
+
# storage — DOShardedTagCache and BucketCachePurge both use
|
|
1124
|
+
# \`ctx.storage.sql\`. DOQueueHandler is currently a placeholder but
|
|
1125
|
+
# declared under SQLite too so future queue persistence has no migration
|
|
1126
|
+
# cost.
|
|
1127
|
+
[[durable_objects.bindings]]
|
|
1128
|
+
name = "NEXT_CACHE_DO_QUEUE"
|
|
1129
|
+
class_name = "DOQueueHandler"
|
|
1130
|
+
[[durable_objects.bindings]]
|
|
1131
|
+
name = "NEXT_TAG_CACHE_DO_SHARDED"
|
|
1132
|
+
class_name = "DOShardedTagCache"
|
|
1133
|
+
[[durable_objects.bindings]]
|
|
1134
|
+
name = "NEXT_CACHE_DO_BUCKET_PURGE"
|
|
1135
|
+
class_name = "BucketCachePurge"
|
|
1136
|
+
|
|
1137
|
+
[[migrations]]
|
|
1138
|
+
tag = "v1"
|
|
1139
|
+
new_sqlite_classes = ["DOQueueHandler", "DOShardedTagCache", "BucketCachePurge"]
|
|
1140
|
+
${wasmRule}
|
|
1141
|
+
`;
|
|
1142
|
+
await fs.writeFile(path.join(outputDir, "wrangler.toml"), toml);
|
|
1143
|
+
}
|
|
298
1144
|
//# sourceMappingURL=bundler.js.map
|