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..1c02f125603d0 100644 --- a/packages/adapter-cloudflare/index.d.ts +++ b/packages/adapter-cloudflare/index.d.ts @@ -2,3 +2,9 @@ import { Adapter } from '@sveltejs/kit'; import './ambient.js'; export default function plugin(): Adapter; + +export interface RoutesJSONSpec { + version: 1; + include: string[]; + exclude: string[]; +} diff --git a/packages/adapter-cloudflare/index.js b/packages/adapter-cloudflare/index.js index bfb9d968aa1f9..9e8de4f617296 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'; @@ -15,6 +15,7 @@ export default function () { return { name: '@sveltejs/adapter-cloudflare', async adapt(builder) { + const build_data = builder.getBuildData(); const files = fileURLToPath(new URL('./files', import.meta.url).href); const dest = builder.getBuildDirectory('cloudflare'); const tmp = builder.getBuildDirectory('cloudflare-tmp'); @@ -28,13 +29,23 @@ export default function () { 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(getRoutesJSONFromBuildData(build_data))), + writeFile(`${dest}/_headers`, getHeadersFromBuildData(build_data)) + ]); + builder.copy(`${files}/worker.js`, `${tmp}/_worker.js`, { replace: { SERVER: `${relativePath}/index.js`, @@ -55,3 +66,42 @@ export default function () { } }; } + +/** + * @param {import('../kit/types/internal').BuildData} build_data + * @returns {import('.').RoutesJSONSpec} + */ +function getRoutesJSONFromBuildData(build_data) { + return { + version: 1, + include: ['/*'], + exclude: [ + `/${build_data.app_dir}/immutable/*`, + ...build_data.manifest_data.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(({ file }) => file.includes('favicon')) + .map((asset) => asset.file) + ] + }; +} + +/** + * @param {import('../kit/types/internal').BuildData} build_data + * @returns {string} + */ +function getHeadersFromBuildData(build_data) { + return ` +# === START AUTOGENERATED SVELTE IMMUTABLE HEADERS === +/${build_data.app_dir}/immutable/* + Cache-Control: public, immutable, max-age=31536000 + X-Robots-Tag: noindex +# === END AUTOGENERATED SVELTE IMMUTABLE HEADERS === + `.trim(); +} diff --git a/packages/kit/src/core/adapt/builder.js b/packages/kit/src/core/adapt/builder.js index e434d947fae7c..36519b2e62c1d 100644 --- a/packages/kit/src/core/adapt/builder.js +++ b/packages/kit/src/core/adapt/builder.js @@ -151,6 +151,10 @@ export function create_builder({ config, build_data, prerendered, log }) { }); }, + getBuildData() { + return build_data; + }, + getBuildDirectory(name) { return `${config.kit.outDir}/${name}`; }, diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index f5033d642bea3..622d247fe7289 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -16,7 +16,7 @@ import { RouteDefinition, TrailingSlash } from './private.js'; -import { SSRNodeLoader, SSRRoute, ValidatedConfig } from './internal.js'; +import { BuildData, SSRNodeLoader, SSRRoute, ValidatedConfig } from './internal.js'; import { HttpError, Redirect } from '../src/runtime/control.js'; export interface Adapter { @@ -56,6 +56,7 @@ export interface Builder { generateManifest: (opts: { relativePath: string; format?: 'esm' | 'cjs' }) => string; + getBuildData(): BuildData; getBuildDirectory(name: string): string; getClientDirectory(): string; getServerDirectory(): string;