diff --git a/.changeset/twenty-signs-brush.md b/.changeset/twenty-signs-brush.md new file mode 100644 index 000000000000..a18ed3f1991c --- /dev/null +++ b/.changeset/twenty-signs-brush.md @@ -0,0 +1,28 @@ +--- +'@astrojs/cloudflare': minor +--- + +Adds support for more `@cloudflare/vite-plugin` options + +The adapter now accepts the following [options from Cloudflare's Vite plugin](https://developers.cloudflare.com/workers/vite-plugin/reference/api/): + +- `auxiliaryWorkers` +- `configPath` +- `inspectorPort` +- `persistState` +- `remoteBindings` +- `experimental.headersAndRedirectsDevModeSupport` + +For example, you can now set `inspectorPort` to provide a custom port for debugging your Workers: + +```js +// astro.config.mjs +import { defineConfig } from 'astro/config' +import cloudflare from '@astrojs/cloudflare' + +export default defineConfig({ + adapter: cloudflare({ + inspectorPort: 3456 + }) +}) +``` \ No newline at end of file diff --git a/packages/integrations/cloudflare/src/entrypoints/preview.ts b/packages/integrations/cloudflare/src/entrypoints/preview.ts index 9265a9ee2686..e6d8affc2020 100644 --- a/packages/integrations/cloudflare/src/entrypoints/preview.ts +++ b/packages/integrations/cloudflare/src/entrypoints/preview.ts @@ -6,11 +6,10 @@ import { type ResolvedServerUrls, } from 'vite'; import { fileURLToPath } from 'node:url'; -import { cloudflare as cfVitePlugin, type PluginConfig } from '@cloudflare/vite-plugin'; +import { cloudflare as cfVitePlugin } from '@cloudflare/vite-plugin'; import type * as http from 'node:http'; import colors from 'piccolore'; import { performance } from 'node:perf_hooks'; -import { cloudflareConfigCustomizer } from '../wrangler.js'; const createPreviewServer: CreatePreviewServer = async ({ logger, @@ -23,10 +22,6 @@ const createPreviewServer: CreatePreviewServer = async ({ }) => { const startServerTime = performance.now(); let previewServer: VitePreviewServer; - const cfPluginConfig: PluginConfig = { - viteEnvironment: { name: 'ssr' }, - config: cloudflareConfigCustomizer(), - }; try { previewServer = await preview({ @@ -44,7 +39,9 @@ const createPreviewServer: CreatePreviewServer = async ({ open: false, allowedHosts: [], }, - plugins: [cfVitePlugin(cfPluginConfig)], + plugins: [ + cfVitePlugin({ ...globalThis.astroCloudflareOptions, viteEnvironment: { name: 'ssr' } }), + ], }); } catch (err) { if (err instanceof Error) { diff --git a/packages/integrations/cloudflare/src/index.ts b/packages/integrations/cloudflare/src/index.ts index a4798a878f5e..c5b9eaf9c300 100644 --- a/packages/integrations/cloudflare/src/index.ts +++ b/packages/integrations/cloudflare/src/index.ts @@ -21,10 +21,15 @@ import { import { parseEnv } from 'node:util'; import { sessionDrivers } from 'astro/config'; import { createCloudflarePrerenderer } from './prerenderer.js'; +import { createRequire } from 'node:module'; export type { Runtime } from './utils/handler.js'; -export type Options = { +export interface Options + extends Pick< + PluginConfig, + 'auxiliaryWorkers' | 'configPath' | 'inspectorPort' | 'persistState' | 'remoteBindings' + > { /** Options for handling images. */ imageService?: ImageServiceConfig; @@ -49,18 +54,26 @@ export type Options = { * See https://developers.cloudflare.com/images/transform-images/bindings/ for more details. */ imagesBindingName?: string; -}; -export default function createIntegration(args?: Options): AstroIntegration { + experimental?: Pick< + NonNullable, + 'headersAndRedirectsDevModeSupport' + >; +} + +export default function createIntegration({ + imageService, + sessionKVBindingName = DEFAULT_SESSION_KV_BINDING_NAME, + imagesBindingName = DEFAULT_IMAGES_BINDING_NAME, + ...cloudflareOptions +}: Options = {}): AstroIntegration { let _config: AstroConfig; let _routes: IntegrationResolvedRoute[]; let _isFullyStatic = false; + let cfPluginConfig: PluginConfig; - const sessionKVBindingName = args?.sessionKVBindingName ?? DEFAULT_SESSION_KV_BINDING_NAME; - const imagesBindingName = args?.imagesBindingName ?? DEFAULT_IMAGES_BINDING_NAME; - - const { buildService, runtimeService } = normalizeImageServiceConfig(args?.imageService); + const { buildService, runtimeService } = normalizeImageServiceConfig(imageService); const needsImagesBinding = runtimeService === 'cloudflare-binding'; return { @@ -97,17 +110,17 @@ export default function createIntegration(args?: Options): AstroIntegration { ttl: session?.ttl, }; } + // In dev, `compile` needs the IMAGES binding for real transforms // (the image-transform-endpoint uses it). At build time, // `compile` uses Sharp on the Node side instead. const needsImagesBindingForDev = isCompile && command === 'dev'; - const cfPluginConfig: PluginConfig = { - viteEnvironment: { name: 'ssr' }, + cfPluginConfig = { config: cloudflareConfigCustomizer({ - sessionKVBindingName: args?.sessionKVBindingName, + sessionKVBindingName, imagesBindingName: - needsImagesBinding || needsImagesBindingForDev ? args?.imagesBindingName : false, + needsImagesBinding || needsImagesBindingForDev ? imagesBindingName : false, }), experimental: { prerenderWorker: { @@ -125,6 +138,12 @@ export default function createIntegration(args?: Options): AstroIntegration { }, }; + // The preview entrypoint uses Cloudflare's vite plugin and so it needs access + // to the config. But there's no proper API for this so we use globalThis. + if (command === 'preview') { + globalThis.astroCloudflareOptions = cfPluginConfig; + } + updateConfig({ build: { redirects: false, @@ -132,7 +151,7 @@ export default function createIntegration(args?: Options): AstroIntegration { session, vite: { plugins: [ - cfVitePlugin(cfPluginConfig), + cfVitePlugin({ ...cfPluginConfig, viteEnvironment: { name: 'ssr' } }), { name: '@astrojs/cloudflare:cf-imports', enforce: 'pre', @@ -229,9 +248,13 @@ export default function createIntegration(args?: Options): AstroIntegration { }), ], }, - image: setImageConfig(args?.imageService, config.image, command, logger), + image: setImageConfig(imageService, config.image, command, logger), }); + if (cloudflareOptions.configPath) { + addWatchFile(createRequire(import.meta.url).resolve(cloudflareOptions.configPath)); + } + addWatchFile(new URL('./wrangler.toml', config.root)); addWatchFile(new URL('./wrangler.json', config.root)); addWatchFile(new URL('./wrangler.jsonc', config.root)); @@ -298,6 +321,7 @@ export default function createIntegration(args?: Options): AstroIntegration { clientDir: _config.build.client, base: _config.base, trailingSlash: _config.trailingSlash, + cfPluginConfig, hasCompileImageService: buildService === 'compile', }), ); @@ -322,9 +346,7 @@ export default function createIntegration(args?: Options): AstroIntegration { // in a global way, so we shim their access as `process.env.*`. This is not the recommended way for users to access environment variables. But we'll add this for compatibility for chosen variables. Mainly to support `@astrojs/db` vite.define = { 'process.env': 'process.env', - 'globalThis.__ASTRO_IMAGES_BINDING_NAME': JSON.stringify( - args?.imagesBindingName ?? 'IMAGES', - ), + 'globalThis.__ASTRO_IMAGES_BINDING_NAME': JSON.stringify(imagesBindingName), ...vite.define, }; } diff --git a/packages/integrations/cloudflare/src/prerenderer.ts b/packages/integrations/cloudflare/src/prerenderer.ts index e65034b54ad0..7bac45c085c7 100644 --- a/packages/integrations/cloudflare/src/prerenderer.ts +++ b/packages/integrations/cloudflare/src/prerenderer.ts @@ -8,7 +8,6 @@ import { preview, type PreviewServer as VitePreviewServer } from 'vite'; import { fileURLToPath } from 'node:url'; import { mkdir } from 'node:fs/promises'; import { cloudflare as cfVitePlugin, type PluginConfig } from '@cloudflare/vite-plugin'; -import { cloudflareConfigCustomizer } from './wrangler.js'; import { serializeRouteData, deserializeRouteData } from 'astro/app/manifest'; import type { StaticPathsResponse, @@ -27,6 +26,7 @@ interface CloudflarePrerendererOptions { clientDir: AstroConfig['build']['client']; base: AstroConfig['base']; trailingSlash: AstroConfig['trailingSlash']; + cfPluginConfig: PluginConfig; hasCompileImageService: boolean; } @@ -40,6 +40,7 @@ export function createCloudflarePrerenderer({ clientDir, base, trailingSlash, + cfPluginConfig, hasCompileImageService, }: CloudflarePrerendererOptions): AstroPrerenderer { let previewServer: VitePreviewServer | undefined; @@ -52,11 +53,6 @@ export function createCloudflarePrerenderer({ // Ensure client dir exists (CF plugin expects it for assets) await mkdir(clientDir, { recursive: true }); - const cfPluginConfig: PluginConfig = { - viteEnvironment: { name: 'prerender' }, - config: cloudflareConfigCustomizer(), - }; - previewServer = await preview({ configFile: false, base, @@ -70,7 +66,7 @@ export function createCloudflarePrerenderer({ port: 0, // Let the OS pick a free port open: false, }, - plugins: [cfVitePlugin(cfPluginConfig)], + plugins: [cfVitePlugin({ ...cfPluginConfig, viteEnvironment: { name: 'prerender' } })], }); const address = previewServer.httpServer.address(); diff --git a/packages/integrations/cloudflare/src/wrangler.ts b/packages/integrations/cloudflare/src/wrangler.ts index fbc676af7b28..36898bf98d6c 100644 --- a/packages/integrations/cloudflare/src/wrangler.ts +++ b/packages/integrations/cloudflare/src/wrangler.ts @@ -5,8 +5,8 @@ export const DEFAULT_IMAGES_BINDING_NAME = 'IMAGES'; export const DEFAULT_ASSETS_BINDING_NAME = 'ASSETS'; interface CloudflareConfigOptions { - sessionKVBindingName?: string; - imagesBindingName?: string | false; + sessionKVBindingName: string | undefined; + imagesBindingName: string | false | undefined; } /** @@ -14,7 +14,7 @@ interface CloudflareConfigOptions { * Sets the main entrypoint and adds bindings for auto-provisioning. */ export function cloudflareConfigCustomizer( - options?: CloudflareConfigOptions, + options: CloudflareConfigOptions, ): PluginConfig['config'] { const sessionKVBindingName = options?.sessionKVBindingName ?? DEFAULT_SESSION_KV_BINDING_NAME; const imagesBindingName = diff --git a/packages/integrations/cloudflare/virtual.d.ts b/packages/integrations/cloudflare/virtual.d.ts index e48a4575f307..45c757c342d4 100644 --- a/packages/integrations/cloudflare/virtual.d.ts +++ b/packages/integrations/cloudflare/virtual.d.ts @@ -19,3 +19,5 @@ declare namespace Cloudflare { interface Env extends Cloudflare.Env {} type ImagesBinding = import('@cloudflare/workers-types').ImagesBinding; type Fetcher = import('@cloudflare/workers-types').Fetcher; + +declare var astroCloudflareOptions: import('@cloudflare/vite-plugin').PluginConfig;