diff --git a/.changeset/empty-falcons-run.md b/.changeset/empty-falcons-run.md new file mode 100644 index 000000000000..9f3ff43f014c --- /dev/null +++ b/.changeset/empty-falcons-run.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/adapter-cloudflare': patch +--- + +Check for Cache match sooner; use `worktop` for types & Cache operations diff --git a/packages/adapter-cloudflare/.gitignore b/packages/adapter-cloudflare/.gitignore index e87415077413..beb15cd7fa9a 100644 --- a/packages/adapter-cloudflare/.gitignore +++ b/packages/adapter-cloudflare/.gitignore @@ -1,3 +1 @@ -.DS_Store -node_modules -target \ No newline at end of file +/files \ No newline at end of file diff --git a/packages/adapter-cloudflare/files/worker.js b/packages/adapter-cloudflare/files/worker.js deleted file mode 100644 index 8a56e6a544db..000000000000 --- a/packages/adapter-cloudflare/files/worker.js +++ /dev/null @@ -1,85 +0,0 @@ -import { Server } from 'SERVER'; -import { manifest, prerendered } from 'MANIFEST'; - -const server = new Server(manifest); - -const prefix = `/${manifest.appDir}/`; - -export default { - /** - * @param {Request} req - * @param {any} env - * @param {any} context - */ - async fetch(req, env, context) { - const url = new URL(req.url); - - // static assets - 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(/\/$/, ''); - 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 env.ASSETS.fetch(req); - } - - // dynamically-generated pages - try { - // @ts-expect-error - `default` exists only in the cloudflare workers environment - const cache = caches.default; - let response = await cache.match(req); - - if (!response) { - response = await server.respond(req, { - platform: { env, context }, - getClientAddress() { - return req.headers.get('cf-connecting-ip'); - } - }); - - // Use waitUntil so you can return the response without blocking on - // writing to cache - try { - // If cookies are being set, ensure we dont cache the page. - if (response.headers.has('Set-Cookie')) { - response = new Response(response.body, response); - response.headers.append('Cache-Control', 'private=Set-Cookie'); - } - - context.waitUntil(cache.put(req, response.clone())); - } catch { - // noop - } - } - - return response; - } catch (e) { - return new Response('Error rendering route: ' + (e.message || e.toString()), { status: 500 }); - } - } -}; diff --git a/packages/adapter-cloudflare/package.json b/packages/adapter-cloudflare/package.json index b75c987f66cc..8762d3209bcc 100644 --- a/packages/adapter-cloudflare/package.json +++ b/packages/adapter-cloudflare/package.json @@ -23,13 +23,16 @@ ], "main": "index.js", "scripts": { + "build": "esbuild src/worker.js --bundle --outfile=files/worker.js --external:SERVER --external:MANIFEST --format=esm", "lint": "eslint --ignore-path .gitignore \"**/*.{ts,js,svelte}\" && npm run check-format", "format": "npm run check-format -- --write", - "check": "tsc", - "check-format": "prettier --check . --config ../../.prettierrc --ignore-path .gitignore" + "check": "tsc --skipLibCheck", + "check-format": "prettier --check . --config ../../.prettierrc --ignore-path .gitignore", + "prepublishOnly": "npm run build" }, "dependencies": { - "esbuild": "^0.14.21" + "esbuild": "^0.14.21", + "worktop": "0.8.0-next.12" }, "devDependencies": { "@types/ws": "^8.5.3", diff --git a/packages/adapter-cloudflare/src/worker.js b/packages/adapter-cloudflare/src/worker.js new file mode 100644 index 000000000000..dbb8f31c3bae --- /dev/null +++ b/packages/adapter-cloudflare/src/worker.js @@ -0,0 +1,68 @@ +import { Server } from 'SERVER'; +import { manifest, prerendered } from 'MANIFEST'; +import * as Cache from 'worktop/cfw.cache'; + +const server = new Server(manifest); + +const prefix = `/${manifest.appDir}/`; + +/** @type {import('worktop/cfw').Module.Worker<{ ASSETS: import('worktop/cfw.durable').Durable.Object }>} */ +const worker = { + async fetch(req, env, context) { + try { + let res = await Cache.lookup(req); + if (res) return res; + + let { pathname } = new URL(req.url); + + // static assets + if (pathname.startsWith(prefix)) { + res = await env.ASSETS.fetch(req); + + res = 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' + } + }); + } else { + // prerendered pages and index.html files + pathname = 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) + ) { + res = await env.ASSETS.fetch(req); + } else { + // dynamically-generated pages + res = await server.respond(req, { + platform: { env, context }, + getClientAddress() { + return req.headers.get('cf-connecting-ip'); + } + }); + } + } + + // Writes to Cache only if allowed + return Cache.save(req, res, context); + } catch (e) { + return new Response('Error rendering route: ' + (e.message || e.toString()), { status: 500 }); + } + } +}; + +export default worker; diff --git a/packages/adapter-cloudflare/tsconfig.json b/packages/adapter-cloudflare/tsconfig.json index 87628b965a0a..d90692441a52 100644 --- a/packages/adapter-cloudflare/tsconfig.json +++ b/packages/adapter-cloudflare/tsconfig.json @@ -11,5 +11,5 @@ "@sveltejs/kit": ["../kit/types/index"] } }, - "include": ["**/*.js", "ambient.d.ts"] + "include": ["index.js", "ambient.d.ts", "src/worker.ts"] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 666b64b04109..3593335d50c5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -72,8 +72,10 @@ importers: '@types/ws': ^8.5.3 esbuild: ^0.14.21 typescript: ^4.6.2 + worktop: 0.8.0-next.12 dependencies: esbuild: 0.14.21 + worktop: 0.8.0-next.12 devDependencies: '@types/ws': 8.5.3 typescript: 4.6.2 @@ -4568,7 +4570,6 @@ packages: /mrmime/1.0.0: resolution: {integrity: sha512-a70zx7zFfVO7XpnQ2IX1Myh9yY4UYvfld/dikWRnsXxbyvMcfz+u6UfgNAtH+k2QqtJuzVpv6eLTx1G2+WKZbQ==} engines: {node: '>=10'} - dev: true /ms/2.0.0: resolution: {integrity: sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=} @@ -5269,6 +5270,11 @@ packages: engines: {node: '>=6'} dev: true + /regexparam/2.0.0: + resolution: {integrity: sha512-gJKwd2MVPWHAIFLsaYDZfyKzHNS4o7E/v8YmNf44vmeV2e4YfVoDToTOKTvE7ab68cRJ++kLuEXJBaEeJVt5ow==} + engines: {node: '>=8'} + dev: false + /regexpp/3.2.0: resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==} engines: {node: '>=8'} @@ -6322,6 +6328,14 @@ packages: engines: {node: '>=0.10.0'} dev: true + /worktop/0.8.0-next.12: + resolution: {integrity: sha512-ZXdgI9XOf0uB4IegFoViLdQ0Bf7hish0XMHwuV3nopOXygfLJkwAC5+HyA+sihBMSM2sLLQ5uGnD5aknL8+NQg==} + engines: {node: '>=12'} + dependencies: + mrmime: 1.0.0 + regexparam: 2.0.0 + dev: false + /wrap-ansi/6.2.0: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} engines: {node: '>=8'}