From ae8225d4e547c8bc07d82e516dd49315eddb56c3 Mon Sep 17 00:00:00 2001 From: John Fawcett Date: Wed, 21 Sep 2022 15:24:20 -0500 Subject: [PATCH] feat: Cloudflare Pages _routes.json specification (#6441) (#6530) * feat: Cloudflare Pages _routes.json specification (#6441) When a SvelteKit project is deployed to Cloudflare Pages, the server-side code is supported through Pages Functions (currently in beta). Unfortunately, by using Functions, each request must go through the server-side Functions code even if it doesn't need to. For instance, an immutable asset request to https://kit.svelte.dev/_app/immutable/assets/_layout-ab34ca4f.css would first have to route through Functions. This is problematic since the request would "count" as a request to Functions even though only a static asset was served. Further, there is a slight amount of added latency. This change exposes a set of includes and excludes based on static files generated. * use writeFileSync over writeFile, for consistency with rest of codebase * code style stuff * tweak changeset Co-authored-by: Rich Harris --- .changeset/afraid-gifts-act.md | 6 +++ packages/adapter-cloudflare/index.d.ts | 7 ++++ packages/adapter-cloudflare/index.js | 55 ++++++++++++++++++++++++-- 3 files changed, 64 insertions(+), 4 deletions(-) create mode 100644 .changeset/afraid-gifts-act.md diff --git a/.changeset/afraid-gifts-act.md b/.changeset/afraid-gifts-act.md new file mode 100644 index 000000000000..7f4a0c421484 --- /dev/null +++ b/.changeset/afraid-gifts-act.md @@ -0,0 +1,6 @@ +--- +'@sveltejs/adapter-cloudflare': patch +'@sveltejs/kit': patch +--- + +Support Cloudflare Pages `_routes.json` specification diff --git a/packages/adapter-cloudflare/index.d.ts b/packages/adapter-cloudflare/index.d.ts index ed89676dadc4..1e47e1627045 100644 --- a/packages/adapter-cloudflare/index.d.ts +++ b/packages/adapter-cloudflare/index.d.ts @@ -2,3 +2,10 @@ import { Adapter } from '@sveltejs/kit'; import './ambient.js'; export default function plugin(): Adapter; + +export interface RoutesJSONSpec { + version: 1; + description: string; + include: string[]; + exclude: string[]; +} diff --git a/packages/adapter-cloudflare/index.js b/packages/adapter-cloudflare/index.js index bfb9d968aa1f..5e6caf6a15ab 100644 --- a/packages/adapter-cloudflare/index.js +++ b/packages/adapter-cloudflare/index.js @@ -23,18 +23,28 @@ export default function () { builder.rimraf(tmp); builder.mkdirp(tmp); - builder.writeClient(dest); + const written_files = builder.writeClient(dest); builder.writePrerendered(dest); const relativePath = posix.relative(tmp, builder.getServerDirectory()); + builder.log.info( + `adapter-cloudfare is writing its own _headers file. If you have your own, you should duplicate the headers contained in: ${dest}/_headers` + ); + writeFileSync( `${tmp}/manifest.js`, - `export const manifest = ${builder.generateManifest({ - relativePath - })};\n\nexport const prerendered = new Set(${JSON.stringify(builder.prerendered.paths)});\n` + `export const manifest = ${builder.generateManifest({ relativePath })};\n\n` + + `export const prerendered = new Set(${JSON.stringify(builder.prerendered.paths)});\n` + ); + + writeFileSync( + `${dest}/_routes.json`, + JSON.stringify(get_routes_json(builder.config.kit.appDir, written_files)) ); + writeFileSync(`${dest}/_headers`, generate_headers(builder.config.kit.appDir)); + builder.copy(`${files}/worker.js`, `${tmp}/_worker.js`, { replace: { SERVER: `${relativePath}/index.js`, @@ -55,3 +65,40 @@ export default function () { } }; } + +/** + * @param {string} app_dir + * @param {string[]} assets + * @returns {import('.').RoutesJSONSpec} + */ +function get_routes_json(app_dir, assets) { + return { + version: 1, + description: 'Generated by @sveltejs/adapter-cloudflare', + include: ['/*'], + exclude: [ + `/${app_dir}/immutable/*`, + ...assets + // We're being conservative by not excluding all assets in + // /static just yet. If there are any upstream auth rules to + // protect certain things (e.g. a PDF that requires auth), + // then we wouldn't want to prevent those requests from going + // to the user functions worker. + // We do want to show an example of a _routes.json that + // excludes more than just /_app/immutable/*, and favicons + // are a reasonable choice + .filter((filePath) => filePath.includes('favicon')) + ] + }; +} + +/** @param {string} app_dir */ +function generate_headers(app_dir) { + return ` +# === START AUTOGENERATED SVELTE IMMUTABLE HEADERS === +/${app_dir}/immutable/* + Cache-Control: public, immutable, max-age=31536000 + X-Robots-Tag: noindex +# === END AUTOGENERATED SVELTE IMMUTABLE HEADERS === + `.trim(); +}