diff --git a/.changeset/olive-jeans-reply.md b/.changeset/olive-jeans-reply.md
new file mode 100644
index 000000000000..f719422261f4
--- /dev/null
+++ b/.changeset/olive-jeans-reply.md
@@ -0,0 +1,5 @@
+---
+'@astrojs/cloudflare': patch
+---
+
+Refactor codebase to enhance code readability and structure, to prioritize maintainability for long-term.
diff --git a/packages/integrations/cloudflare/README.md b/packages/integrations/cloudflare/README.md
index f968c97a3689..086459509a40 100644
--- a/packages/integrations/cloudflare/README.md
+++ b/packages/integrations/cloudflare/README.md
@@ -25,57 +25,56 @@ npm install @astrojs/cloudflare
2. Add the following to your `astro.config.mjs` file:
-```js ins={3, 6-7}
+```diff lang="js"
// astro.config.mjs
import { defineConfig } from 'astro/config';
-import cloudflare from '@astrojs/cloudflare';
++ import cloudflare from '@astrojs/cloudflare';
export default defineConfig({
- output: 'server',
- adapter: cloudflare(),
++ output: 'server',
++ adapter: cloudflare(),
});
```
## Options
-### Mode
+### `mode`
`mode: "advanced" | "directory"`
default `"advanced"`
-Cloudflare Pages has 2 different modes for deploying functions, `advanced` mode which picks up the `_worker.js` in `dist`, or a directory mode where pages will compile the worker out of a functions folder in the project root. For most projects the adapter default of `advanced` will be sufficient; the `dist` folder will contain your compiled project.
-
-#### `mode:directory`
+Cloudflare Pages has 2 different modes for deploying functions, `advanced` mode which picks up the `_worker.js` in the `dist` folder, and `directory` mode where pages will compile the worker out of a `functions` folder in the project root.
-Switching to directory mode allows you to use [pages plugins](https://developers.cloudflare.com/pages/platform/functions/plugins/) such as [Sentry](https://developers.cloudflare.com/pages/platform/functions/plugins/sentry/) or write custom code to enable logging.
+Switching to directory mode allows you to use [pages plugins](https://developers.cloudflare.com/pages/platform/functions/plugins/) or add custom functions manually.
-```ts
+```js
// astro.config.mjs
export default defineConfig({
adapter: cloudflare({ mode: 'directory' }),
});
```
-In `directory` mode, the adapter will compile the client-side part of your app the same way as in `advanced` mode by default, but moves the worker script into a `functions` folder in the project root. In this case, the adapter will only ever place a `[[path]].js` in that folder, allowing you to add additional plugins and pages middleware which can be checked into version control.
+In `directory` mode, the adapter will compile the client-side part of your app the same way as in `advanced` mode by default, but moves the worker script into the `functions` folder in the project root. In this case, the adapter will only ever place a `[[path]].js` in that folder, allowing you to add additional files manually such as plugins and pages middleware which can be checked into version control.
-To instead compile a separate bundle for each page, set the `functionPerPath` option in your Cloudflare adapter config. This option requires some manual maintenance of the `functions` folder. Files emitted by Astro will overwrite existing `functions` files with identical names, so you must choose unique file names for each file you manually add. Additionally, the adapter will never empty the `functions` folder of outdated files, so you must clean up the folder manually when you remove pages.
+To instead compile a separate bundle for each page, set the `functionPerRoute` option in your Cloudflare adapter config. This option requires some manual maintenance of the `functions` folder. Files emitted by Astro will overwrite existing `functions` files with identical names, so you must choose unique file names for each file you manually add. Additionally, the adapter will never empty the `functions` folder of outdated files, so you must clean up the folder manually when you remove pages.
-```diff
+```diff lang="js"
+// astro.config.mjs
import {defineConfig} from "astro/config";
import cloudflare from '@astrojs/cloudflare';
export default defineConfig({
adapter: cloudflare({
- mode: 'directory',
++ mode: 'directory',
+ functionPerRoute: true
})
})
```
-Note that this adapter does not support using [Cloudflare Pages Middleware](https://developers.cloudflare.com/pages/platform/functions/middleware/). Astro will bundle the [Astro middleware](https://docs.astro.build/en/guides/middleware/) into each page.
+This adapter doesn't support the [`edgeMiddleware`](https://docs.astro.build/en/reference/adapter-reference/#edgemiddleware) option.
-### routes.strategy
+### `routes.strategy`
`routes.strategy: "auto" | "include" | "exclude"`
@@ -129,16 +128,14 @@ There are three options available:
}
```
-### routes.include
-
+### `routes.include`
`routes.include: string[]`
default `[]`
If you want to use the automatic `_routes.json` generation, but want to include additional routes (e.g. when having custom functions in the `functions` folder), you can use the `routes.include` option to add additional routes to the `include` array.
-### routes.exclude
-
+### `routes.exclude`
`routes.exclude: string[]`
default `[]`
@@ -147,7 +144,7 @@ If you want to use the automatic `_routes.json` generation, but want to exclude
The following example automatically generates `_routes.json` while including and excluding additional routes. Note that that is only necessary if you have custom functions in the `functions` folder that are not handled by Astro.
-```diff
+```diff lang="js"
// astro.config.mjs
export default defineConfig({
adapter: cloudflare({
@@ -155,33 +152,75 @@ export default defineConfig({
+ routes: {
+ strategy: 'include',
+ include: ['/users/*'], // handled by custom function: functions/users/[id].js
-+ exclude: ['/users/faq'], // handled by static page: pages/users/faq.astro
+ exclude: ['/users/faq'], // handled by static page: pages/users/faq.astro
+ },
}),
});
```
-## Enabling Preview
+### `wasmModuleImports`
-In order for preview to work you must install `wrangler`
+`wasmModuleImports: boolean`
-```sh
-pnpm install wrangler --save-dev
+default: `false`
+
+Whether or not to import `.wasm` files [directly as ES modules](https://github.com/WebAssembly/esm-integration/tree/main/proposals/esm-integration) using the `.wasm?module` import syntax.
+
+Add `wasmModuleImports: true` to `astro.config.mjs` to enable this functionality in both the Cloudflare build and the Astro dev server. [Read more](#use-wasm-modules)
+
+```diff lang="js"
+// astro.config.mjs
+import {defineConfig} from "astro/config";
+import cloudflare from '@astrojs/cloudflare';
+
+export default defineConfig({
+ adapter: cloudflare({
++ wasmModuleImports: true
+ }),
+ output: 'server'
+})
```
-It's then possible to update the preview script in your `package.json` to `"preview": "wrangler pages dev ./dist"`. This will allow you to run your entire application locally with [Wrangler](https://github.com/cloudflare/wrangler2), which supports secrets, environment variables, KV namespaces, Durable Objects and [all other supported Cloudflare bindings](https://developers.cloudflare.com/pages/platform/functions/#adding-bindings).
+### `runtime`
-## Access to the Cloudflare runtime
+`runtime: "off" | "local" | "remote"`
-You can access all the Cloudflare bindings and environment variables from Astro components and API routes through `Astro.locals`.
+default `"off"`
+
+Determines whether and how the Cloudflare Runtime is added to `astro dev`.
+
+The Cloudflare Runtime includes [Cloudflare bindings](https://developers.cloudflare.com/pages/platform/functions/bindings), [environment variables](https://developers.cloudflare.com/pages/platform/functions/bindings/#environment-variables), and the [cf object](https://developers.cloudflare.com/workers/runtime-apis/request/#incomingrequestcfproperties). Read more about [accessing the Cloudflare Runtime](#access-to-the-cloudflare-runtime).
-If you're inside an `.astro` file, you access the runtime using the `Astro.locals` global:
+- `local`: uses bindings mocking and locally static placeholdes
+- `remote`: uses remote bindings and a live fetched cf object
+- `off`: no access to the Cloudflare runtime using `astro dev`. You can alternatively use [Preview with Wrangler](#preview-with-wrangler)
+
+```diff lang="js"
+// astro.config.mjs
+import { defineConfig } from 'astro/config';
+import cloudflare from '@astrojs/cloudflare';
+
+export default defineConfig({
+ output: 'server',
+ adapter: cloudflare({
++ runtime: 'local',
+ }),
+});
+```
+
+## Cloudflare runtime
+
+You can access the runtime from Astro components through `Astro.locals` inside any .astro` file.
```astro
-const env = Astro.locals.runtime.env;
+---
+// src/pages/index.astro
+const runtime = Astro.locals.runtime;
+---
+
{JSON.stringify(runtime.env)}
```
-From an endpoint:
+You can access the runtime from API endpoints through `context.locals`:
```js
// src/pages/api/someFile.js
@@ -192,21 +231,24 @@ export function GET(context) {
}
```
-Depending on your adapter mode (advanced = worker, directory = pages), the runtime object will look a little different due to differences in the Cloudflare API.
+### Typing
-If you're using the `advanced` runtime, you can type the `runtime` object as following:
+If you have configured `mode: advanced`, you can type the `runtime` object using `AdvancedRuntime`:
```ts
// src/env.d.ts
///
-import type { AdvancedRuntime } from '@astrojs/cloudflare';
+type KVNamespace = import('@cloudflare/workers-types/experimental').KVNamespace;
type ENV = {
SERVER_URL: string;
+ KV_BINDING: KVNamespace;
};
+type Runtime = import('@astrojs/cloudflare').AdvancedRuntime;
+
declare namespace App {
- interface Locals extends AdvancedRuntime {
+ interface Locals extends Runtime {
user: {
name: string;
surname: string;
@@ -215,19 +257,22 @@ declare namespace App {
}
```
-If you're using the `directory` runtime, you can type the `runtime` object as following:
+If you have configured `mode: directory`, you can type the `runtime` object using `DirectoryRuntime`:
```ts
// src/env.d.ts
///
-import type { DirectoryRuntime } from '@astrojs/cloudflare';
+type KVNamespace = import('@cloudflare/workers-types/experimental').KVNamespace;
type ENV = {
SERVER_URL: string;
+ KV_BINDING: KVNamespace;
};
+type Runtime = import('@astrojs/cloudflare').DirectoryRuntime;
+
declare namespace App {
- interface Locals extends DirectoryRuntime {
+ interface Locals extends Runtime {
user: {
name: string;
surname: string;
@@ -236,71 +281,26 @@ declare namespace App {
}
```
-### Environment Variables
-
-See Cloudflare's documentation for [working with environment variables](https://developers.cloudflare.com/pages/platform/functions/bindings/#environment-variables).
-
-```js
-// pages/[id].json.js
-
-export function GET({ params }) {
- // Access environment variables per request inside a function
- const serverUrl = import.meta.env.SERVER_URL;
- const result = await fetch(serverUrl + "/user/" + params.id);
- return {
- body: await result.text(),
- };
-}
-```
-
-### `cloudflare.runtime`
-
-`runtime: "off" | "local" | "remote"`
-default `"off"`
-
-This optional flag enables the Astro dev server to populate environment variables and the Cloudflare Request Object, avoiding the need for Wrangler.
-
-- `local`: environment variables are available, but the request object is populated from a static placeholder value.
-- `remote`: environment variables and the live, fetched request object are available.
-- `off`: the Astro dev server will populate neither environment variables nor the request object. Use Wrangler to access Cloudflare bindings and environment variables.
+## Platform
-```js
-// astro.config.mjs
-import { defineConfig } from 'astro/config';
-import cloudflare from '@astrojs/cloudflare';
-
-export default defineConfig({
- output: 'server',
- adapter: cloudflare({
- runtime: 'off' | 'local' | 'remote',
- }),
-});
-```
+### Headers
-## Wasm module imports
+You can attach [custom headers](https://developers.cloudflare.com/pages/platform/headers/) to your responses by adding a `_headers` file in your Astro project's `public/` folder. This file will be copied to your build output directory.
-`wasmModuleImports: boolean`
+### Redirects
-default: `false`
+You can declare [custom redirects](https://developers.cloudflare.com/pages/platform/redirects/) using Cloudflare Pages. This allows you to redirect requests to a different URL. You can add a `_redirects` file in your Astro project's `public/` folder. This file will be copied to your build output directory.
-Whether or not to import `.wasm` files [directly as ES modules](https://github.com/WebAssembly/esm-integration/tree/main/proposals/esm-integration).
+### Routes
-Add `wasmModuleImports: true` to `astro.config.mjs` to enable in both the Cloudflare build and the Astro dev server.
+You can define which routes are invoking functions and which are static assets, using [Cloudflare routing](https://developers.cloudflare.com/pages/platform/functions/routing/#functions-invocation-routes) via a `_routes.json` file. This file is automatically generated by Astro.
-```diff
-// astro.config.mjs
-import {defineConfig} from "astro/config";
-import cloudflare from '@astrojs/cloudflare';
+#### Custom `_routes.json`
-export default defineConfig({
- adapter: cloudflare({
-+ wasmModuleImports: true
- }),
- output: 'server'
-})
-```
+By default, `@astrojs/cloudflare` will generate a `_routes.json` file with `include` and `exclude` rules based on your applications's dynamic and static routes.
+This will enable Cloudflare to serve files and process static redirects without a function invocation. Creating a custom `_routes.json` will override this automatic optimization. See [Cloudflare's documentation on creating a custom `routes.json`](https://developers.cloudflare.com/pages/platform/functions/routing/#create-a-_routesjson-file) for more details.
-Once enabled, you can import a web assembly module in Astro with a `.wasm?module` import.
+## Use Wasm modules
The following is an example of importing a Wasm module that then responds to requests by adding the request's number parameters together.
@@ -320,17 +320,6 @@ export async function GET(context) {
While this example is trivial, Wasm can be used to accelerate computationally intensive operations which do not involve significant I/O such as embedding an image processing library.
-## Headers, Redirects and function invocation routes
-
-Cloudflare has support for adding custom [headers](https://developers.cloudflare.com/pages/platform/headers/), configuring static [redirects](https://developers.cloudflare.com/pages/platform/redirects/) and defining which routes should [invoke functions](https://developers.cloudflare.com/pages/platform/functions/routing/#function-invocation-routes). Cloudflare looks for `_headers`, `_redirects`, and `_routes.json` files in your build output directory to configure these features. This means they should be placed in your Astro project’s `public/` directory.
-
-### Custom `_routes.json`
-
-By default, `@astrojs/cloudflare` will generate a `_routes.json` file with `include` and `exclude` rules based on your applications's dynamic and static routes.
-This will enable Cloudflare to serve files and process static redirects without a function invocation. Creating a custom `_routes.json` will override this automatic optimization and, if not configured manually, cause function invocations that will count against the request limits of your Cloudflare plan.
-
-See [Cloudflare's documentation](https://developers.cloudflare.com/pages/platform/functions/routing/#create-a-_routesjson-file) for more details.
-
## Node.js compatibility
Astro's Cloudflare adapter allows you to use any Node.js runtime API supported by Cloudflare:
@@ -354,32 +343,24 @@ export const prerender = false;
import { Buffer } from 'node:buffer';
```
-Additionally, you'll need to enable the Compatibility Flag in Cloudflare. The configuration for this flag may vary based on where you deploy your Astro site.
+Additionally, you'll need to enable the Compatibility Flag in Cloudflare. The configuration for this flag may vary based on where you deploy your Astro site. For detailed guidance, please refer to the [Cloudflare documentation on enabling Node.js compatibility](https://developers.cloudflare.com/workers/runtime-apis/nodejs).
-For detailed guidance, please refer to the [Cloudflare documentation](https://developers.cloudflare.com/workers/runtime-apis/nodejs).
+## Preview with Wrangler
-## Troubleshooting
+To use [`wrangler`](https://developers.cloudflare.com/workers/wrangler/) to run your application locally, update the preview script:
-For help, check out the `#support` channel on [Discord](https://astro.build/chat). Our friendly Support Squad members are here to help!
-
-You can also check our [Astro Integration Documentation][astro-integration] for more on integrations.
+```json
+//package.json
+"preview": "wrangler pages dev ./dist"
+```
-### Meaningful error messages
+[`wrangler`](https://developers.cloudflare.com/workers/wrangler/) gives you access to [Cloudflare bindings](https://developers.cloudflare.com/pages/platform/functions/bindings), [environment variables](https://developers.cloudflare.com/pages/platform/functions/bindings/#environment-variables), and the [cf object](https://developers.cloudflare.com/workers/runtime-apis/request/#incomingrequestcfproperties). Getting hot reloading or the astro dev server to work with Wrangler might require custom setup. See [community examples](https://github.com/withastro/roadmap/discussions/590).
-Currently, errors during running your application in Wrangler are not very useful, due to the minification of your code. For better debugging, you can add `vite.build.minify = false` setting to your `astro.config.js`
+## Troubleshooting
-```js
-export default defineConfig({
- adapter: cloudflare(),
- output: 'server',
+For help, check out the `#support` channel on [Discord](https://astro.build/chat). Our friendly Support Squad members are here to help!
- vite: {
- build: {
- minify: false,
- },
- },
-});
-```
+You can also check our [Astro Integration Documentation][astro-integration] for more on integrations.
## Contributing
diff --git a/packages/integrations/cloudflare/package.json b/packages/integrations/cloudflare/package.json
index cc460ff6496b..88ff252d254d 100644
--- a/packages/integrations/cloudflare/package.json
+++ b/packages/integrations/cloudflare/package.json
@@ -19,17 +19,12 @@
"homepage": "https://docs.astro.build/en/guides/integrations-guide/cloudflare/",
"exports": {
".": "./dist/index.js",
- "./runtime": {
- "types": "./runtime.d.ts",
- "default": "./dist/runtime.js"
- },
- "./server.advanced.js": "./dist/server.advanced.js",
- "./server.directory.js": "./dist/server.directory.js",
+ "./entrypoints/server.advanced.js": "./dist/entrypoints/server.advanced.js",
+ "./entrypoints/server.directory.js": "./dist/entrypoints/server.directory.js",
"./package.json": "./package.json"
},
"files": [
- "dist",
- "runtime.d.ts"
+ "dist"
],
"scripts": {
"build": "astro-scripts build \"src/**/*.ts\" && tsc",
diff --git a/packages/integrations/cloudflare/src/server.advanced.ts b/packages/integrations/cloudflare/src/entrypoints/server.advanced.ts
similarity index 97%
rename from packages/integrations/cloudflare/src/server.advanced.ts
rename to packages/integrations/cloudflare/src/entrypoints/server.advanced.ts
index ac6e0fe5547c..957c1791d107 100644
--- a/packages/integrations/cloudflare/src/server.advanced.ts
+++ b/packages/integrations/cloudflare/src/entrypoints/server.advanced.ts
@@ -1,7 +1,7 @@
import type { Request as CFRequest, ExecutionContext } from '@cloudflare/workers-types';
import type { SSRManifest } from 'astro';
import { App } from 'astro/app';
-import { getProcessEnvProxy, isNode } from './util.js';
+import { getProcessEnvProxy, isNode } from '../util.js';
if (!isNode) {
process.env = getProcessEnvProxy();
diff --git a/packages/integrations/cloudflare/src/server.directory.ts b/packages/integrations/cloudflare/src/entrypoints/server.directory.ts
similarity index 97%
rename from packages/integrations/cloudflare/src/server.directory.ts
rename to packages/integrations/cloudflare/src/entrypoints/server.directory.ts
index ffd4ba87a696..3542279b0490 100644
--- a/packages/integrations/cloudflare/src/server.directory.ts
+++ b/packages/integrations/cloudflare/src/entrypoints/server.directory.ts
@@ -1,7 +1,7 @@
import type { Request as CFRequest, EventContext } from '@cloudflare/workers-types';
import type { SSRManifest } from 'astro';
import { App } from 'astro/app';
-import { getProcessEnvProxy, isNode } from './util.js';
+import { getProcessEnvProxy, isNode } from '../util.js';
if (!isNode) {
process.env = getProcessEnvProxy();
diff --git a/packages/integrations/cloudflare/src/getAdapter.ts b/packages/integrations/cloudflare/src/getAdapter.ts
new file mode 100644
index 000000000000..0cc1263a17f1
--- /dev/null
+++ b/packages/integrations/cloudflare/src/getAdapter.ts
@@ -0,0 +1,40 @@
+import type { AstroAdapter, AstroFeatureMap } from 'astro';
+
+export function getAdapter({
+ isModeDirectory,
+ functionPerRoute,
+}: {
+ isModeDirectory: boolean;
+ functionPerRoute: boolean;
+}): AstroAdapter {
+ const astroFeatures = {
+ hybridOutput: 'stable',
+ staticOutput: 'unsupported',
+ serverOutput: 'stable',
+ assets: {
+ supportKind: 'stable',
+ isSharpCompatible: false,
+ isSquooshCompatible: false,
+ },
+ } satisfies AstroFeatureMap;
+
+ if (isModeDirectory) {
+ return {
+ name: '@astrojs/cloudflare',
+ serverEntrypoint: '@astrojs/cloudflare/entrypoints/server.directory.js',
+ exports: ['onRequest', 'manifest'],
+ adapterFeatures: {
+ functionPerRoute,
+ edgeMiddleware: false,
+ },
+ supportedAstroFeatures: astroFeatures,
+ };
+ }
+
+ return {
+ name: '@astrojs/cloudflare',
+ serverEntrypoint: '@astrojs/cloudflare/entrypoints/server.advanced.js',
+ exports: ['default'],
+ supportedAstroFeatures: astroFeatures,
+ };
+}
diff --git a/packages/integrations/cloudflare/src/index.ts b/packages/integrations/cloudflare/src/index.ts
index fa2ea3198645..12ff00a54b04 100644
--- a/packages/integrations/cloudflare/src/index.ts
+++ b/packages/integrations/cloudflare/src/index.ts
@@ -1,5 +1,4 @@
-import type { IncomingRequestCfProperties } from '@cloudflare/workers-types/experimental';
-import type { AstroAdapter, AstroConfig, AstroIntegration, RouteData } from 'astro';
+import type { AstroConfig, AstroIntegration, RouteData } from 'astro';
import { createRedirectsFromAstroRoutes } from '@astrojs/underscore-redirects';
import { CacheStorage } from '@miniflare/cache';
@@ -9,14 +8,19 @@ import { AstroError } from 'astro/errors';
import esbuild from 'esbuild';
import * as fs from 'node:fs';
import * as os from 'node:os';
-import { basename, dirname, relative, sep } from 'node:path';
+import { dirname, relative, sep } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import glob from 'tiny-glob';
-import { getEnvVars } from './parser.js';
-import { wasmModuleLoader } from './wasm-module-loader.js';
+import { getAdapter } from './getAdapter.js';
+import { deduplicatePatterns } from './utils/deduplicatePatterns.js';
+import { getCFObject } from './utils/getCFObject.js';
+import { getEnvVars } from './utils/parser.js';
+import { prependForwardSlash } from './utils/prependForwardSlash.js';
+import { rewriteWasmImportPath } from './utils/rewriteWasmImportPath.js';
+import { wasmModuleLoader } from './utils/wasm-module-loader.js';
-export type { AdvancedRuntime } from './server.advanced.js';
-export type { DirectoryRuntime } from './server.directory.js';
+export type { AdvancedRuntime } from './entrypoints/server.advanced.js';
+export type { DirectoryRuntime } from './entrypoints/server.directory.js';
type Options = {
mode?: 'directory' | 'advanced';
@@ -62,134 +66,13 @@ class StorageFactory {
}
}
-export function getAdapter({
- isModeDirectory,
- functionPerRoute,
-}: {
- isModeDirectory: boolean;
- functionPerRoute: boolean;
-}): AstroAdapter {
- return isModeDirectory
- ? {
- name: '@astrojs/cloudflare',
- serverEntrypoint: '@astrojs/cloudflare/server.directory.js',
- exports: ['onRequest', 'manifest'],
- adapterFeatures: {
- functionPerRoute,
- edgeMiddleware: false,
- },
- supportedAstroFeatures: {
- hybridOutput: 'stable',
- staticOutput: 'unsupported',
- serverOutput: 'stable',
- assets: {
- supportKind: 'stable',
- isSharpCompatible: false,
- isSquooshCompatible: false,
- },
- },
- }
- : {
- name: '@astrojs/cloudflare',
- serverEntrypoint: '@astrojs/cloudflare/server.advanced.js',
- exports: ['default'],
- supportedAstroFeatures: {
- hybridOutput: 'stable',
- staticOutput: 'unsupported',
- serverOutput: 'stable',
- assets: {
- supportKind: 'stable',
- isSharpCompatible: false,
- isSquooshCompatible: false,
- },
- },
- };
-}
-
-async function getCFObject(runtimeMode: string): Promise {
- const CF_ENDPOINT = 'https://workers.cloudflare.com/cf.json';
- const CF_FALLBACK: IncomingRequestCfProperties = {
- asOrganization: '',
- asn: 395747,
- colo: 'DFW',
- city: 'Austin',
- region: 'Texas',
- regionCode: 'TX',
- metroCode: '635',
- postalCode: '78701',
- country: 'US',
- continent: 'NA',
- timezone: 'America/Chicago',
- latitude: '30.27130',
- longitude: '-97.74260',
- clientTcpRtt: 0,
- httpProtocol: 'HTTP/1.1',
- requestPriority: 'weight=192;exclusive=0',
- tlsCipher: 'AEAD-AES128-GCM-SHA256',
- tlsVersion: 'TLSv1.3',
- tlsClientAuth: {
- certPresented: '0',
- certVerified: 'NONE',
- certRevoked: '0',
- certIssuerDN: '',
- certSubjectDN: '',
- certIssuerDNRFC2253: '',
- certSubjectDNRFC2253: '',
- certIssuerDNLegacy: '',
- certSubjectDNLegacy: '',
- certSerial: '',
- certIssuerSerial: '',
- certSKI: '',
- certIssuerSKI: '',
- certFingerprintSHA1: '',
- certFingerprintSHA256: '',
- certNotBefore: '',
- certNotAfter: '',
- },
- edgeRequestKeepAliveStatus: 0,
- hostMetadata: undefined,
- clientTrustScore: 99,
- botManagement: {
- corporateProxy: false,
- verifiedBot: false,
- ja3Hash: '25b4882c2bcb50cd6b469ff28c596742',
- staticResource: false,
- detectionIds: [],
- score: 99,
- },
- };
-
- if (runtimeMode === 'local') {
- return CF_FALLBACK;
- } else if (runtimeMode === 'remote') {
- try {
- const res = await fetch(CF_ENDPOINT);
- const cfText = await res.text();
- const storedCf = JSON.parse(cfText);
- return storedCf;
- } catch (e: any) {
- return CF_FALLBACK;
- }
- }
-}
-
-const SHIM = `globalThis.process = {
- argv: [],
- env: {},
-};`;
-
-const SERVER_BUILD_FOLDER = '/$server_build/';
-
-/**
- * These route types are candiates for being part of the `_routes.json` `include` array.
- */
-const potentialFunctionRouteTypes = ['endpoint', 'page'];
-
export default function createIntegration(args?: Options): AstroIntegration {
let _config: AstroConfig;
let _buildConfig: BuildConfig;
let _entryPoints = new Map();
+ const SERVER_BUILD_FOLDER = '/$server_build/';
+
const isModeDirectory = args?.mode === 'directory';
const functionPerRoute = args?.functionPerRoute ?? false;
const runtimeMode = args?.runtime ?? 'off';
@@ -221,13 +104,13 @@ export default function createIntegration(args?: Options): AstroIntegration {
_config = config;
_buildConfig = config.build;
- if (config.output === 'static') {
+ if (_config.output === 'static') {
throw new AstroError(
'[@astrojs/cloudflare] `output: "server"` or `output: "hybrid"` is required to use this adapter. Otherwise, this adapter is not necessary to deploy a static site to Cloudflare.'
);
}
- if (config.base === SERVER_BUILD_FOLDER) {
+ if (_config.base === SERVER_BUILD_FOLDER) {
throw new AstroError(
'[@astrojs/cloudflare] `base: "${SERVER_BUILD_FOLDER}"` is not allowed. Please change your `base` config to something else.'
);
@@ -372,7 +255,10 @@ export default function createIntegration(args?: Options): AstroIntegration {
bundle: true,
minify: _config.vite?.build?.minify !== false,
banner: {
- js: SHIM,
+ js: `globalThis.process = {
+ argv: [],
+ env: {},
+ };`,
},
logOverride: {
'ignored-bare-import': 'silent',
@@ -449,7 +335,10 @@ export default function createIntegration(args?: Options): AstroIntegration {
bundle: true,
minify: _config.vite?.build?.minify !== false,
banner: {
- js: SHIM,
+ js: `globalThis.process = {
+ argv: [],
+ env: {},
+ };`,
},
logOverride: {
'ignored-bare-import': 'silent',
@@ -506,10 +395,14 @@ export default function createIntegration(args?: Options): AstroIntegration {
// this creates a _routes.json, in case there is none present to enable
// cloudflare to handle static files and support _redirects configuration
- // (without calling the function)
if (!routesExists) {
+ /**
+ * These route types are candiates for being part of the `_routes.json` `include` array.
+ */
+ const potentialFunctionRouteTypes = ['endpoint', 'page'];
+
const functionEndpoints = routes
- // Certain route types, when their prerender option is set to false, a run on the server as function invocations
+ // Certain route types, when their prerender option is set to false, run on the server as function invocations
.filter((route) => potentialFunctionRouteTypes.includes(route.type) && !route.prerender)
.map((route) => {
const includePattern =
@@ -672,59 +565,3 @@ export default function createIntegration(args?: Options): AstroIntegration {
},
};
}
-
-function prependForwardSlash(path: string) {
- return path[0] === '/' ? path : '/' + path;
-}
-
-/**
- * Remove duplicates and redundant patterns from an `include` or `exclude` list.
- * Otherwise Cloudflare will throw an error on deployment. Plus, it saves more entries.
- * E.g. `['/foo/*', '/foo/*', '/foo/bar'] => ['/foo/*']`
- * @param patterns a list of `include` or `exclude` patterns
- * @returns a deduplicated list of patterns
- */
-function deduplicatePatterns(patterns: string[]) {
- const openPatterns: RegExp[] = [];
-
- return [...new Set(patterns)]
- .sort((a, b) => a.length - b.length)
- .filter((pattern) => {
- if (openPatterns.some((p) => p.test(pattern))) {
- return false;
- }
-
- if (pattern.endsWith('*')) {
- openPatterns.push(new RegExp(`^${pattern.replace(/(\*\/)*\*$/g, '.*')}`));
- }
-
- return true;
- });
-}
-
-/**
- *
- * @param relativePathToAssets - relative path from the final location for the current esbuild output bundle, to the assets directory.
- */
-function rewriteWasmImportPath({
- relativePathToAssets,
-}: {
- relativePathToAssets: string;
-}): esbuild.Plugin {
- return {
- name: 'wasm-loader',
- setup(build) {
- build.onResolve({ filter: /.*\.wasm.mjs$/ }, (args) => {
- const updatedPath = [
- relativePathToAssets.replaceAll('\\', '/'),
- basename(args.path).replace(/\.mjs$/, ''),
- ].join('/');
-
- return {
- path: updatedPath, // change the reference to the changed module
- external: true, // mark it as external in the bundle
- };
- });
- },
- };
-}
diff --git a/packages/integrations/cloudflare/src/utils/deduplicatePatterns.ts b/packages/integrations/cloudflare/src/utils/deduplicatePatterns.ts
new file mode 100644
index 000000000000..37743fe55305
--- /dev/null
+++ b/packages/integrations/cloudflare/src/utils/deduplicatePatterns.ts
@@ -0,0 +1,26 @@
+/**
+ * Remove duplicates and redundant patterns from an `include` or `exclude` list.
+ * Otherwise Cloudflare will throw an error on deployment. Plus, it saves more entries.
+ * E.g. `['/foo/*', '/foo/*', '/foo/bar'] => ['/foo/*']`
+ * @param patterns a list of `include` or `exclude` patterns
+ * @returns a deduplicated list of patterns
+ */
+export function deduplicatePatterns(patterns: string[]) {
+ const openPatterns: RegExp[] = [];
+
+ // A value in the set may only occur once; it is unique in the set's collection.
+ // ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
+ return [...new Set(patterns)]
+ .sort((a, b) => a.length - b.length)
+ .filter((pattern) => {
+ if (openPatterns.some((p) => p.test(pattern))) {
+ return false;
+ }
+
+ if (pattern.endsWith('*')) {
+ openPatterns.push(new RegExp(`^${pattern.replace(/(\*\/)*\*$/g, '.*')}`));
+ }
+
+ return true;
+ });
+}
diff --git a/packages/integrations/cloudflare/src/utils/getCFObject.ts b/packages/integrations/cloudflare/src/utils/getCFObject.ts
new file mode 100644
index 000000000000..7a4cd8a0cab7
--- /dev/null
+++ b/packages/integrations/cloudflare/src/utils/getCFObject.ts
@@ -0,0 +1,70 @@
+import type { IncomingRequestCfProperties } from '@cloudflare/workers-types/experimental';
+
+export async function getCFObject(
+ runtimeMode: string
+): Promise {
+ const CF_ENDPOINT = 'https://workers.cloudflare.com/cf.json';
+ const CF_FALLBACK: IncomingRequestCfProperties = {
+ asOrganization: '',
+ asn: 395747,
+ colo: 'DFW',
+ city: 'Austin',
+ region: 'Texas',
+ regionCode: 'TX',
+ metroCode: '635',
+ postalCode: '78701',
+ country: 'US',
+ continent: 'NA',
+ timezone: 'America/Chicago',
+ latitude: '30.27130',
+ longitude: '-97.74260',
+ clientTcpRtt: 0,
+ httpProtocol: 'HTTP/1.1',
+ requestPriority: 'weight=192;exclusive=0',
+ tlsCipher: 'AEAD-AES128-GCM-SHA256',
+ tlsVersion: 'TLSv1.3',
+ tlsClientAuth: {
+ certPresented: '0',
+ certVerified: 'NONE',
+ certRevoked: '0',
+ certIssuerDN: '',
+ certSubjectDN: '',
+ certIssuerDNRFC2253: '',
+ certSubjectDNRFC2253: '',
+ certIssuerDNLegacy: '',
+ certSubjectDNLegacy: '',
+ certSerial: '',
+ certIssuerSerial: '',
+ certSKI: '',
+ certIssuerSKI: '',
+ certFingerprintSHA1: '',
+ certFingerprintSHA256: '',
+ certNotBefore: '',
+ certNotAfter: '',
+ },
+ edgeRequestKeepAliveStatus: 0,
+ hostMetadata: undefined,
+ clientTrustScore: 99,
+ botManagement: {
+ corporateProxy: false,
+ verifiedBot: false,
+ ja3Hash: '25b4882c2bcb50cd6b469ff28c596742',
+ staticResource: false,
+ detectionIds: [],
+ score: 99,
+ },
+ };
+
+ if (runtimeMode === 'local') {
+ return CF_FALLBACK;
+ } else if (runtimeMode === 'remote') {
+ try {
+ const res = await fetch(CF_ENDPOINT);
+ const cfText = await res.text();
+ const storedCf = JSON.parse(cfText);
+ return storedCf;
+ } catch (e: any) {
+ return CF_FALLBACK;
+ }
+ }
+}
diff --git a/packages/integrations/cloudflare/src/parser.ts b/packages/integrations/cloudflare/src/utils/parser.ts
similarity index 100%
rename from packages/integrations/cloudflare/src/parser.ts
rename to packages/integrations/cloudflare/src/utils/parser.ts
diff --git a/packages/integrations/cloudflare/src/utils/prependForwardSlash.ts b/packages/integrations/cloudflare/src/utils/prependForwardSlash.ts
new file mode 100644
index 000000000000..b66b588f384b
--- /dev/null
+++ b/packages/integrations/cloudflare/src/utils/prependForwardSlash.ts
@@ -0,0 +1,3 @@
+export function prependForwardSlash(path: string) {
+ return path[0] === '/' ? path : '/' + path;
+}
diff --git a/packages/integrations/cloudflare/src/utils/rewriteWasmImportPath.ts b/packages/integrations/cloudflare/src/utils/rewriteWasmImportPath.ts
new file mode 100644
index 000000000000..ada19bb56be2
--- /dev/null
+++ b/packages/integrations/cloudflare/src/utils/rewriteWasmImportPath.ts
@@ -0,0 +1,29 @@
+import esbuild from 'esbuild';
+import { basename } from 'node:path';
+
+/**
+ *
+ * @param relativePathToAssets - relative path from the final location for the current esbuild output bundle, to the assets directory.
+ */
+export function rewriteWasmImportPath({
+ relativePathToAssets,
+}: {
+ relativePathToAssets: string;
+}): esbuild.Plugin {
+ return {
+ name: 'wasm-loader',
+ setup(build) {
+ build.onResolve({ filter: /.*\.wasm.mjs$/ }, (args) => {
+ const updatedPath = [
+ relativePathToAssets.replaceAll('\\', '/'),
+ basename(args.path).replace(/\.mjs$/, ''),
+ ].join('/');
+
+ return {
+ path: updatedPath,
+ external: true, // mark it as external in the bundle
+ };
+ });
+ },
+ };
+}
diff --git a/packages/integrations/cloudflare/src/wasm-module-loader.ts b/packages/integrations/cloudflare/src/utils/wasm-module-loader.ts
similarity index 100%
rename from packages/integrations/cloudflare/src/wasm-module-loader.ts
rename to packages/integrations/cloudflare/src/utils/wasm-module-loader.ts