-
Notifications
You must be signed in to change notification settings - Fork 73
Extract the worker init code to a separate file #563
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
d93cb34
47f79d5
5718b3d
6aca644
9fff83d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@opennextjs/cloudflare": patch | ||
| --- | ||
|
|
||
| Extract the worker init code to a separate file |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| import path from "node:path"; | ||
| import { fileURLToPath } from "node:url"; | ||
|
|
||
| import type { BuildOptions } from "@opennextjs/aws/build/helper"; | ||
| import { build } from "esbuild"; | ||
|
|
||
| /** | ||
| * Compiles the initialization code for the workerd runtime | ||
| */ | ||
| export async function compileInit(options: BuildOptions) { | ||
| const currentDir = path.join(path.dirname(fileURLToPath(import.meta.url))); | ||
| const templatesDir = path.join(currentDir, "../../templates"); | ||
| const initPath = path.join(templatesDir, "init.js"); | ||
|
|
||
| await build({ | ||
| entryPoints: [initPath], | ||
| outdir: path.join(options.outputDir, "cloudflare"), | ||
| bundle: false, | ||
| minify: false, | ||
| format: "esm", | ||
| target: "esnext", | ||
| platform: "node", | ||
| define: { | ||
| __BUILD_TIMESTAMP_MS__: JSON.stringify(Date.now()), | ||
| }, | ||
| }); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,133 @@ | ||
| /** | ||
| * Initialization for the workerd runtime. | ||
| * | ||
| * The file must be imported at the top level the worker. | ||
| */ | ||
|
|
||
| import { AsyncLocalStorage } from "node:async_hooks"; | ||
| import process from "node:process"; | ||
| import stream from "node:stream"; | ||
|
|
||
| // @ts-expect-error: resolved by wrangler build | ||
| import * as nextEnvVars from "./next-env.mjs"; | ||
|
|
||
| const cloudflareContextALS = new AsyncLocalStorage(); | ||
|
|
||
| // Note: this symbol needs to be kept in sync with `src/api/get-cloudflare-context.ts` | ||
| Object.defineProperty(globalThis, Symbol.for("__cloudflare-context__"), { | ||
| get() { | ||
| return cloudflareContextALS.getStore(); | ||
| }, | ||
| }); | ||
|
|
||
| /** | ||
| * Executes the handler with the Cloudflare context. | ||
| */ | ||
| export async function runWithCloudflareRequestContext( | ||
| request: Request, | ||
| env: CloudflareEnv, | ||
| ctx: ExecutionContext, | ||
| handler: () => Promise<Response> | ||
| ): Promise<Response> { | ||
| init(request, env); | ||
|
|
||
| return cloudflareContextALS.run({ env, ctx, cf: request.cf }, handler); | ||
| } | ||
|
|
||
| let initialized = false; | ||
|
|
||
| /** | ||
| * Initializes the runtime on the first call, | ||
| * no-op on subsequent invocations. | ||
| */ | ||
| function init(request: Request, env: CloudflareEnv) { | ||
| if (initialized) { | ||
| return; | ||
| } | ||
| initialized = true; | ||
|
|
||
| const url = new URL(request.url); | ||
|
|
||
| initRuntime(); | ||
| populateProcessEnv(url, env); | ||
| } | ||
|
|
||
| function initRuntime() { | ||
| // Some packages rely on `process.version` and `process.versions.node` (i.e. Jose@4) | ||
| // TODO: Remove when https://github.com/unjs/unenv/pull/493 is merged | ||
| Object.assign(process, { version: process.version || "v22.14.0" }); | ||
| // @ts-expect-error Node type does not match workerd | ||
| Object.assign(process.versions, { node: "22.14.0", ...process.versions }); | ||
|
|
||
| // Used by unbundled js files (which don't inherit the __dirname present in the define field) | ||
| // so we also need to set it on the global scope | ||
| // Note: this was hit in the next/dist/compiled/@opentelemetry/api module | ||
| globalThis.__dirname ??= ""; | ||
| globalThis.__filename ??= ""; | ||
|
|
||
| // Do not crash on cache not supported | ||
| // https://github.com/cloudflare/workerd/pull/2434 | ||
| // compatibility flag "cache_option_enabled" -> does not support "force-cache" | ||
| const __original_fetch = globalThis.fetch; | ||
|
|
||
| globalThis.fetch = (input, init) => { | ||
| if (init) { | ||
| delete (init as { cache: unknown }).cache; | ||
| } | ||
| return __original_fetch(input, init); | ||
| }; | ||
|
|
||
| const CustomRequest = class extends globalThis.Request { | ||
| constructor(input: RequestInfo | URL, init?: RequestInit) { | ||
| if (init) { | ||
| delete (init as { cache: unknown }).cache; | ||
| // https://github.com/cloudflare/workerd/issues/2746 | ||
| // https://github.com/cloudflare/workerd/issues/3245 | ||
| Object.defineProperty(init, "body", { | ||
| // @ts-ignore | ||
| value: init.body instanceof stream.Readable ? ReadableStream.from(init.body) : init.body, | ||
| }); | ||
| } | ||
| super(input, init); | ||
| } | ||
| }; | ||
|
|
||
| Object.assign(globalThis, { | ||
| Request: CustomRequest, | ||
| // This is only needed for an external middleware bundle | ||
| __dangerous_ON_edge_converter_returns_request: true, | ||
|
||
| //@ts-expect-error Inline at build time by ESBuild | ||
| __BUILD_TIMESTAMP_MS__: __BUILD_TIMESTAMP_MS__, | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Populate process.env with: | ||
| * - the environment variables and secrets from the cloudflare platform | ||
| * - the variables from Next .env* files | ||
| * - the origin resolver information | ||
| */ | ||
| function populateProcessEnv(url: URL, env: CloudflareEnv) { | ||
| for (const [key, value] of Object.entries(env)) { | ||
| if (typeof value === "string") { | ||
| process.env[key] = value; | ||
| } | ||
| } | ||
|
|
||
| const mode = env.NEXTJS_ENV ?? "production"; | ||
| if (nextEnvVars[mode]) { | ||
| for (const key in nextEnvVars[mode]) { | ||
| process.env[key] ??= nextEnvVars[mode][key]; | ||
| } | ||
| } | ||
|
|
||
| // Set the default Origin for the origin resolver. | ||
| // This is only needed for an external middleware bundle | ||
| process.env.OPEN_NEXT_ORIGIN = JSON.stringify({ | ||
vicb marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| default: { | ||
| host: url.hostname, | ||
| protocol: url.protocol.slice(0, -1), | ||
| port: url.port, | ||
| }, | ||
| }); | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.