| 1 | /** |
| 2 | * `@alchemy.run/frontend-frameworks/nuxt/cloudflare` — the Cloudflare Workers deploy target |
| 3 | * for `@alchemy.run/frontend-frameworks/nuxt`. |
| 4 | * |
| 5 | * Nuxt 4's Cloudflare story is nitro's `cloudflare_module` preset: the |
| 6 | * `.output` it produces is already self-contained workerd ESM |
| 7 | * (`.output/server/index.mjs` + chunks, `.output/public` static assets with |
| 8 | * prerendered pages and `_headers` merged in), so this target needs no |
| 9 | * finishing pass. What it owns: |
| 10 | * |
| 11 | * - **`nitroPreset`** — `"cloudflare_module"`, enforced as the last word on |
| 12 | * the resolved nitro config. |
| 13 | * - **`configureNitro`** — wrangler-free doctrine: `cloudflare.deployConfig: |
| 14 | * false` (nitro must never write a `wrangler.json` into user projects) and |
| 15 | * `cloudflare.nodeCompat: true` by default (without a wrangler config on |
| 16 | * disk nitro would otherwise skip its hybrid workerd node-compat; the |
| 17 | * deployed worker enables the `nodejs_compat` flag to match). Also wires |
| 18 | * the user-entry seam: a configured `main` becomes nitro's `entry`. |
| 19 | * - **`entry`** — surfaces `config.main` as the generic user-entry carriage. |
| 20 | * Nitro's entry module is the exports seam: the emitted worker exports are |
| 21 | * exactly the entry module's exports, so a user entry that re-exports |
| 22 | * nitro's runtime handler (`nitropack/presets/cloudflare/runtime/cloudflare-module`) |
| 23 | * and adds its own classes gets Durable Objects et al. onto the same |
| 24 | * worker. |
| 25 | * - **`bundle`** — workerd resolve conditions and the `cloudflare:` |
| 26 | * externals, for callers that post-process server code. |
| 27 | * - **`devPlatform`** — the wrangler-free dev bridge: opens |
| 28 | * cloudflare-runtime's platform proxy (a local workerd instance hosting |
| 29 | * the configured binding hooks) and injects a nitro plugin that serves |
| 30 | * `event.context.cf` / `event.context.waitUntil` / |
| 31 | * `event.context.cloudflare = { request, env, context }` inside nitro's |
| 32 | * dev SSR worker thread (see `./dev/host.ts`). |
| 33 | */ |
| 34 | import { makeDeployTarget } from "../core/index.ts"; |
| 35 | import { makeCloudflareDevPlatform } from "./dev/host.ts"; |
| 36 | import type { NuxtTarget, NuxtTargetConfig } from "./Nuxt.ts"; |
| 37 | |
| 38 | /** The nitro deployment preset this target builds with. */ |
| 39 | export const NITRO_PRESET = "cloudflare_module"; |
| 40 | |
| 41 | /** |
| 42 | * The importable specifier of nitro's cloudflare-module runtime handler — the |
| 43 | * module a USER worker entry re-exports to wrap the framework's fetch |
| 44 | * handler (the "wrappable handler" half of the user-entry seam): |
| 45 | * |
| 46 | * ```ts |
| 47 | * // worker-entry.mjs |
| 48 | * import nitroHandler from "nitropack/presets/cloudflare/runtime/cloudflare-module"; |
| 49 | * export { MyDurableObject } from "./durable-object.mjs"; |
| 50 | * export default nitroHandler; |
| 51 | * ``` |
| 52 | */ |
| 53 | export const NITRO_HANDLER_SPECIFIER = |
| 54 | "nitropack/presets/cloudflare/runtime/cloudflare-module"; |
| 55 | |
| 56 | /** |
| 57 | * Create the Cloudflare Workers {@link NuxtTarget}. See the module doc for |
| 58 | * the seams. The config's `compatibilityDate`/`compatibilityFlags` are |
| 59 | * carried for serve/deploy consumers; the build itself only needs |
| 60 | * `nodeCompat` (default `true`) and the optional user `main`. |
| 61 | */ |
| 62 | export const makeCloudflareTarget = ( |
| 63 | config: NuxtTargetConfig = {}, |
| 64 | ): NuxtTarget => |
| 65 | makeDeployTarget({ |
| 66 | platform: "cloudflare", |
| 67 | config, |
| 68 | bundle: { |
| 69 | conditions: ["workerd", "worker", "module", "browser"], |
| 70 | external: ["cloudflare:"], |
| 71 | }, |
| 72 | entry: config.main !== undefined ? { main: config.main } : undefined, |
| 73 | nitroPreset: NITRO_PRESET, |
| 74 | configureNitro: (nitroConfig, _context) => { |
| 75 | const cloudflare = |
| 76 | nitroConfig.cloudflare !== null && |
| 77 | typeof nitroConfig.cloudflare === "object" |
| 78 | ? nitroConfig.cloudflare |
| 79 | : {}; |
| 80 | nitroConfig.cloudflare = { |
| 81 | ...cloudflare, |
| 82 | // Wrangler-free: never write a wrangler.json into the user project. |
| 83 | deployConfig: false, |
| 84 | // Without a wrangler config on disk, nitro only enables its hybrid |
| 85 | // workerd node-compat when told to. The deployed worker enables the |
| 86 | // `nodejs_compat` compatibility flag to match. |
| 87 | nodeCompat: config.nodeCompat ?? true, |
| 88 | }; |
| 89 | // NOTE: the user entry (context.entry) is deliberately NOT wired here. |
| 90 | // The framework package sets it on the nitro INSTANCE at `nitro:init`: |
| 91 | // a config-level entry would leak into the prerenderer's Node-preset |
| 92 | // clone (it copies `options._config`) and a workerd-flavored entry |
| 93 | // (importing `cloudflare:` modules) cannot load there. |
| 94 | }, |
| 95 | // Dev: `event.context.cloudflare` served through cloudflare-runtime's |
| 96 | // platform proxy (workerd hosting the binding hooks), bridged into |
| 97 | // nitro's dev SSR worker thread by the shipped nitro plugin. Note the |
| 98 | // proxy cannot host user worker-entry classes (Durable Objects declared |
| 99 | // via the entry/exports seam only exist in the production build), so |
| 100 | // DO namespace bindings are not servable in dev yet. |
| 101 | devPlatform: makeCloudflareDevPlatform({ |
| 102 | compatibilityDate: config.compatibilityDate, |
| 103 | compatibilityFlags: config.compatibilityFlags, |
| 104 | }), |
| 105 | }); |
| 106 | |
| 107 | /** |
| 108 | * The deploy-target module contract (`resolveDeployTarget` accepts the |
| 109 | * default export — or the named `target` export — as a value or factory). |
| 110 | */ |
| 111 | export const target = makeCloudflareTarget; |
| 112 | |
| 113 | export default makeCloudflareTarget; |