summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.changeset/quick-items-jog.md6
-rw-r--r--packages/integrations/cloudflare/README.md10
-rw-r--r--packages/integrations/cloudflare/package.json3
-rw-r--r--packages/integrations/cloudflare/src/index.ts112
-rw-r--r--packages/integrations/cloudflare/src/server.advanced.ts7
-rw-r--r--packages/integrations/cloudflare/src/server.directory.ts7
-rw-r--r--pnpm-lock.yaml2
7 files changed, 127 insertions, 20 deletions
diff --git a/.changeset/quick-items-jog.md b/.changeset/quick-items-jog.md
new file mode 100644
index 000000000..fe35edd9b
--- /dev/null
+++ b/.changeset/quick-items-jog.md
@@ -0,0 +1,6 @@
+---
+'@astrojs/cloudflare': minor
+---
+
+Now building for Cloudflare directory mode takes advantage of the standard asset handling from Cloudflare Pages, and therefore does not call a function script to deliver static assets anymore.
+Also supports the use of `_routes.json`, `_redirects` and `_headers` files when placed into the `public` folder. \ No newline at end of file
diff --git a/packages/integrations/cloudflare/README.md b/packages/integrations/cloudflare/README.md
index 0a8012b1c..3acec5512 100644
--- a/packages/integrations/cloudflare/README.md
+++ b/packages/integrations/cloudflare/README.md
@@ -66,7 +66,7 @@ In order for preview to work you must install `wrangler`
$ pnpm install wrangler --save-dev
```
-It's then possible to update the preview script in your `package.json` to `"preview": "wrangler pages dev ./dist"`.This will allow you 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).
+It's then possible to update the preview script in your `package.json` to `"preview": "wrangler pages dev ./dist"`. This will allow you 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).
## Access to the Cloudflare runtime
@@ -107,6 +107,14 @@ export function get({ params }) {
}
```
+## 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 that lists all files from your `dist/` folder and redirects from the `_redirects` file in the `exclude` array. 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.
+
## Troubleshooting
For help, check out the `#support` channel on [Discord](https://astro.build/chat). Our friendly Support Squad members are here to help!
diff --git a/packages/integrations/cloudflare/package.json b/packages/integrations/cloudflare/package.json
index 82110c7ef..81db5b1ce 100644
--- a/packages/integrations/cloudflare/package.json
+++ b/packages/integrations/cloudflare/package.json
@@ -34,7 +34,8 @@
"test": "mocha --exit --timeout 30000 test/"
},
"dependencies": {
- "esbuild": "^0.14.42"
+ "esbuild": "^0.14.42",
+ "tiny-glob": "^0.2.9"
},
"peerDependencies": {
"astro": "^1.6.10"
diff --git a/packages/integrations/cloudflare/src/index.ts b/packages/integrations/cloudflare/src/index.ts
index 44112e8be..805eccb9c 100644
--- a/packages/integrations/cloudflare/src/index.ts
+++ b/packages/integrations/cloudflare/src/index.ts
@@ -1,6 +1,8 @@
import type { AstroAdapter, AstroConfig, AstroIntegration } from 'astro';
import esbuild from 'esbuild';
import * as fs from 'fs';
+import * as os from 'os';
+import glob from 'tiny-glob';
import { fileURLToPath } from 'url';
type Options = {
@@ -32,6 +34,8 @@ const SHIM = `globalThis.process = {
env: {},
};`;
+const SERVER_BUILD_FOLDER = '/$server_build/';
+
export default function createIntegration(args?: Options): AstroIntegration {
let _config: AstroConfig;
let _buildConfig: BuildConfig;
@@ -45,8 +49,8 @@ export default function createIntegration(args?: Options): AstroIntegration {
needsBuildConfig = !config.build.client;
updateConfig({
build: {
- client: new URL('./static/', config.outDir),
- server: new URL('./', config.outDir),
+ client: new URL(`.${config.base}`, config.outDir),
+ server: new URL(`.${SERVER_BUILD_FOLDER}`, config.outDir),
serverEntry: '_worker.js',
},
});
@@ -62,6 +66,11 @@ export default function createIntegration(args?: Options): AstroIntegration {
`);
}
+
+ if (config.base === SERVER_BUILD_FOLDER) {
+ throw new Error(`
+ [@astrojs/cloudflare] \`base: "${SERVER_BUILD_FOLDER}"\` is not allowed. Please change your \`base\` config to something else.`);
+ }
},
'astro:build:setup': ({ vite, target }) => {
if (target === 'server') {
@@ -84,19 +93,20 @@ export default function createIntegration(args?: Options): AstroIntegration {
'astro:build:start': ({ buildConfig }) => {
// Backwards compat
if (needsBuildConfig) {
- buildConfig.client = new URL('./static/', _config.outDir);
- buildConfig.server = new URL('./', _config.outDir);
+ buildConfig.client = new URL(`.${_config.base}`, _config.outDir);
+ buildConfig.server = new URL(`.${SERVER_BUILD_FOLDER}`, _config.outDir);
buildConfig.serverEntry = '_worker.js';
}
},
'astro:build:done': async () => {
- const entryUrl = new URL(_buildConfig.serverEntry, _buildConfig.server);
- const pkg = fileURLToPath(entryUrl);
+ const entryPath = fileURLToPath(new URL(_buildConfig.serverEntry, _buildConfig.server)),
+ entryUrl = new URL(_buildConfig.serverEntry, _config.outDir),
+ buildPath = fileURLToPath(entryUrl);
await esbuild.build({
target: 'es2020',
platform: 'browser',
- entryPoints: [pkg],
- outfile: pkg,
+ entryPoints: [entryPath],
+ outfile: buildPath,
allowOverwrite: true,
format: 'esm',
bundle: true,
@@ -107,8 +117,90 @@ export default function createIntegration(args?: Options): AstroIntegration {
});
// throw the server folder in the bin
- const chunksUrl = new URL('./chunks', _buildConfig.server);
- await fs.promises.rm(chunksUrl, { recursive: true, force: true });
+ const serverUrl = new URL(_buildConfig.server);
+ await fs.promises.rm(serverUrl, { recursive: true, force: true });
+
+ // move cloudflare specific files to the root
+ const cloudflareSpecialFiles = ['_headers', '_redirects', '_routes.json'];
+ if (_config.base !== '/') {
+ for (const file of cloudflareSpecialFiles) {
+ try {
+ await fs.promises.rename(
+ new URL(file, _buildConfig.client),
+ new URL(file, _config.outDir)
+ );
+ } catch (e) {
+ // ignore
+ }
+ }
+ }
+
+ const routesExists = await fs.promises
+ .stat(new URL('./_routes.json', _config.outDir))
+ .then((stat) => stat.isFile())
+ .catch(() => false);
+
+ // 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) {
+ const staticPathList: Array<string> = (
+ await glob(`${fileURLToPath(_buildConfig.client)}/**/*`, {
+ cwd: fileURLToPath(_config.outDir),
+ filesOnly: true,
+ })
+ )
+ .filter((file: string) => cloudflareSpecialFiles.indexOf(file) < 0)
+ .map((file: string) => `/${file}`);
+
+ const redirectsExists = await fs.promises
+ .stat(new URL('./_redirects', _config.outDir))
+ .then((stat) => stat.isFile())
+ .catch(() => false);
+
+ // convert all redirect source paths into a list of routes
+ // and add them to the static path
+ if (redirectsExists) {
+ const redirects = (
+ await fs.promises.readFile(new URL('./_redirects', _config.outDir), 'utf-8')
+ )
+ .split(os.EOL)
+ .map((line) => {
+ const parts = line.split(' ');
+ if (parts.length < 2) {
+ return null;
+ } else {
+ // convert /products/:id to /products/*
+ return (
+ parts[0]
+ .replace(/\/:.*?(?=\/|$)/g, '/*')
+ // remove query params as they are not supported by cloudflare
+ .replace(/\?.*$/, '')
+ );
+ }
+ })
+ .filter(
+ (line, index, arr) => line !== null && arr.indexOf(line) === index
+ ) as string[];
+
+ if (redirects.length > 0) {
+ staticPathList.push(...redirects);
+ }
+ }
+
+ await fs.promises.writeFile(
+ new URL('./_routes.json', _config.outDir),
+ JSON.stringify(
+ {
+ version: 1,
+ include: ['/*'],
+ exclude: staticPathList,
+ },
+ null,
+ 2
+ )
+ );
+ }
if (isModeDirectory) {
const functionsUrl = new URL(`file://${process.cwd()}/functions/`);
diff --git a/packages/integrations/cloudflare/src/server.advanced.ts b/packages/integrations/cloudflare/src/server.advanced.ts
index cb83dd994..0d765d0bb 100644
--- a/packages/integrations/cloudflare/src/server.advanced.ts
+++ b/packages/integrations/cloudflare/src/server.advanced.ts
@@ -15,12 +15,11 @@ export function createExports(manifest: SSRManifest) {
const fetch = async (request: Request, env: Env, context: any) => {
process.env = env as any;
- const { origin, pathname } = new URL(request.url);
+ const { pathname } = new URL(request.url);
- // static assets
+ // static assets fallback, in case default _routes.json is not used
if (manifest.assets.has(pathname)) {
- const assetRequest = new Request(`${origin}/static/${app.removeBase(pathname)}`, request);
- return env.ASSETS.fetch(assetRequest);
+ return env.ASSETS.fetch(request);
}
let routeData = app.match(request, { matchNotFound: true });
diff --git a/packages/integrations/cloudflare/src/server.directory.ts b/packages/integrations/cloudflare/src/server.directory.ts
index 321f37e18..69d008b0f 100644
--- a/packages/integrations/cloudflare/src/server.directory.ts
+++ b/packages/integrations/cloudflare/src/server.directory.ts
@@ -17,11 +17,10 @@ export function createExports(manifest: SSRManifest) {
} & Record<string, unknown>) => {
process.env = runtimeEnv.env as any;
- const { origin, pathname } = new URL(request.url);
- // static assets
+ const { pathname } = new URL(request.url);
+ // static assets fallback, in case default _routes.json is not used
if (manifest.assets.has(pathname)) {
- const assetRequest = new Request(`${origin}/static/${app.removeBase(pathname)}`, request);
- return next(assetRequest);
+ return next(request);
}
let routeData = app.match(request, { matchNotFound: true });
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7bf60c302..c6fcfa56c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -2554,9 +2554,11 @@ importers:
cheerio: ^1.0.0-rc.11
esbuild: ^0.14.42
mocha: ^9.2.2
+ tiny-glob: ^0.2.9
wrangler: ^2.0.23
dependencies:
esbuild: 0.14.54
+ tiny-glob: 0.2.9
devDependencies:
astro: link:../../astro
astro-scripts: link:../../../scripts