From bde732ead383357d1c1eb7f66139360c48622bd8 Mon Sep 17 00:00:00 2001 From: John Fawcett Date: Mon, 29 Aug 2022 10:22:29 -0500 Subject: [PATCH] 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. --- .changeset/afraid-gifts-act.md | 6 +++ packages/adapter-cloudflare/index.d.ts | 7 +++ packages/adapter-cloudflare/index.js | 68 +++++++++++++++++++++++--- 3 files changed, 74 insertions(+), 7 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..7971030a467a --- /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..c28e29ace7fa 100644 --- a/packages/adapter-cloudflare/index.js +++ b/packages/adapter-cloudflare/index.js @@ -1,4 +1,4 @@ -import { writeFileSync } from 'fs'; +import { writeFile } from 'fs/promises'; import { posix } from 'path'; import { fileURLToPath } from 'url'; import * as esbuild from 'esbuild'; @@ -23,18 +23,32 @@ export default function () { builder.rimraf(tmp); builder.mkdirp(tmp); - builder.writeClient(dest); + const written_files = builder.writeClient(dest); + console.log('written_files', written_files); builder.writePrerendered(dest); const relativePath = posix.relative(tmp, builder.getServerDirectory()); - writeFileSync( - `${tmp}/manifest.js`, - `export const manifest = ${builder.generateManifest({ - relativePath - })};\n\nexport const prerendered = new Set(${JSON.stringify(builder.prerendered.paths)});\n` + 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` ); + await Promise.all([ + writeFile( + `${tmp}/manifest.js`, + `export const manifest = ${builder.generateManifest({ + relativePath + })};\n\nexport const prerendered = new Set(${JSON.stringify( + builder.prerendered.paths + )});\n` + ), + writeFile( + `${dest}/_routes.json`, + JSON.stringify(getRoutesJSON(builder.config.kit.appDir, written_files)) + ), + writeFile(`${dest}/_headers`, getAutogeneratedHeaders(builder.config.kit.appDir)) + ]); + builder.copy(`${files}/worker.js`, `${tmp}/_worker.js`, { replace: { SERVER: `${relativePath}/index.js`, @@ -55,3 +69,43 @@ export default function () { } }; } + +/** + * @param {string} app_dir + * @param {string[]} assets + * @returns {import('.').RoutesJSONSpec} + */ +function getRoutesJSON(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 + * @returns {string} + */ +function getAutogeneratedHeaders(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(); +}