Skip to content

Commit 2ce6454

Browse files
authored
Extract the worker init code to a separate file (#563)
1 parent 6343fb4 commit 2ce6454

File tree

7 files changed

+168
-104
lines changed

7 files changed

+168
-104
lines changed

.changeset/eleven-sloths-punch.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@opennextjs/cloudflare": patch
3+
---
4+
5+
Extract the worker init code to a separate file

packages/cloudflare/src/cli/build/build.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type { ProjectOptions } from "../project-options.js";
1212
import { bundleServer } from "./bundle-server.js";
1313
import { compileCacheAssetsManifestSqlFile } from "./open-next/compile-cache-assets-manifest.js";
1414
import { compileEnvFiles } from "./open-next/compile-env-files.js";
15+
import { compileInit } from "./open-next/compile-init.js";
1516
import { compileDurableObjects } from "./open-next/compileDurableObjects.js";
1617
import { createServerBundle } from "./open-next/createServerBundle.js";
1718
import { createWranglerConfigIfNotExistent } from "./utils/index.js";
@@ -63,6 +64,9 @@ export async function build(
6364
// Compile .env files
6465
compileEnvFiles(options);
6566

67+
// Compile workerd init
68+
compileInit(options);
69+
6670
// Compile middleware
6771
await createMiddleware(options, { forceOnlyBuildOnce: true });
6872

packages/cloudflare/src/cli/build/bundle-server.ts

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -144,46 +144,6 @@ export async function bundleServer(buildOpts: BuildOptions): Promise<void> {
144144
"process.env.__NEXT_EXPERIMENTAL_REACT": `${needsExperimentalReact(nextConfig)}`,
145145
},
146146
platform: "node",
147-
banner: {
148-
js: `
149-
// Used by unbundled js files (which don't inherit the __dirname present in the define field)
150-
// so we also need to set it on the global scope
151-
// Note: this was hit in the next/dist/compiled/@opentelemetry/api module
152-
globalThis.__dirname ??= "";
153-
globalThis.__filename ??= "";
154-
155-
// Do not crash on cache not supported
156-
// https://github.com/cloudflare/workerd/pull/2434
157-
// compatibility flag "cache_option_enabled" -> does not support "force-cache"
158-
const curFetch = globalThis.fetch;
159-
globalThis.fetch = (input, init) => {
160-
if (init) {
161-
delete init.cache;
162-
}
163-
return curFetch(input, init);
164-
};
165-
import __cf_stream from 'node:stream';
166-
fetch = globalThis.fetch;
167-
const CustomRequest = class extends globalThis.Request {
168-
constructor(input, init) {
169-
if (init) {
170-
delete init.cache;
171-
// https://github.com/cloudflare/workerd/issues/2746
172-
// https://github.com/cloudflare/workerd/issues/3245
173-
Object.defineProperty(init, "body", {
174-
value: init.body instanceof __cf_stream.Readable ? ReadableStream.from(init.body) : init.body
175-
});
176-
}
177-
super(input, init);
178-
}
179-
};
180-
globalThis.Request = CustomRequest;
181-
Request = globalThis.Request;
182-
// Makes the edge converter returns either a Response or a Request.
183-
globalThis.__dangerous_ON_edge_converter_returns_request = true;
184-
globalThis.__BUILD_TIMESTAMP_MS__ = ${Date.now()};
185-
`,
186-
},
187147
});
188148

189149
fs.writeFileSync(openNextServerBundle + ".meta.json", JSON.stringify(result.metafile, null, 2));

packages/cloudflare/src/cli/build/open-next/compile-env-files.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { extractProjectEnvVars } from "../utils/index.js";
99
* Compiles the values extracted from the project's env files to the output directory for use in the worker.
1010
*/
1111
export function compileEnvFiles(buildOpts: BuildOptions) {
12-
const envDir = path.join(buildOpts.outputDir, "env");
12+
const envDir = path.join(buildOpts.outputDir, "cloudflare");
1313
fs.mkdirSync(envDir, { recursive: true });
1414
["production", "development", "test"].forEach((mode) =>
1515
fs.appendFileSync(
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import path from "node:path";
2+
import { fileURLToPath } from "node:url";
3+
4+
import type { BuildOptions } from "@opennextjs/aws/build/helper";
5+
import { build } from "esbuild";
6+
7+
/**
8+
* Compiles the initialization code for the workerd runtime
9+
*/
10+
export async function compileInit(options: BuildOptions) {
11+
const currentDir = path.join(path.dirname(fileURLToPath(import.meta.url)));
12+
const templatesDir = path.join(currentDir, "../../templates");
13+
const initPath = path.join(templatesDir, "init.js");
14+
15+
await build({
16+
entryPoints: [initPath],
17+
outdir: path.join(options.outputDir, "cloudflare"),
18+
bundle: false,
19+
minify: false,
20+
format: "esm",
21+
target: "esnext",
22+
platform: "node",
23+
define: {
24+
__BUILD_TIMESTAMP_MS__: JSON.stringify(Date.now()),
25+
},
26+
});
27+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/**
2+
* Initialization for the workerd runtime.
3+
*
4+
* The file must be imported at the top level the worker.
5+
*/
6+
7+
import { AsyncLocalStorage } from "node:async_hooks";
8+
import process from "node:process";
9+
import stream from "node:stream";
10+
11+
// @ts-expect-error: resolved by wrangler build
12+
import * as nextEnvVars from "./next-env.mjs";
13+
14+
const cloudflareContextALS = new AsyncLocalStorage();
15+
16+
// Note: this symbol needs to be kept in sync with `src/api/get-cloudflare-context.ts`
17+
Object.defineProperty(globalThis, Symbol.for("__cloudflare-context__"), {
18+
get() {
19+
return cloudflareContextALS.getStore();
20+
},
21+
});
22+
23+
/**
24+
* Executes the handler with the Cloudflare context.
25+
*/
26+
export async function runWithCloudflareRequestContext(
27+
request: Request,
28+
env: CloudflareEnv,
29+
ctx: ExecutionContext,
30+
handler: () => Promise<Response>
31+
): Promise<Response> {
32+
init(request, env);
33+
34+
return cloudflareContextALS.run({ env, ctx, cf: request.cf }, handler);
35+
}
36+
37+
let initialized = false;
38+
39+
/**
40+
* Initializes the runtime on the first call,
41+
* no-op on subsequent invocations.
42+
*/
43+
function init(request: Request, env: CloudflareEnv) {
44+
if (initialized) {
45+
return;
46+
}
47+
initialized = true;
48+
49+
const url = new URL(request.url);
50+
51+
initRuntime();
52+
populateProcessEnv(url, env);
53+
}
54+
55+
function initRuntime() {
56+
// Some packages rely on `process.version` and `process.versions.node` (i.e. Jose@4)
57+
// TODO: Remove when https://github.com/unjs/unenv/pull/493 is merged
58+
Object.assign(process, { version: process.version || "v22.14.0" });
59+
// @ts-expect-error Node type does not match workerd
60+
Object.assign(process.versions, { node: "22.14.0", ...process.versions });
61+
62+
globalThis.__dirname ??= "";
63+
globalThis.__filename ??= "";
64+
65+
// Do not crash on cache not supported
66+
// https://github.com/cloudflare/workerd/pull/2434
67+
// compatibility flag "cache_option_enabled" -> does not support "force-cache"
68+
const __original_fetch = globalThis.fetch;
69+
70+
globalThis.fetch = (input, init) => {
71+
if (init) {
72+
delete (init as { cache: unknown }).cache;
73+
}
74+
return __original_fetch(input, init);
75+
};
76+
77+
const CustomRequest = class extends globalThis.Request {
78+
constructor(input: RequestInfo | URL, init?: RequestInit) {
79+
if (init) {
80+
delete (init as { cache: unknown }).cache;
81+
// https://github.com/cloudflare/workerd/issues/2746
82+
// https://github.com/cloudflare/workerd/issues/3245
83+
Object.defineProperty(init, "body", {
84+
// @ts-ignore
85+
value: init.body instanceof stream.Readable ? ReadableStream.from(init.body) : init.body,
86+
});
87+
}
88+
super(input, init);
89+
}
90+
};
91+
92+
Object.assign(globalThis, {
93+
Request: CustomRequest,
94+
//@ts-expect-error Inline at build time by ESBuild
95+
__BUILD_TIMESTAMP_MS__: __BUILD_TIMESTAMP_MS__,
96+
});
97+
}
98+
99+
/**
100+
* Populate process.env with:
101+
* - the environment variables and secrets from the cloudflare platform
102+
* - the variables from Next .env* files
103+
* - the origin resolver information
104+
*/
105+
function populateProcessEnv(url: URL, env: CloudflareEnv) {
106+
for (const [key, value] of Object.entries(env)) {
107+
if (typeof value === "string") {
108+
process.env[key] = value;
109+
}
110+
}
111+
112+
const mode = env.NEXTJS_ENV ?? "production";
113+
if (nextEnvVars[mode]) {
114+
for (const key in nextEnvVars[mode]) {
115+
process.env[key] ??= nextEnvVars[mode][key];
116+
}
117+
}
118+
119+
// Set the default Origin for the origin resolver.
120+
// This is only needed for an external middleware bundle
121+
process.env.OPEN_NEXT_ORIGIN = JSON.stringify({
122+
default: {
123+
host: url.hostname,
124+
protocol: url.protocol.slice(0, -1),
125+
port: url.port,
126+
},
127+
});
128+
}
Lines changed: 3 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,16 @@
1-
import { AsyncLocalStorage } from "node:async_hooks";
2-
import process from "node:process";
3-
4-
import type { CloudflareContext } from "../../api";
5-
// @ts-expect-error: resolved by wrangler build
6-
import * as nextEnvVars from "./env/next-env.mjs";
7-
8-
const cloudflareContextALS = new AsyncLocalStorage<CloudflareContext>();
9-
10-
// Note: this symbol needs to be kept in sync with `src/api/get-cloudflare-context.ts`
11-
Object.defineProperty(globalThis, Symbol.for("__cloudflare-context__"), {
12-
get() {
13-
return cloudflareContextALS.getStore();
14-
},
15-
});
1+
//@ts-expect-error: Will be resolved by wrangler build
2+
import { runWithCloudflareRequestContext } from "./cloudflare/init.js";
163

174
//@ts-expect-error: Will be resolved by wrangler build
185
export { DOQueueHandler } from "./.build/durable-objects/queue.js";
196
//@ts-expect-error: Will be resolved by wrangler build
207
export { DOShardedTagCache } from "./.build/durable-objects/sharded-tag-cache.js";
218

22-
// Populate process.env on the first request
23-
let processEnvPopulated = false;
24-
259
export default {
2610
async fetch(request, env, ctx) {
27-
return cloudflareContextALS.run({ env, ctx, cf: request.cf }, async () => {
11+
return runWithCloudflareRequestContext(request, env, ctx, async () => {
2812
const url = new URL(request.url);
2913

30-
populateProcessEnv(url, env);
31-
3214
// Serve images in development.
3315
// Note: "/cdn-cgi/image/..." requests do not reach production workers.
3416
if (url.pathname.startsWith("/cdn-cgi/image/")) {
@@ -57,45 +39,3 @@ export default {
5739
});
5840
},
5941
} as ExportedHandler<CloudflareEnv>;
60-
61-
/**
62-
* Populate process.env with:
63-
* - the environment variables and secrets from the cloudflare platform
64-
* - the variables from Next .env* files
65-
* - the origin resolver information
66-
*/
67-
function populateProcessEnv(url: URL, env: CloudflareEnv) {
68-
if (processEnvPopulated) {
69-
return;
70-
}
71-
72-
// Some packages rely on `process.version` and `process.versions.node` (i.e. Jose@4)
73-
// TODO: Remove when https://github.com/unjs/unenv/pull/493 is merged
74-
Object.assign(process, { version: process.version || "v22.14.0" });
75-
// @ts-expect-error Node type does not match workerd
76-
Object.assign(process.versions, { node: "22.14.0", ...process.versions });
77-
78-
processEnvPopulated = true;
79-
80-
for (const [key, value] of Object.entries(env)) {
81-
if (typeof value === "string") {
82-
process.env[key] = value;
83-
}
84-
}
85-
86-
const mode = env.NEXTJS_ENV ?? "production";
87-
if (nextEnvVars[mode]) {
88-
for (const key in nextEnvVars[mode]) {
89-
process.env[key] ??= nextEnvVars[mode][key];
90-
}
91-
}
92-
93-
// Set the default Origin for the origin resolver.
94-
process.env.OPEN_NEXT_ORIGIN = JSON.stringify({
95-
default: {
96-
host: url.hostname,
97-
protocol: url.protocol.slice(0, -1),
98-
port: url.port,
99-
},
100-
});
101-
}

0 commit comments

Comments
 (0)