diff --git a/.changeset/brave-weeks-allow.md b/.changeset/brave-weeks-allow.md new file mode 100644 index 000000000000..b226878449c3 --- /dev/null +++ b/.changeset/brave-weeks-allow.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +Expose appDir to adapters diff --git a/.changeset/witty-meals-tie.md b/.changeset/witty-meals-tie.md new file mode 100644 index 000000000000..5e9f5f96f2fe --- /dev/null +++ b/.changeset/witty-meals-tie.md @@ -0,0 +1,8 @@ +--- +'@sveltejs/adapter-cloudflare': patch +'@sveltejs/adapter-cloudflare-workers': patch +'@sveltejs/adapter-netlify': patch +'@sveltejs/adapter-vercel': patch +--- + +Add immutable cache headers to generated assets diff --git a/packages/adapter-cloudflare-workers/README.md b/packages/adapter-cloudflare-workers/README.md index 50511e89a745..db05a9bd5274 100644 --- a/packages/adapter-cloudflare-workers/README.md +++ b/packages/adapter-cloudflare-workers/README.md @@ -47,7 +47,7 @@ Now you should get some details from Cloudflare. You should get your: 1. Account ID 2. And your Zone-ID (Optional) -Get them by visiting your Cloudflare-Dashboard and click on any domain. There, you can scroll down and on the left, you can see your details under **API**. +Get them by visiting your [Cloudflare dashboard](https://dash.cloudflare.com) and click on any domain. There, you can scroll down and on the left, you can see your details under **API**. Then configure your sites build directory and your account-details in the config file: diff --git a/packages/adapter-cloudflare-workers/files/entry.js b/packages/adapter-cloudflare-workers/files/entry.js index 4ec7e7bfca63..4fbe5789eadd 100644 --- a/packages/adapter-cloudflare-workers/files/entry.js +++ b/packages/adapter-cloudflare-workers/files/entry.js @@ -1,32 +1,54 @@ import { App } from 'APP'; -import { manifest } from './manifest.js'; -import { getAssetFromKV, NotFoundError } from '@cloudflare/kv-asset-handler'; +import { manifest, prerendered } from './manifest.js'; +import { getAssetFromKV } from '@cloudflare/kv-asset-handler'; const app = new App(manifest); -addEventListener('fetch', (event) => { +const prefix = `/${manifest.appDir}/`; + +addEventListener('fetch', (/** @type {FetchEvent} */ event) => { event.respondWith(handle(event)); }); +/** + * @param {FetchEvent} event + * @returns {Promise} + */ async function handle(event) { - // try static files first - if (event.request.method == 'GET') { - try { - // TODO rather than attempting to get an asset, - // use the asset manifest to see if it exists - return await getAssetFromKV(event); - } catch (e) { - if (!(e instanceof NotFoundError)) { - return new Response('Error loading static asset:' + (e.message || e.toString()), { - status: 500 - }); + const { request } = event; + + const url = new URL(request.url); + + // generated assets + if (url.pathname.startsWith(prefix)) { + const res = await getAssetFromKV(event); + return new Response(res.body, { + headers: { + 'cache-control': 'public, immutable, max-age=31536000', + 'content-type': res.headers.get('content-type') } - } + }); } - // fall back to an app route - const request = event.request; + // prerendered pages and index.html files + const pathname = url.pathname.replace(/\/$/, ''); + let file = pathname.substring(1); + try { + file = decodeURIComponent(file); + } catch (err) { + // ignore + } + + if ( + manifest.assets.has(file) || + manifest.assets.has(file + '/index.html') || + prerendered.has(pathname || '/') + ) { + return await getAssetFromKV(event); + } + + // dynamically-generated pages try { const rendered = await app.render({ url: request.url, @@ -38,14 +60,14 @@ async function handle(event) { if (rendered) { return new Response(rendered.body, { status: rendered.status, - headers: makeHeaders(rendered.headers) + headers: make_headers(rendered.headers) }); } } catch (e) { return new Response('Error rendering route:' + (e.message || e.toString()), { status: 500 }); } - return new Response({ + return new Response('Not Found', { status: 404, statusText: 'Not Found' }); @@ -56,11 +78,8 @@ async function read(request) { return new Uint8Array(await request.arrayBuffer()); } -/** - * @param {Record} headers - * @returns {Request} - */ -function makeHeaders(headers) { +/** @param {Record} headers */ +function make_headers(headers) { const result = new Headers(); for (const header in headers) { const value = headers[header]; diff --git a/packages/adapter-cloudflare-workers/index.js b/packages/adapter-cloudflare-workers/index.js index b58907fc979c..56cb6c12a1a3 100644 --- a/packages/adapter-cloudflare-workers/index.js +++ b/packages/adapter-cloudflare-workers/index.js @@ -23,7 +23,7 @@ export default function () { builder.rimraf(entrypoint); builder.log.info('Prerendering static pages...'); - await builder.prerender({ + const { paths } = await builder.prerender({ dest: bucket }); @@ -47,7 +47,7 @@ export default function () { `${tmp}/manifest.js`, `export const manifest = ${builder.generateManifest({ relativePath - })};\n` + })};\n\nexport const prerendered = new Set(${JSON.stringify(paths)});\n` ); await esbuild.build({ @@ -67,6 +67,7 @@ export default function () { }; } +/** @param {import('@sveltejs/kit').Builder} builder */ function validate_config(builder) { if (existsSync('wrangler.toml')) { let wrangler_config; diff --git a/packages/adapter-cloudflare-workers/package.json b/packages/adapter-cloudflare-workers/package.json index b8239080742b..73a135344b5b 100644 --- a/packages/adapter-cloudflare-workers/package.json +++ b/packages/adapter-cloudflare-workers/package.json @@ -31,6 +31,8 @@ "esbuild": "^0.13.15" }, "devDependencies": { + "@cloudflare/kv-asset-handler": "^0.2.0", + "@cloudflare/workers-types": "^3.3.0", "@sveltejs/kit": "workspace:*" } } diff --git a/packages/adapter-cloudflare-workers/tsconfig.json b/packages/adapter-cloudflare-workers/tsconfig.json new file mode 100644 index 000000000000..56db9ba35cfc --- /dev/null +++ b/packages/adapter-cloudflare-workers/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "noEmit": true, + "noImplicitAny": true, + "target": "es2020", + "module": "es2020", + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "types": ["@cloudflare/workers-types"] + }, + "include": ["./index.js", "files"] +} diff --git a/packages/adapter-cloudflare/files/worker.js b/packages/adapter-cloudflare/files/worker.js index aaf128a26bc5..c10c2b217328 100644 --- a/packages/adapter-cloudflare/files/worker.js +++ b/packages/adapter-cloudflare/files/worker.js @@ -10,7 +10,20 @@ export default { const url = new URL(req.url); // static assets - if (url.pathname.startsWith(prefix)) return env.ASSETS.fetch(req); + if (url.pathname.startsWith(prefix)) { + /** @type {Response} */ + const res = await env.ASSETS.fetch(req); + + return new Response(res.body, { + headers: { + // include original cache headers, minus cache-control which + // is overridden, and etag which is no longer useful + 'cache-control': 'public, immutable, max-age=31536000', + 'content-type': res.headers.get('content-type'), + 'x-robots-tag': 'noindex' + } + }); + } // prerendered pages and index.html files const pathname = url.pathname.replace(/\/$/, ''); diff --git a/packages/adapter-netlify/index.js b/packages/adapter-netlify/index.js index 7af85cc601c1..de939673c8b6 100644 --- a/packages/adapter-netlify/index.js +++ b/packages/adapter-netlify/index.js @@ -129,7 +129,13 @@ export default function ({ split = false } = {}) { builder.copy('_redirects', redirect_file); appendFileSync(redirect_file, `\n\n${redirects.join('\n')}`); - // TODO write a _headers file that makes client-side assets immutable + builder.log.minor('Writing custom headers...'); + const headers_file = join(publish, '_headers'); + builder.copy('_headers', headers_file); + appendFileSync( + headers_file, + `\n\n/${builder.appDir}/*\n cache-control: public\n cache-control: immutable\n cache-control: max-age=31536000\n` + ); } }; } diff --git a/packages/adapter-vercel/index.js b/packages/adapter-vercel/index.js index f18a5ad74efb..5c5a38f2331d 100644 --- a/packages/adapter-vercel/index.js +++ b/packages/adapter-vercel/index.js @@ -68,6 +68,12 @@ export default function () { writeFileSync( `${dir}/config/routes.json`, JSON.stringify([ + { + src: `/${builder.appDir}/.+`, + headers: { + 'cache-control': 'public, immutable, max-age=31536000' + } + }, { handle: 'filesystem' }, diff --git a/packages/kit/src/core/adapt/builder.js b/packages/kit/src/core/adapt/builder.js index 9ba6bcb7e8b0..792b748f04e3 100644 --- a/packages/kit/src/core/adapt/builder.js +++ b/packages/kit/src/core/adapt/builder.js @@ -32,6 +32,8 @@ export function create_builder({ cwd, config, build_data, log }) { mkdirp, copy, + appDir: config.kit.appDir, + createEntries(fn) { generated_manifest = true; diff --git a/packages/kit/types/config.d.ts b/packages/kit/types/config.d.ts index 541c27223f3e..a9612bfb4f7b 100644 --- a/packages/kit/types/config.d.ts +++ b/packages/kit/types/config.d.ts @@ -38,6 +38,8 @@ export interface Builder { rimraf(dir: string): void; mkdirp(dir: string): void; + appDir: string; + /** * Create entry points that map to individual functions * @param fn A function that groups a set of routes into an entry point diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5cfb681c77a3..a2866001a0fc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -87,6 +87,8 @@ importers: packages/adapter-cloudflare-workers: specifiers: + '@cloudflare/kv-asset-handler': ^0.2.0 + '@cloudflare/workers-types': ^3.3.0 '@iarna/toml': ^2.2.5 '@sveltejs/kit': workspace:* esbuild: ^0.13.15 @@ -94,6 +96,8 @@ importers: '@iarna/toml': 2.2.5 esbuild: 0.13.15 devDependencies: + '@cloudflare/kv-asset-handler': 0.2.0 + '@cloudflare/workers-types': 3.3.0 '@sveltejs/kit': link:../kit packages/adapter-netlify: @@ -222,8 +226,8 @@ importers: '@lukeed/uuid': 2.0.0 cookie: 0.4.1 devDependencies: - '@sveltejs/adapter-auto': link:../../../adapter-auto - '@sveltejs/kit': link:../../../kit + '@sveltejs/adapter-auto': 1.0.0-next.9 + '@sveltejs/kit': 1.0.0-next.217_svelte@3.44.2 svelte: 3.44.2 svelte-preprocess: 4.9.8_svelte@3.44.2+typescript@4.5.2 typescript: 4.5.2 @@ -1045,6 +1049,16 @@ packages: prettier: 1.19.1 dev: true + /@cloudflare/kv-asset-handler/0.2.0: + resolution: {integrity: sha512-MVbXLbTcAotOPUj0pAMhVtJ+3/kFkwJqc5qNOleOZTv6QkZZABDMS21dSrSlVswEHwrpWC03e4fWytjqKvuE2A==} + dependencies: + mime: 3.0.0 + dev: true + + /@cloudflare/workers-types/3.3.0: + resolution: {integrity: sha512-3v3bm/hOuzNtHgDqPowrRE63H0GEn40LfhLMVpzS5yeg5tlE5nEQ0qobmGOJBCvJJ1LhgRRHZTJszXHs1DXQWg==} + dev: true + /@eslint/eslintrc/1.0.4: resolution: {integrity: sha512-h8Vx6MdxwWI2WM8/zREHMoqdgLNXEL4QX3MWSVMdyNJGvXVOs+6lp+m2hc3FnuMHDc4poxFNI20vCk0OmI4G0Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -1083,7 +1097,6 @@ packages: /@iarna/toml/2.2.5: resolution: {integrity: sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==} - dev: false /@istanbuljs/schema/0.1.3: resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} @@ -1359,7 +1372,53 @@ packages: dependencies: estree-walker: 2.0.2 picomatch: 2.3.0 - dev: false + + /@sveltejs/adapter-auto/1.0.0-next.9: + resolution: {integrity: sha512-dx3NgDHIG7DdhjiAnAE7vLRf9odON0J3AEMFiTpIBf3nmnwmX6Xqxtr4Ai09Sjo23sWnMRFU/flqf6V6otigBA==} + dependencies: + '@sveltejs/adapter-cloudflare': 1.0.0-next.6 + '@sveltejs/adapter-netlify': 1.0.0-next.37 + '@sveltejs/adapter-vercel': 1.0.0-next.35 + dev: true + + /@sveltejs/adapter-cloudflare/1.0.0-next.6: + resolution: {integrity: sha512-31Ercf8jMLXpMzblrWNraCvfJ1+32+/ahnpaZcX0VTEukkZlS1cIl506m/skuK8Us67bY5CC8P07jjsv4yUEeg==} + dependencies: + esbuild: 0.13.15 + dev: true + + /@sveltejs/adapter-netlify/1.0.0-next.37: + resolution: {integrity: sha512-Advp53rLMsi9Kg3GYVwWFTmqzIIKCJ2WTrm2zx12Q37RrO+HaY2R+sRMGGGrjD2fk6w1qR7WR9Qt9p0n3moUbw==} + dependencies: + '@iarna/toml': 2.2.5 + esbuild: 0.13.15 + tiny-glob: 0.2.9 + dev: true + + /@sveltejs/adapter-vercel/1.0.0-next.35: + resolution: {integrity: sha512-juh74OesfWUo2EFThOIXX4I/9MJBGpDK3+reYc/R6slPxLLEVIZDV7TcAc2yldSGgMDCUbQg7nmXjF098IT1NQ==} + dependencies: + esbuild: 0.13.15 + dev: true + + /@sveltejs/kit/1.0.0-next.217_svelte@3.44.2: + resolution: {integrity: sha512-fWBFRstHIgrY+GYe3YRe3anXZn8TlaQpowEI0W7lijZI/4HTN8pdyIIMdip55yU1AEKTRIPbg3wwaUsUKxjpmQ==} + engines: {node: '>=14.13'} + hasBin: true + peerDependencies: + svelte: ^3.44.0 + dependencies: + '@sveltejs/vite-plugin-svelte': 1.0.0-next.32_svelte@3.44.2+vite@2.7.2 + sade: 1.7.4 + svelte: 3.44.2 + vite: 2.7.2 + transitivePeerDependencies: + - diff-match-patch + - less + - sass + - stylus + - supports-color + dev: true /@sveltejs/vite-plugin-svelte/1.0.0-next.32_svelte@3.44.2+vite@2.7.2: resolution: {integrity: sha512-Lhf5BxVylosHIW6U2s6WDQA39ycd+bXivC8gHsXCJeLzxoHj7Pv7XAOk25xRSXT4wHg9DWFMBQh2DFU0DxHZ2g==} @@ -1382,7 +1441,6 @@ packages: vite: 2.7.2 transitivePeerDependencies: - supports-color - dev: false /@types/amphtml-validator/1.0.1: resolution: {integrity: sha512-DWE7fy6KtC+Uw0KV/HAmjuH2GB/o8yskXlvmVWR7mOVsLDybp+XrwkzEeRFU9wGjWKeRMBNGsx+5DRq7sUsAwA==} @@ -2382,7 +2440,6 @@ packages: cpu: [arm64] os: [android] requiresBuild: true - dev: false optional: true /esbuild-darwin-64/0.13.15: @@ -2390,7 +2447,6 @@ packages: cpu: [x64] os: [darwin] requiresBuild: true - dev: false optional: true /esbuild-darwin-arm64/0.13.15: @@ -2398,7 +2454,6 @@ packages: cpu: [arm64] os: [darwin] requiresBuild: true - dev: false optional: true /esbuild-freebsd-64/0.13.15: @@ -2406,7 +2461,6 @@ packages: cpu: [x64] os: [freebsd] requiresBuild: true - dev: false optional: true /esbuild-freebsd-arm64/0.13.15: @@ -2414,7 +2468,6 @@ packages: cpu: [arm64] os: [freebsd] requiresBuild: true - dev: false optional: true /esbuild-linux-32/0.13.15: @@ -2422,7 +2475,6 @@ packages: cpu: [ia32] os: [linux] requiresBuild: true - dev: false optional: true /esbuild-linux-64/0.13.15: @@ -2430,7 +2482,6 @@ packages: cpu: [x64] os: [linux] requiresBuild: true - dev: false optional: true /esbuild-linux-arm/0.13.15: @@ -2438,7 +2489,6 @@ packages: cpu: [arm] os: [linux] requiresBuild: true - dev: false optional: true /esbuild-linux-arm64/0.13.15: @@ -2446,7 +2496,6 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true - dev: false optional: true /esbuild-linux-mips64le/0.13.15: @@ -2454,7 +2503,6 @@ packages: cpu: [mips64el] os: [linux] requiresBuild: true - dev: false optional: true /esbuild-linux-ppc64le/0.13.15: @@ -2462,7 +2510,6 @@ packages: cpu: [ppc64] os: [linux] requiresBuild: true - dev: false optional: true /esbuild-netbsd-64/0.13.15: @@ -2470,7 +2517,6 @@ packages: cpu: [x64] os: [netbsd] requiresBuild: true - dev: false optional: true /esbuild-openbsd-64/0.13.15: @@ -2478,7 +2524,6 @@ packages: cpu: [x64] os: [openbsd] requiresBuild: true - dev: false optional: true /esbuild-sunos-64/0.13.15: @@ -2486,7 +2531,6 @@ packages: cpu: [x64] os: [sunos] requiresBuild: true - dev: false optional: true /esbuild-windows-32/0.13.15: @@ -2494,7 +2538,6 @@ packages: cpu: [ia32] os: [win32] requiresBuild: true - dev: false optional: true /esbuild-windows-64/0.13.15: @@ -2502,7 +2545,6 @@ packages: cpu: [x64] os: [win32] requiresBuild: true - dev: false optional: true /esbuild-windows-arm64/0.13.15: @@ -2510,7 +2552,6 @@ packages: cpu: [arm64] os: [win32] requiresBuild: true - dev: false optional: true /esbuild/0.13.15: @@ -2535,7 +2576,6 @@ packages: esbuild-windows-32: 0.13.15 esbuild-windows-64: 0.13.15 esbuild-windows-arm64: 0.13.15 - dev: false /escalade/3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} @@ -3874,7 +3914,6 @@ packages: resolution: {integrity: sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - dev: false /natural-compare/1.4.0: resolution: {integrity: sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=} @@ -4294,7 +4333,6 @@ packages: nanoid: 3.1.30 picocolors: 1.0.0 source-map-js: 1.0.1 - dev: false /preferred-pm/3.0.3: resolution: {integrity: sha512-+wZgbxNES/KlJs9q40F/1sfOd/j7f1O9JaHcW5Dsn3aUUOZg3L2bjpVUcKV2jvtElYfoTuQiNeMfQJ4kwUAhCQ==} @@ -4486,7 +4524,6 @@ packages: /require-relative/0.8.7: resolution: {integrity: sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4=} - dev: false /resolve-from/4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} @@ -4695,7 +4732,6 @@ packages: /source-map-js/1.0.1: resolution: {integrity: sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA==} engines: {node: '>=0.10.0'} - dev: false /source-map-support/0.4.18: resolution: {integrity: sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==} @@ -4891,7 +4927,6 @@ packages: svelte: '>=3.19.0' dependencies: svelte: 3.44.2 - dev: false /svelte-preprocess/4.9.8_svelte@3.44.2+typescript@4.4.4: resolution: {integrity: sha512-EQS/oRZzMtYdAprppZxY3HcysKh11w54MgA63ybtL+TAZ4hVqYOnhw41JVJjWN9dhPnNjjLzvbZ2tMhTsla1Og==} @@ -5283,7 +5318,6 @@ packages: rollup: 2.60.2 optionalDependencies: fsevents: 2.3.2 - dev: false /wcwidth/1.0.1: resolution: {integrity: sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=}