diff --git a/.changeset/afraid-gifts-act.md b/.changeset/afraid-gifts-act.md new file mode 100644 index 0000000000000..7971030a467aa --- /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 ed89676dadc4c..1e47e16270450 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 bfb9d968aa1f9..c28e29ace7faa 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(); +}