From 2f12c15346572689d2620e6d4acb9a7935172341 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Thu, 20 Aug 2026 16:30:00 -0400 Subject: [PATCH 01/59] cloudflare --- .../adapter-cloudflare/files/internal.d.ts | 21 ++++++++----- packages/adapter-cloudflare/files/worker.js | 30 +++++++------------ packages/adapter-cloudflare/index.js | 21 +++++-------- 3 files changed, 31 insertions(+), 41 deletions(-) diff --git a/packages/adapter-cloudflare/files/internal.d.ts b/packages/adapter-cloudflare/files/internal.d.ts index 6c79569f7f7f..d3e8d81fe7e8 100644 --- a/packages/adapter-cloudflare/files/internal.d.ts +++ b/packages/adapter-cloudflare/files/internal.d.ts @@ -1,12 +1,17 @@ declare module 'SERVER' { - export { Server } from '@sveltejs/kit'; + export const server: import('@sveltejs/kit').Server; } -declare module 'MANIFEST' { - import { SSRManifest } from '@sveltejs/kit'; - - export const manifest: SSRManifest; - export const prerendered: Set; - export const app_path: string; - export const base_path: string; +namespace Cloudflare { + interface Env { + ASSETS_BINDING: { + fetch: typeof fetch; + }; + [key: string]: string | undefined; + } } + +declare const BASE_PATH: string; +declare const APP_PATH: string; +declare const PRERENDERED: Set; +declare const MANIFEST_ASSETS: Set; diff --git a/packages/adapter-cloudflare/files/worker.js b/packages/adapter-cloudflare/files/worker.js index 911af0cfb971..47b478476762 100644 --- a/packages/adapter-cloudflare/files/worker.js +++ b/packages/adapter-cloudflare/files/worker.js @@ -1,13 +1,12 @@ -import { Server } from 'SERVER'; -import { manifest, prerendered, base_path } from 'MANIFEST'; +import { server } from 'SERVER'; import { env } from 'cloudflare:workers'; -const server = new Server(manifest); +const base_path = BASE_PATH; +const assets = MANIFEST_ASSETS; +const prerendered = PRERENDERED; -const app_path = `/${manifest.appPath}`; - -const immutable = `${app_path}/immutable/`; -const version_file = `${app_path}/version.json`; +const immutable = `/${APP_PATH}/immutable/`; +const version_file = `/${APP_PATH}/version.json`; /** * We don't know the origin until we receive a request, but @@ -17,11 +16,10 @@ const version_file = `${app_path}/version.json`; let origin; const initialized = server.init({ - env: /** @type {Record} */ (env), + env, read: async (file) => { const url = `${origin}/${file}`; - const response = - await /** @type {{ ASSETS: { fetch: typeof fetch } }} */ (env).ASSETS.fetch(url); + const response = await env.ASSETS_BINDING.fetch(url); if (!response.ok) { throw new Error( @@ -33,13 +31,8 @@ const initialized = server.init({ } }); +/** @type {import('@cloudflare/workers-types').ExportedHandler} */ export default { - /** - * @param {Request} req - * @param {{ ASSETS: { fetch: typeof fetch } }} env - * @param {ExecutionContext} ctx - * @returns {Promise} - */ async fetch(req, env, ctx) { if (!origin) { origin = new URL(req.url).origin; @@ -61,8 +54,7 @@ export default { let is_static_asset = false; const filename = stripped_pathname.slice(base_path.length + 1); if (filename) { - is_static_asset = - manifest.assets.has(filename) || manifest.assets.has(filename + '/index.html'); + is_static_asset = assets.has(filename) || assets.has(filename + '/index.html'); } let location = pathname.at(-1) === '/' ? stripped_pathname : pathname + '/'; @@ -73,7 +65,7 @@ export default { pathname === version_file || pathname.startsWith(immutable) ) { - let res = await env.ASSETS.fetch(req); + let res = await env.ASSETS_BINDING.fetch(req); // `_headers` applies cache regardless of status, so we need to ensure an // error response does not get cached if (res.status >= 400) { diff --git a/packages/adapter-cloudflare/index.js b/packages/adapter-cloudflare/index.js index d137538e7194..b4e981f1cf07 100644 --- a/packages/adapter-cloudflare/index.js +++ b/packages/adapter-cloudflare/index.js @@ -103,22 +103,15 @@ export default function (options = {}) { // worker const worker_dest_dir = path.dirname(worker_dest); - writeFileSync( - `${tmp}/manifest.js`, - `export const manifest = ${builder.generateManifest({ relativePath: path.posix.relative(tmp, builder.getServerDirectory()) })};\n\n` + - `export const prerendered = new Set(${JSON.stringify(builder.prerendered.paths)});\n\n` + - `export const base_path = ${JSON.stringify(builder.config.paths.base)};\n` - ); + builder.writeServerEntrypoint(`${tmp}/server.js`); builder.copy(`${files}/worker.js`, worker_dest, { replace: { - // the paths returned by the Wrangler config might be Windows paths, - // so we need to convert them to POSIX paths or else the backslashes - // will be interpreted as escape characters and create an incorrect import path. - // We also need to ensure the relative imports start with ./ since Wrangler - // errors if a relative import looks like a package import - SERVER: `./${posixify(path.relative(worker_dest_dir, builder.getServerDirectory()))}/index.js`, - MANIFEST: `./${posixify(path.relative(worker_dest_dir, tmp))}/manifest.js`, - ASSETS: assets_binding + SERVER: `./${path.posix.relative(worker_dest_dir, tmp)}/index.js`, + BASE_PATH: JSON.stringify(builder.config.paths.base), + APP_PATH: JSON.stringify(builder.getAppPath()), + MANIFEST_ASSETS: `new Set(${JSON.stringify(builder.manifest.assets)})`, + PRERENDERED: `new Set(${JSON.stringify(builder.prerendered.paths)})`, + ASSETS_BINDING: assets_binding } }); if (builder.hasServerInstrumentationFile()) { From 07566386e157b4b3435bff19f0ba9e0114be5378 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Thu, 20 Aug 2026 16:39:20 -0400 Subject: [PATCH 02/59] netlify --- packages/adapter-netlify/files/edge.js | 5 +-- packages/adapter-netlify/files/serverless.js | 7 ++--- packages/adapter-netlify/index.js | 32 ++++++++------------ packages/adapter-netlify/internal.d.ts | 8 +---- 4 files changed, 17 insertions(+), 35 deletions(-) diff --git a/packages/adapter-netlify/files/edge.js b/packages/adapter-netlify/files/edge.js index 518593892b06..b38d8b34e1cd 100644 --- a/packages/adapter-netlify/files/edge.js +++ b/packages/adapter-netlify/files/edge.js @@ -1,7 +1,4 @@ -import { Server } from '0SERVER'; -import { manifest } from 'MANIFEST'; - -const server = new Server(manifest); +import { server } from '0SERVER'; /** * We don't know the origin until we receive a request, but diff --git a/packages/adapter-netlify/files/serverless.js b/packages/adapter-netlify/files/serverless.js index 56502dc95c6c..7397719ef335 100644 --- a/packages/adapter-netlify/files/serverless.js +++ b/packages/adapter-netlify/files/serverless.js @@ -1,14 +1,11 @@ -import { Server } from '0SERVER'; import { createReadableStream } from '@sveltejs/kit/node'; import process from 'node:process'; /** - * @param {import('@sveltejs/kit').SSRManifest} manifest + * @param {import('@sveltejs/kit').Server} server * @returns {(request: Request, context: import('@netlify/types').Context) => Promise} */ -export function init(manifest) { - const server = new Server(manifest); - +export function init(server) { /** @type {Promise | null} */ let init_promise = server.init({ env: process.env, diff --git a/packages/adapter-netlify/index.js b/packages/adapter-netlify/index.js index c08cda021813..84bb0ffd6575 100644 --- a/packages/adapter-netlify/index.js +++ b/packages/adapter-netlify/index.js @@ -264,12 +264,11 @@ function write_frameworks_config({ builder }) { * }} opts */ function generate_serverless_function({ builder, routes, patterns, name, type, exclude }) { - const manifest = builder.generateManifest({ - relativePath: '../server', + builder.writeServerEntrypoint(`.netlify/v1/server-${name}.js`, { routes }); - const fn = generate_serverless_function_module(manifest, type); + const fn = generate_serverless_function_module(name, type); const config = generate_config_export(name, patterns, exclude); if (builder.hasServerInstrumentationFile()) { @@ -288,11 +287,11 @@ function generate_serverless_function({ builder, routes, patterns, name, type, e } /** - * @param {string} manifest + * @param {string} name * @param {ServerlessFunctionType} type * @returns {string} */ -function generate_serverless_function_module(manifest, type) { +function generate_serverless_function_module(name, type) { if (type === 'catch-all') { // Netlify encodes the response body but `fetch` automatically decodes it. // So, we need to remove the `content-encoding` header to allow Netlify @@ -300,10 +299,11 @@ function generate_serverless_function_module(manifest, type) { return `\ import { applyReroute } from '@sveltejs/kit/adapter'; import { init } from '../serverless.js'; +import { server } from '../server-${name}.js'; const original_url_header = \`x-sveltekit-original-url-\${process.env.NETLIFY_FUNCTIONS_TOKEN}\` -const respond = init(${manifest}); +const respond = init(server); export default async (request, context) => { const catch_all_response = await respond(request, context); @@ -329,10 +329,11 @@ export default async (request, context) => { if (type === 'split') { return `\ import { init } from '../serverless.js'; +import { server } from '../server-${name}.js'; const original_url_header = \`x-sveltekit-original-url-\${process.env.NETLIFY_FUNCTIONS_TOKEN}\` -const respond = init(${manifest}); +const respond = init(server); export default async (request, context) => { if (request.headers.has(original_url_header)) { @@ -348,8 +349,9 @@ export default async (request, context) => { return `\ import { init } from '../serverless.js'; +import { server } from '../server-${name}.js'; -export default init(${manifest}); +export default init(server); `; } @@ -426,19 +428,11 @@ async function generate_edge_functions({ builder }) { builder.copy(`${files}/edge.js`, `${tmp}/entry.js`, { replace: { - '0SERVER': `${relativePath}/index.js`, - MANIFEST: './manifest.js' + '0SERVER': `./server.js` } }); - const manifest = builder.generateManifest({ - relativePath - }); - - writeFileSync(`${tmp}/manifest.js`, `export const manifest = ${manifest};\n`); - - /** @type {{ assets: Set }} */ - const { assets } = (await import(pathToFileURL(`${tmp}/manifest.js`).href)).manifest; + builder.writeServerEntrypoint(`${tmp}/server.js`); const path = '/*'; // We only need to specify paths without the trailing slash because @@ -449,7 +443,7 @@ async function generate_edge_functions({ builder }) { `/${builder.getAppPath()}/version.json`, // the base root and `trailingSlash: 'always'` pages are recorded with a trailing slash ...builder.prerendered.paths.map((path) => (path === '/' ? path : path.replace(/\/$/, ''))), - ...Array.from(assets).flatMap((asset) => { + ...Array.from(builder.manifest.assets).flatMap((asset) => { if (asset.endsWith('/index.html')) { const dir = asset.replace(/\/index\.html$/, ''); return [`${builder.config.paths.base}/${asset}`, `${builder.config.paths.base}/${dir}`]; diff --git a/packages/adapter-netlify/internal.d.ts b/packages/adapter-netlify/internal.d.ts index 55da8ba1fbf5..97df9e32dfe3 100644 --- a/packages/adapter-netlify/internal.d.ts +++ b/packages/adapter-netlify/internal.d.ts @@ -1,9 +1,3 @@ declare module '0SERVER' { - export { Server } from '@sveltejs/kit'; -} - -declare module 'MANIFEST' { - import { SSRManifest } from '@sveltejs/kit'; - - export const manifest: SSRManifest; + export const server: import('@sveltejs/kit').Server; } From 3711f70f2cf9e54b8ce327a6910fb8ef88a45ef4 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Thu, 20 Aug 2026 16:50:17 -0400 Subject: [PATCH 03/59] node --- packages/adapter-node/index.d.ts | 5 ++++ packages/adapter-node/index.js | 44 +++++++++++++++------------- packages/adapter-node/internal.d.ts | 11 +------ packages/adapter-node/src/handler.js | 18 +++++------- 4 files changed, 37 insertions(+), 41 deletions(-) diff --git a/packages/adapter-node/index.d.ts b/packages/adapter-node/index.d.ts index 71daeab1361c..4166b9c9b3ae 100644 --- a/packages/adapter-node/index.d.ts +++ b/packages/adapter-node/index.d.ts @@ -5,6 +5,11 @@ declare global { const ENV_PREFIX: string; const PRECOMPRESS: boolean; const ORIGIN: string | undefined; + const UNCOMPRESSED_EXTENSIONS: Set; + const BASE_PATH: string; + const APP_PATH: string; + const PRERENDERED: Set; + const MIME_TYPES: Record; } interface AdapterOptions { diff --git a/packages/adapter-node/index.js b/packages/adapter-node/index.js index 4ce90cf944c7..b6ec49696af5 100644 --- a/packages/adapter-node/index.js +++ b/packages/adapter-node/index.js @@ -65,16 +65,6 @@ export default function (opts = {}) { const dir_id = `${entries}/dir.js`; - writeFileSync( - `${server}/manifest.js`, - [ - `export const manifest = ${builder.generateManifest({ relativePath: './' })};`, - `export const prerendered = new Set(${JSON.stringify(builder.prerendered.paths)});`, - `export const base = ${JSON.stringify(builder.config.paths.base)};`, - `export const uncompressed_extensions = new Set(${JSON.stringify([...uncompressed_extensions])});` - ].join('\n\n') - ); - /** @type {Record} */ const input = { index: `${entries}/index.js`, @@ -86,6 +76,20 @@ export default function (opts = {}) { input['instrumentation.server'] = `${server}/instrumentation.server.js`; } + builder.writeServerEntrypoint(`${server}/server.js`); + + /** @type {Record} */ + const defines = { + UNCOMPRESSED_EXTENSIONS: `new Set(${JSON.stringify([...uncompressed_extensions])})`, + BASE_PATH: JSON.stringify(builder.config.paths.base), + APP_PATH: JSON.stringify(builder.getAppPath()), + PRERENDERED: `new Set(${JSON.stringify(builder.prerendered.paths)})`, + MIME_TYPES: JSON.stringify(builder.mimeTypes), + ORIGIN: JSON.stringify(builder.config.paths.origin) || 'undefined', + ENV_PREFIX: JSON.stringify(envPrefix), + PRECOMPRESS: JSON.stringify(precompress) + }; + // we bundle the Vite output so that deployments only need // their production dependencies. Anything in devDependencies // will get included in the bundled code @@ -111,9 +115,11 @@ export default function (opts = {}) { { // resolve the app's server and manifest, generated above name: 'adapter-node-resolve-app', - resolveId(id) { - if (id === 'SERVER') return `${server}/index.js`; - if (id === 'MANIFEST') return `${server}/manifest.js`; + resolveId: { + filter: { id: /^SERVER$/ }, + handler() { + return `${server}/server.js`; + } } }, { @@ -125,13 +131,11 @@ export default function (opts = {}) { filter: { id: new RegExp(escape_regex(entries)) }, handler(_code, _id, { magicString }) { if (!magicString) throw new Error('experimental.nativeMagicString is not enabled'); - magicString - .replace(/\bENV_PREFIX\b/g, JSON.stringify(envPrefix)) - .replace(/\bPRECOMPRESS\b/g, JSON.stringify(precompress)) - .replace( - /\bORIGIN\b/g, - JSON.stringify(builder.config.paths.origin) || 'undefined' - ); + + for (const [from, to] of Object.entries(defines)) { + magicString.replace(new RegExp(`\\b${from}\\b`, 'g'), to); + } + return { code: magicString, map: magicString.generateMap().toString() diff --git a/packages/adapter-node/internal.d.ts b/packages/adapter-node/internal.d.ts index 8748941c8a6f..8897747bd7c3 100644 --- a/packages/adapter-node/internal.d.ts +++ b/packages/adapter-node/internal.d.ts @@ -1,12 +1,3 @@ -declare module 'MANIFEST' { - import { SSRManifest } from '@sveltejs/kit'; - - export const base: string; - export const uncompressed_extensions: Set; - export const manifest: SSRManifest; - export const prerendered: Set; -} - declare module 'SERVER' { - export { Server } from '@sveltejs/kit'; + export const server: import('@sveltejs/kit').Server; } diff --git a/packages/adapter-node/src/handler.js b/packages/adapter-node/src/handler.js index 28ab9d49b650..5aa8920b4402 100644 --- a/packages/adapter-node/src/handler.js +++ b/packages/adapter-node/src/handler.js @@ -6,17 +6,17 @@ import process from 'node:process'; import sirv from 'sirv'; import { parse as polka_url_parser } from '@polka/url'; import { getRequest, setResponse, createReadableStream } from '@sveltejs/kit/node'; -import { Server } from 'SERVER'; -import { manifest, prerendered, base, uncompressed_extensions } from 'MANIFEST'; +import { server } from 'SERVER'; import { dir } from './dir.js'; import { env, env_prefix } from './env.js'; import { parse_as_bytes } from './utils.js'; /** @typedef {(req: IncomingMessage, res: ServerResponse, next: () => void | Promise) => void | Promise} Middleware */ -const server = new Server(manifest); - const origin = ORIGIN; +const uncompressed_extensions = UNCOMPRESSED_EXTENSIONS; +const prerendered = PRERENDERED; +const mime_types = MIME_TYPES; const xff_depth = parseInt(env('XFF_DEPTH', '1')); const address_header = env('ADDRESS_HEADER', '').toLowerCase(); @@ -32,7 +32,7 @@ if (isNaN(body_size_limit)) { ); } -const asset_dir = `${dir}/client${base}`; +const asset_dir = `${dir}/client${BASE_PATH}`; await server.init({ env: process.env, @@ -56,16 +56,12 @@ function serve(path, client = false) { } // `sirv` uses its own bundled `mrmime`, which the manifest's added types never reach - let type = manifest.mimeTypes[pathname.slice(pathname.lastIndexOf('.'))]; + let type = mime_types[pathname.slice(pathname.lastIndexOf('.'))]; if (type === 'text/html') type += ';charset=utf-8'; if (type) res.setHeader('content-type', type); // only apply to build directory, not e.g. version.json - if ( - client && - pathname.startsWith(`/${manifest.appPath}/immutable/`) && - res.statusCode === 200 - ) { + if (client && pathname.startsWith(`/${APP_PATH}/immutable/`) && res.statusCode === 200) { res.setHeader('cache-control', 'public,max-age=31536000,immutable'); } } From 7a60529430158ca8d4bc0f0bb901c1e23d92f832 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Thu, 20 Aug 2026 16:51:55 -0400 Subject: [PATCH 04/59] vercel --- .../docs/25-build-and-deploy/99-writing-adapters.md | 4 ++-- packages/adapter-vercel/files/serverless.js | 5 +---- packages/adapter-vercel/index.js | 10 ++-------- packages/adapter-vercel/internal.d.ts | 7 +------ 4 files changed, 6 insertions(+), 20 deletions(-) diff --git a/documentation/docs/25-build-and-deploy/99-writing-adapters.md b/documentation/docs/25-build-and-deploy/99-writing-adapters.md index a4002696a018..02996e7466c8 100644 --- a/documentation/docs/25-build-and-deploy/99-writing-adapters.md +++ b/documentation/docs/25-build-and-deploy/99-writing-adapters.md @@ -60,8 +60,8 @@ Within the `adapt` method, there are a number of things that an adapter should d - Clear out the build directory - Write SvelteKit output with `builder.writeClient`, `builder.writeServer`, and `builder.writePrerendered` - Output code that: - - Imports `Server` from `${builder.getServerDirectory()}/index.js` - - Instantiates the app with a manifest generated with `builder.generateManifest({ relativePath })` + - Imports a `server` instance generated by `${builder.generateServer()}` + - Runs `await server.init({ env, read })` - Listens for requests from the platform, converts them to a standard [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) if necessary, calls the `server.respond(request, { getClientAddress })` function to generate a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) and responds with it - expose any platform-specific information to SvelteKit via the `platform` option passed to `server.respond` - Bundle the output to avoid needing to install dependencies on the target platform, if necessary diff --git a/packages/adapter-vercel/files/serverless.js b/packages/adapter-vercel/files/serverless.js index d6aa640edf16..8e3bdc096974 100644 --- a/packages/adapter-vercel/files/serverless.js +++ b/packages/adapter-vercel/files/serverless.js @@ -1,10 +1,7 @@ import { createReadableStream } from '@sveltejs/kit/node'; -import { Server } from 'SERVER'; -import { manifest } from 'MANIFEST'; +import { server } from 'SERVER'; import process from 'node:process'; -const server = new Server(manifest); - await server.init({ env: process.env, read: createReadableStream diff --git a/packages/adapter-vercel/index.js b/packages/adapter-vercel/index.js index 99a7b67c6ec5..137247c51352 100644 --- a/packages/adapter-vercel/index.js +++ b/packages/adapter-vercel/index.js @@ -61,11 +61,9 @@ const plugin = function (defaults = {}) { builder.copy(proxy, entrypoint); } - const relativePath = path.posix.relative(tmp, builder.getServerDirectory()); builder.copy(`${files}/serverless.js`, proxy ? `${tmp}/serverless.js` : entrypoint, { replace: { - SERVER: `${relativePath}/index.js`, - MANIFEST: './manifest.js' + SERVER: `./server.js` } }); if (builder.hasServerInstrumentationFile()) { @@ -74,11 +72,7 @@ const plugin = function (defaults = {}) { instrumentation: `${builder.getServerDirectory()}/instrumentation.server.js` }); } - - write( - `${tmp}/manifest.js`, - `export const manifest = ${builder.generateManifest({ relativePath, routes })};\n` - ); + builder.writeServerEntrypoint(`${tmp}/server.js`, { routes }); await create_function_bundle(builder, entrypoint, dir, config); diff --git a/packages/adapter-vercel/internal.d.ts b/packages/adapter-vercel/internal.d.ts index 537f7cc041d1..8897747bd7c3 100644 --- a/packages/adapter-vercel/internal.d.ts +++ b/packages/adapter-vercel/internal.d.ts @@ -1,8 +1,3 @@ declare module 'SERVER' { - export { Server } from '@sveltejs/kit'; -} - -declare module 'MANIFEST' { - import { SSRManifest } from '@sveltejs/kit'; - export const manifest: SSRManifest; + export const server: import('@sveltejs/kit').Server; } From f7f8f633142ee5bb87eccc1e8f68153e4cb3f19c Mon Sep 17 00:00:00 2001 From: Ottomated Date: Thu, 20 Aug 2026 17:24:49 -0400 Subject: [PATCH 05/59] breaking: replace the `generateManifest` adapter api with `writeServerEntrypoint` and `builder.manifest` --- .changeset/smooth-flies-begin.md | 10 ++++ packages/adapter-netlify/index.js | 7 +-- packages/kit/src/core/adapt/builder.js | 59 +++++++++++++------ packages/kit/src/core/adapt/index.js | 3 + .../kit/src/core/sync/write_app_manifest.js | 5 +- packages/kit/src/exports/public.d.ts | 18 +++++- packages/kit/src/exports/vite/build/index.js | 19 ++++-- .../kit/src/exports/vite/preview/index.js | 1 + packages/kit/types/index.d.ts | 17 +++++- 9 files changed, 107 insertions(+), 32 deletions(-) create mode 100644 .changeset/smooth-flies-begin.md diff --git a/.changeset/smooth-flies-begin.md b/.changeset/smooth-flies-begin.md new file mode 100644 index 000000000000..da2f9b58f3e1 --- /dev/null +++ b/.changeset/smooth-flies-begin.md @@ -0,0 +1,10 @@ +--- +"@sveltejs/adapter-cloudflare": major +"@sveltejs/adapter-netlify": major +"@sveltejs/adapter-node": major +"@sveltejs/adapter-vercel": major +"@sveltejs/kit": major +--- + +breaking: replace the `generateManifest` adapter api with `writeServerEntrypoint` and `builder.manifest` + \ No newline at end of file diff --git a/packages/adapter-netlify/index.js b/packages/adapter-netlify/index.js index 84bb0ffd6575..f780c2fab4f2 100644 --- a/packages/adapter-netlify/index.js +++ b/packages/adapter-netlify/index.js @@ -1,6 +1,6 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; -import { join, posix } from 'node:path'; -import { fileURLToPath, pathToFileURL } from 'node:url'; +import { join } from 'node:path'; +import { fileURLToPath } from 'node:url'; import { builtinModules } from 'node:module'; import process from 'node:process'; import toml from '@iarna/toml'; @@ -424,7 +424,6 @@ async function generate_edge_functions({ builder }) { mkdirSync('.netlify/v1/edge-functions', { recursive: true }); builder.log.minor('Generating Edge Function...'); - const relativePath = posix.relative(tmp, builder.getServerDirectory()); builder.copy(`${files}/edge.js`, `${tmp}/entry.js`, { replace: { @@ -443,7 +442,7 @@ async function generate_edge_functions({ builder }) { `/${builder.getAppPath()}/version.json`, // the base root and `trailingSlash: 'always'` pages are recorded with a trailing slash ...builder.prerendered.paths.map((path) => (path === '/' ? path : path.replace(/\/$/, ''))), - ...Array.from(builder.manifest.assets).flatMap((asset) => { + ...Array.from(builder.manifest.assets).flatMap(({ path: asset }) => { if (asset.endsWith('/index.html')) { const dir = asset.replace(/\/index\.html$/, ''); return [`${builder.config.paths.base}/${asset}`, `${builder.config.paths.base}/${dir}`]; diff --git a/packages/kit/src/core/adapt/builder.js b/packages/kit/src/core/adapt/builder.js index 5be5e91482bd..cf02857a0327 100644 --- a/packages/kit/src/core/adapt/builder.js +++ b/packages/kit/src/core/adapt/builder.js @@ -13,7 +13,7 @@ import { rmSync, statSync } from 'node:fs'; -import { extname, resolve, join, dirname, relative } from 'node:path'; +import path from 'node:path'; import { pipeline } from 'node:stream'; import { promisify } from 'node:util'; import zlib from 'node:zlib'; @@ -22,10 +22,11 @@ import { posixify } from '../../utils/os.js'; import { generate_manifest } from '../generate_manifest/index.js'; import { get_route_segments } from '../../utils/routing.js'; import generate_fallback from '../postbuild/fallback.js'; -import { write } from '../sync/utils.js'; +import { dedent, write } from '../sync/utils.js'; import { find_server_assets } from '../generate_manifest/find_server_assets.js'; import { create_exported_declarations } from '../env.js'; import { handle_issues, validate } from '../../exports/internal/env.js'; +import { get_mime_lookup } from '../utils.js'; const pipe = promisify(pipeline); const extensions = [ @@ -51,6 +52,7 @@ const extensions = [ * route_data: RouteData[]; * prerendered: Prerendered; * prerender_map: PrerenderMap; + * app_manifest: typeof import('$app/manifest'); * log: Logger; * vite_config: ResolvedConfig; * remotes: RemoteChunk[]; @@ -65,6 +67,7 @@ export function create_builder({ route_data, prerendered, prerender_map, + app_manifest, log, vite_config, remotes, @@ -112,17 +115,21 @@ export function create_builder({ config, prerendered, routes, + manifest: app_manifest, + get mimeTypes() { + return get_mime_lookup(build_data.manifest_data); + }, async compress(directory) { if (!existsSync(directory)) { return []; } - const files = [...walk(directory)].filter((file) => extensions.includes(extname(file))); + const files = [...walk(directory)].filter((file) => extensions.includes(path.extname(file))); await Promise.all( files.flatMap((file) => { - const abs = resolve(directory, file); + const abs = path.resolve(directory, file); return [compress_file(abs, 'gz'), compress_file(abs, 'br')]; }) ); @@ -184,17 +191,31 @@ export function create_builder({ write(`${dest}/env.js`, `export const env=${payload}`); }, - generateManifest({ relativePath, routes: subset }) { - return generate_manifest({ - build_data, - prerendered: prerendered.paths, - relative_path: relativePath, - routes: subset - ? subset.map((route) => /** @type {import('types').RouteData} */ (lookup.get(route))) - : route_data.filter((route) => prerender_map.get(route.id) !== true), - remotes, - root: vite_config.root - }); + generateManifest() { + throw new Error( + `The \`generateManifest\` adapter API is deprecated — it has been replaced with \`writeServerEntrypoint\` and \`getManifest\`. You may need to update your adapter` + ); + }, + + writeServerEntrypoint(dest, { routes: subset } = {}) { + const relativePath = path.posix.relative(path.dirname(dest), this.getServerDirectory()); + write( + dest, + dedent` + import { Server } from '${relativePath}/index.js'; + const manifest = ${generate_manifest({ + build_data, + prerendered: prerendered.paths, + relative_path: relativePath, + routes: subset + ? subset.map((route) => /** @type {import('types').RouteData} */ (lookup.get(route))) + : route_data.filter((route) => prerender_map.get(route.id) !== true), + remotes, + root: vite_config.root + })}; + export const server = new Server(manifest); + ` + ); }, getBuildDirectory(name) { @@ -241,7 +262,7 @@ export function create_builder({ instrument({ entrypoint, instrumentation, - start = join(dirname(entrypoint), 'start.js'), + start = path.join(path.dirname(entrypoint), 'start.js'), module = { exports: ['default'] } @@ -262,8 +283,10 @@ export function create_builder({ copy(`${entrypoint}.map`, `${start}.map`); } - const relative_instrumentation = posixify(relative(dirname(entrypoint), instrumentation)); - const relative_start = posixify(relative(dirname(entrypoint), start)); + const relative_instrumentation = posixify( + path.relative(path.dirname(entrypoint), instrumentation) + ); + const relative_start = posixify(path.relative(path.dirname(entrypoint), start)); const facade = 'generateText' in module diff --git a/packages/kit/src/core/adapt/index.js b/packages/kit/src/core/adapt/index.js index 46f1fd5bc640..7b6c0850a26a 100644 --- a/packages/kit/src/core/adapt/index.js +++ b/packages/kit/src/core/adapt/index.js @@ -7,6 +7,7 @@ import { create_builder } from './builder.js'; * @param {import('types').ServerMetadata} server_metadata * @param {import('types').Prerendered} prerendered * @param {import('types').PrerenderMap} prerender_map + * @param {typeof import('$app/manifest')} app_manifest * @param {import('types').Logger} log * @param {import('types').RemoteChunk[]} remotes * @param {import('vite').ResolvedConfig} vite_config @@ -18,6 +19,7 @@ export async function adapt( server_metadata, prerendered, prerender_map, + app_manifest, log, remotes, vite_config, @@ -48,6 +50,7 @@ export async function adapt( route_data: build_data.manifest_data.routes.filter((route) => route.page || route.endpoint), prerendered, prerender_map, + app_manifest, log, remotes, vite_config, diff --git a/packages/kit/src/core/sync/write_app_manifest.js b/packages/kit/src/core/sync/write_app_manifest.js index 8a0fe6b07b18..3a16fb5c2495 100644 --- a/packages/kit/src/core/sync/write_app_manifest.js +++ b/packages/kit/src/core/sync/write_app_manifest.js @@ -1,4 +1,5 @@ /** @import { Asset, ManifestData, RouteData } from 'types' */ +/** @import { ManifestRoute } from '$app/manifest' */ import { s } from '../../utils/misc.js'; import { is_app_route, is_endpoint_route, is_page_route } from './create_manifest_data/index.js'; @@ -60,11 +61,11 @@ function stringify_assets(assets) { /** * @param {RouteData[] | undefined} routes - * @returns {Array<{ id: string; page: boolean; endpoint: boolean }>} + * @returns {ManifestRoute[]} */ export function get_manifest_routes(routes) { return ( - routes?.filter(is_app_route).map((route) => ({ + routes?.filter(is_app_route).map((route) => /** @type {ManifestRoute} */ ({ id: route.id, page: is_page_route(route), endpoint: is_endpoint_route(route) diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index 8fde64196aeb..dfddcad02a01 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -139,6 +139,10 @@ export interface Builder { prerendered: Prerendered; /** An array of all routes (including prerendered) */ routes: RouteDefinition[]; + /** The value of the `$app/manifest` module */ + manifest: typeof import('$app/manifest'); + /** */ + mimeTypes: Record; /** * Create separate functions that map to one or more routes of your app. @@ -166,8 +170,9 @@ export interface Builder { * Generate a server-side manifest to initialise the SvelteKit [server](https://svelte.dev/docs/kit/@sveltejs-kit#Server) with. * @param opts * @param opts.relativePath A relative path to the base directory of the server build output + * @deprecated removed in 3.0. Use `builder.writeServerEntrypoint` or `builder.manifest` instead */ - generateManifest: (opts: { relativePath: string; routes?: RouteDefinition[] }) => string; + generateManifest?: (opts: { relativePath: string; routes?: RouteDefinition[] }) => string; /** * Resolve a path to the `name` directory inside `outDir`, e.g. `/path/to/.svelte-kit/my-adapter`. @@ -181,6 +186,17 @@ export interface Builder { /** Get the application path including any configured `base` path, e.g. `my-base-path/_app`. */ getAppPath: () => string; + /** + * Writes a javascript file that initialises the SvelteKit [server](https://svelte.dev/docs/kit/@sveltejs-kit#Server). + * @param dest + * @param opts.routes A subset of the routes to include in the server's manifest + */ + writeServerEntrypoint: ( + dest: string, + opts?: { + routes?: RouteDefinition[]; + } + ) => void; /** * Write client assets to `dest`. * @param dest the destination folder diff --git a/packages/kit/src/exports/vite/build/index.js b/packages/kit/src/exports/vite/build/index.js index 84b850a50702..db7686ac2de7 100644 --- a/packages/kit/src/exports/vite/build/index.js +++ b/packages/kit/src/exports/vite/build/index.js @@ -862,6 +862,14 @@ export function plugin_compile( // defer until after other buildApp hooks have run finalise = async () => { + /** @type {typeof import('$app/manifest')} */ + const app_manifest = { + assets: manifest_data.assets.map((asset) => ({ path: asset.file })), + immutable: immutable ?? [], + prerendered: prerendered_paths, + routes: get_manifest_routes(manifest_data.routes) + }; + // defer creating the service worker to avoid other plugins from // overwriting it if they run a client environment build if (service_worker_entry_file) { @@ -871,12 +879,10 @@ export function plugin_compile( builder.environments.serviceWorker.config.define = { ...builder.environments.serviceWorker.config.define, - __SVELTEKIT_MANIFEST_ASSETS__: s( - manifest_data.assets.map((asset) => ({ path: asset.file })) - ), - __SVELTEKIT_MANIFEST_IMMUTABLE__: s(immutable), - __SVELTEKIT_MANIFEST_PRERENDERED__: s(prerendered_paths), - __SVELTEKIT_MANIFEST_ROUTES__: s(get_manifest_routes(manifest_data.routes)) + __SVELTEKIT_MANIFEST_ASSETS__: s(app_manifest.assets), + __SVELTEKIT_MANIFEST_IMMUTABLE__: s(app_manifest.immutable), + __SVELTEKIT_MANIFEST_PRERENDERED__: s(app_manifest.prerendered), + __SVELTEKIT_MANIFEST_ROUTES__: s(app_manifest.routes) }; // we have to overwrite this because it can't be configured per environment in the config hook @@ -905,6 +911,7 @@ export function plugin_compile( metadata, prerendered, prerender_results.prerender_map, + app_manifest, log, remotes, vite_config, diff --git a/packages/kit/src/exports/vite/preview/index.js b/packages/kit/src/exports/vite/preview/index.js index 2c29442c6032..4e0187c9e1b7 100644 --- a/packages/kit/src/exports/vite/preview/index.js +++ b/packages/kit/src/exports/vite/preview/index.js @@ -43,6 +43,7 @@ export async function preview(vite, svelte_config) { /** @type {ServerModule} */ const { Server } = await import(pathToFileURL(join(dir, 'index.js')).href); + /** @type {{ manifest: import('@sveltejs/kit').SSRManifest }} */ const { manifest } = await import(pathToFileURL(join(dir, 'manifest.js')).href); set_assets(assets); diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 72ef01fc7cbf..b410780768fc 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -125,6 +125,10 @@ declare module '@sveltejs/kit' { prerendered: Prerendered; /** An array of all routes (including prerendered) */ routes: RouteDefinition[]; + /** The value of the `$app/manifest` module */ + manifest: typeof import('$app/manifest'); + + mimeTypes: Record; /** * Create separate functions that map to one or more routes of your app. @@ -151,8 +155,9 @@ declare module '@sveltejs/kit' { /** * Generate a server-side manifest to initialise the SvelteKit [server](https://svelte.dev/docs/kit/@sveltejs-kit#Server) with. * @param opts.relativePath A relative path to the base directory of the server build output + * @deprecated removed in 3.0. Use `builder.writeServerEntrypoint` or `builder.manifest` instead */ - generateManifest: (opts: { relativePath: string; routes?: RouteDefinition[] }) => string; + generateManifest?: (opts: { relativePath: string; routes?: RouteDefinition[] }) => string; /** * Resolve a path to the `name` directory inside `outDir`, e.g. `/path/to/.svelte-kit/my-adapter`. @@ -166,6 +171,16 @@ declare module '@sveltejs/kit' { /** Get the application path including any configured `base` path, e.g. `my-base-path/_app`. */ getAppPath: () => string; + /** + * Writes a javascript file that initialises the SvelteKit [server](https://svelte.dev/docs/kit/@sveltejs-kit#Server). + * @param opts.routes A subset of the routes to include in the server's manifest + */ + writeServerEntrypoint: ( + dest: string, + opts?: { + routes?: RouteDefinition[]; + } + ) => void; /** * Write client assets to `dest`. * @param dest the destination folder From 47a043454225e7ddab5def4e84de3bac25fbd48b Mon Sep 17 00:00:00 2001 From: Ottomated Date: Thu, 20 Aug 2026 17:42:31 -0400 Subject: [PATCH 06/59] fix cloudflare --- packages/adapter-cloudflare/index.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/adapter-cloudflare/index.js b/packages/adapter-cloudflare/index.js index b4e981f1cf07..f31a807ebedb 100644 --- a/packages/adapter-cloudflare/index.js +++ b/packages/adapter-cloudflare/index.js @@ -106,10 +106,10 @@ export default function (options = {}) { builder.writeServerEntrypoint(`${tmp}/server.js`); builder.copy(`${files}/worker.js`, worker_dest, { replace: { - SERVER: `./${path.posix.relative(worker_dest_dir, tmp)}/index.js`, + SERVER: `./${path.posix.relative(worker_dest_dir, tmp)}/server.js`, BASE_PATH: JSON.stringify(builder.config.paths.base), APP_PATH: JSON.stringify(builder.getAppPath()), - MANIFEST_ASSETS: `new Set(${JSON.stringify(builder.manifest.assets)})`, + MANIFEST_ASSETS: `new Set(${JSON.stringify(builder.manifest.assets.map((a) => a.path))})`, PRERENDERED: `new Set(${JSON.stringify(builder.prerendered.paths)})`, ASSETS_BINDING: assets_binding } @@ -283,8 +283,3 @@ function validate_wrangler_config(config_file = undefined) { building_for_cloudflare_pages }; } - -/** @param {string} str */ -function posixify(str) { - return str.replace(/\\/g, '/'); -} From b1e7adbc81e339627a58756ae6cf8a44331a35e1 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Thu, 20 Aug 2026 17:45:35 -0400 Subject: [PATCH 07/59] unused --- packages/adapter-node/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/adapter-node/index.js b/packages/adapter-node/index.js index b6ec49696af5..08dea8b74eb7 100644 --- a/packages/adapter-node/index.js +++ b/packages/adapter-node/index.js @@ -1,4 +1,4 @@ -import { mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { mkdirSync, readFileSync, rmSync } from 'node:fs'; import { extname } from 'node:path'; import { fileURLToPath } from 'node:url'; import { rolldown } from 'rolldown'; From afcd82534d16a8a319d4c886f8e340c6171ac131 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Thu, 20 Aug 2026 20:20:15 -0400 Subject: [PATCH 08/59] address most of suggestions --- .../99-writing-adapters.md | 2 +- packages/adapter-cloudflare/index.js | 12 +++++- packages/adapter-netlify/index.js | 3 +- packages/adapter-node/index.js | 4 +- packages/kit/src/core/adapt/builder.js | 38 +++++++++++++++---- .../kit/src/core/generate_manifest/index.js | 6 +-- packages/kit/src/exports/public.d.ts | 4 +- packages/kit/types/index.d.ts | 4 +- 8 files changed, 57 insertions(+), 16 deletions(-) diff --git a/documentation/docs/25-build-and-deploy/99-writing-adapters.md b/documentation/docs/25-build-and-deploy/99-writing-adapters.md index 02996e7466c8..c788a6571125 100644 --- a/documentation/docs/25-build-and-deploy/99-writing-adapters.md +++ b/documentation/docs/25-build-and-deploy/99-writing-adapters.md @@ -60,7 +60,7 @@ Within the `adapt` method, there are a number of things that an adapter should d - Clear out the build directory - Write SvelteKit output with `builder.writeClient`, `builder.writeServer`, and `builder.writePrerendered` - Output code that: - - Imports a `server` instance generated by `${builder.generateServer()}` + - Imports a `server` instance generated by `${builder.writeServerEntrypoint()}` - Runs `await server.init({ env, read })` - Listens for requests from the platform, converts them to a standard [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) if necessary, calls the `server.respond(request, { getClientAddress })` function to generate a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) and responds with it - expose any platform-specific information to SvelteKit via the `platform` option passed to `server.respond` diff --git a/packages/adapter-cloudflare/index.js b/packages/adapter-cloudflare/index.js index f31a807ebedb..cc81b4c6b32e 100644 --- a/packages/adapter-cloudflare/index.js +++ b/packages/adapter-cloudflare/index.js @@ -106,7 +106,12 @@ export default function (options = {}) { builder.writeServerEntrypoint(`${tmp}/server.js`); builder.copy(`${files}/worker.js`, worker_dest, { replace: { - SERVER: `./${path.posix.relative(worker_dest_dir, tmp)}/server.js`, + // the paths returned by the Wrangler config might be Windows paths, + // so we need to convert them to POSIX paths or else the backslashes + // will be interpreted as escape characters and create an incorrect import path. + // We also need to ensure the relative imports start with ./ since Wrangler + // errors if a relative import looks like a package import + SERVER: `./${posixify(path.relative(worker_dest_dir, tmp))}/server.js`, BASE_PATH: JSON.stringify(builder.config.paths.base), APP_PATH: JSON.stringify(builder.getAppPath()), MANIFEST_ASSETS: `new Set(${JSON.stringify(builder.manifest.assets.map((a) => a.path))})`, @@ -283,3 +288,8 @@ function validate_wrangler_config(config_file = undefined) { building_for_cloudflare_pages }; } + +/** @param {string} str */ +function posixify(str) { + return str.replace(/\\/g, '/'); +} diff --git a/packages/adapter-netlify/index.js b/packages/adapter-netlify/index.js index f780c2fab4f2..2c52b27be6e2 100644 --- a/packages/adapter-netlify/index.js +++ b/packages/adapter-netlify/index.js @@ -265,7 +265,8 @@ function write_frameworks_config({ builder }) { */ function generate_serverless_function({ builder, routes, patterns, name, type, exclude }) { builder.writeServerEntrypoint(`.netlify/v1/server-${name}.js`, { - routes + routes, + serverDirectory: '.netlify/v1/server' }); const fn = generate_serverless_function_module(name, type); diff --git a/packages/adapter-node/index.js b/packages/adapter-node/index.js index 08dea8b74eb7..bf8c7db95494 100644 --- a/packages/adapter-node/index.js +++ b/packages/adapter-node/index.js @@ -133,7 +133,9 @@ export default function (opts = {}) { if (!magicString) throw new Error('experimental.nativeMagicString is not enabled'); for (const [from, to] of Object.entries(defines)) { - magicString.replace(new RegExp(`\\b${from}\\b`, 'g'), to); + // remove $& and $N substitutions by replacing every $ with $$ + const value = to.replace(/\$/g, '$$$$'); + magicString.replace(new RegExp(`\\b${from}\\b`, 'g'), value); } return { diff --git a/packages/kit/src/core/adapt/builder.js b/packages/kit/src/core/adapt/builder.js index cf02857a0327..12a290f5b3ff 100644 --- a/packages/kit/src/core/adapt/builder.js +++ b/packages/kit/src/core/adapt/builder.js @@ -17,7 +17,7 @@ import path from 'node:path'; import { pipeline } from 'node:stream'; import { promisify } from 'node:util'; import zlib from 'node:zlib'; -import { copy, walk } from '../../utils/filesystem.js'; +import { copy, relative_path, walk } from '../../utils/filesystem.js'; import { posixify } from '../../utils/os.js'; import { generate_manifest } from '../generate_manifest/index.js'; import { get_route_segments } from '../../utils/routing.js'; @@ -27,6 +27,7 @@ import { find_server_assets } from '../generate_manifest/find_server_assets.js'; import { create_exported_declarations } from '../env.js'; import { handle_issues, validate } from '../../exports/internal/env.js'; import { get_mime_lookup } from '../utils.js'; +import { lookup as mime_lookup } from '../../utils/mime.js'; const pipe = promisify(pipeline); const extensions = [ @@ -117,7 +118,27 @@ export function create_builder({ routes, manifest: app_manifest, get mimeTypes() { - return get_mime_lookup(build_data.manifest_data); + const mime_types = get_mime_lookup(build_data.manifest_data); + const server_assets = find_server_assets( + build_data, + route_data.filter((route) => prerender_map.get(route.id) !== true), + vite_config.root + ); + /** @type {Record} */ + const files = {}; + for (const file of server_assets) { + files[file] = statSync(path.resolve(build_data.out_dir, 'server', file)).size; + + const ext = path.extname(file); + mime_types[ext] ??= mime_lookup(ext) || ''; + } + + // record extensions that only exist in prerendered output, e.g. a prerendered favicon.ico + for (const pathname of prerendered.paths) { + const ext = path.extname(pathname); + if (ext) mime_types[ext] ??= mime_lookup(ext) || ''; + } + return mime_types; }, async compress(directory) { @@ -193,20 +214,23 @@ export function create_builder({ generateManifest() { throw new Error( - `The \`generateManifest\` adapter API is deprecated — it has been replaced with \`writeServerEntrypoint\` and \`getManifest\`. You may need to update your adapter` + `The \`generateManifest\` adapter API is deprecated — it has been replaced with \`writeServerEntrypoint\` and \`builder.manifest\`. You may need to update your adapter` ); }, - writeServerEntrypoint(dest, { routes: subset } = {}) { - const relativePath = path.posix.relative(path.dirname(dest), this.getServerDirectory()); + writeServerEntrypoint(dest, { routes: subset, serverDirectory } = {}) { + const relative = relative_path( + path.dirname(dest), + serverDirectory ?? this.getServerDirectory() + ); write( dest, dedent` - import { Server } from '${relativePath}/index.js'; + import { Server } from '${relative}/index.js'; const manifest = ${generate_manifest({ build_data, prerendered: prerendered.paths, - relative_path: relativePath, + relative_path: relative, routes: subset ? subset.map((route) => /** @type {import('types').RouteData} */ (lookup.get(route))) : route_data.filter((route) => prerender_map.get(route.id) !== true), diff --git a/packages/kit/src/core/generate_manifest/index.js b/packages/kit/src/core/generate_manifest/index.js index f524e1da70d2..9edced21991d 100644 --- a/packages/kit/src/core/generate_manifest/index.js +++ b/packages/kit/src/core/generate_manifest/index.js @@ -1,4 +1,4 @@ -/** @import { RemoteChunk } from 'types' */ +/** @import { BuildData, RemoteChunk, RouteData } from 'types' */ import fs from 'node:fs'; import path from 'node:path'; import { lookup as mime_lookup } from '../../utils/mime.js'; @@ -15,10 +15,10 @@ import { uneval } from 'devalue'; * Generates the data used to write the server-side manifest.js file. This data is used in the Vite * build process, to power routing, etc. * @param {{ - * build_data: import('types').BuildData; + * build_data: BuildData; * prerendered: string[]; * relative_path: string; - * routes: import('types').RouteData[]; + * routes: RouteData[]; * remotes: RemoteChunk[]; * root: string; * }} opts diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index dfddcad02a01..6fff2dd40cb0 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -141,7 +141,7 @@ export interface Builder { routes: RouteDefinition[]; /** The value of the `$app/manifest` module */ manifest: typeof import('$app/manifest'); - /** */ + /** A record of file extensions to MIME types */ mimeTypes: Record; /** @@ -190,11 +190,13 @@ export interface Builder { * Writes a javascript file that initialises the SvelteKit [server](https://svelte.dev/docs/kit/@sveltejs-kit#Server). * @param dest * @param opts.routes A subset of the routes to include in the server's manifest + * @param opts.serverDirectory The directory containing the server code. Defaults to `getServerDirectory()`. */ writeServerEntrypoint: ( dest: string, opts?: { routes?: RouteDefinition[]; + serverDirectory?: string; } ) => void; /** diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index b410780768fc..b1c0cbbd38ef 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -127,7 +127,7 @@ declare module '@sveltejs/kit' { routes: RouteDefinition[]; /** The value of the `$app/manifest` module */ manifest: typeof import('$app/manifest'); - + /** A record of file extensions to MIME types */ mimeTypes: Record; /** @@ -174,11 +174,13 @@ declare module '@sveltejs/kit' { /** * Writes a javascript file that initialises the SvelteKit [server](https://svelte.dev/docs/kit/@sveltejs-kit#Server). * @param opts.routes A subset of the routes to include in the server's manifest + * @param opts.serverDirectory The directory containing the server code. Defaults to `getServerDirectory()`. */ writeServerEntrypoint: ( dest: string, opts?: { routes?: RouteDefinition[]; + serverDirectory?: string; } ) => void; /** From cb57f4a65efc9ebec9a1b4798dba8a4ed2f94ea2 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Fri, 21 Aug 2026 22:21:30 -0400 Subject: [PATCH 09/59] bun :( --- packages/adapter-bun/index.js | 8 ++++++-- packages/adapter-bun/internal.d.ts | 4 ++-- packages/adapter-bun/src/handler.js | 6 ++---- packages/adapter-bun/src/routes-util.js | 4 ++-- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/packages/adapter-bun/index.js b/packages/adapter-bun/index.js index abb7579f3f57..d5d4d71ec120 100644 --- a/packages/adapter-bun/index.js +++ b/packages/adapter-bun/index.js @@ -136,9 +136,13 @@ export default function (opts = {}) { const manifest_file = path.resolve(server, 'manifest.js'); const server_options_file = path.resolve(src_dir, 'options.js'); + const tmp = builder.getBuildDirectory('bun-tmp'); + fs.mkdirSync(tmp, { recursive: true }); + builder.writeServerEntrypoint(`${tmp}/server.js`); + const virtual_files = { [manifest_file]: - `export const manifest = ${builder.generateManifest({ relativePath: './' })};\n` + + `export const app_dir = ${JSON.stringify(builder.getAppPath())};\n` + `export const base = ${JSON.stringify(builder.config.paths.base || '/')};\n` + `export const embed = ${JSON.stringify(!!buildOptions.compile)};\n` + `export const env_prefix = ${JSON.stringify(envPrefix)};\n` + @@ -198,7 +202,7 @@ export default function (opts = {}) { name: 'adapter-bun', setup(build) { build.onResolve({ filter: /^(SERVER|MANIFEST|ROUTES|SERVER_OPTIONS)$/ }, ({ path }) => { - if (path === 'SERVER') return { path: `${server}/index.js` }; + if (path === 'SERVER') return { path: `${tmp}/server.js` }; if (path === 'MANIFEST') return { path: manifest_file }; if (path === 'ROUTES') return { path: routes_file }; if (path === 'SERVER_OPTIONS') return { path: server_options_file }; diff --git a/packages/adapter-bun/internal.d.ts b/packages/adapter-bun/internal.d.ts index b3e0ceed137a..ce532903b579 100644 --- a/packages/adapter-bun/internal.d.ts +++ b/packages/adapter-bun/internal.d.ts @@ -1,5 +1,5 @@ declare module 'MANIFEST' { - export const manifest: import('@sveltejs/kit').SSRManifest; + export const app_dir: string; export const base: string; export const embed: boolean; export const env_prefix: string; @@ -12,7 +12,7 @@ declare module 'ROUTES' { } declare module 'SERVER' { - export { Server } from '@sveltejs/kit'; + export const server: import('@sveltejs/kit').Server; } declare module 'SERVER_OPTIONS' { diff --git a/packages/adapter-bun/src/handler.js b/packages/adapter-bun/src/handler.js index d773849f2d56..6fa5d8c9a18a 100644 --- a/packages/adapter-bun/src/handler.js +++ b/packages/adapter-bun/src/handler.js @@ -1,11 +1,9 @@ /** @import { Server as BunServer } from 'bun' */ -import { Server } from 'SERVER'; -import { manifest, origin, env_prefix } from 'MANIFEST'; +import { server } from 'SERVER'; +import { origin, env_prefix } from 'MANIFEST'; import { server_assets } from 'ROUTES'; import { env, number_env } from './env.js'; -const server = new Server(manifest); - const address_header = env('ADDRESS_HEADER', '').toLowerCase(); const protocol_header = env('PROTOCOL_HEADER', '').toLowerCase(); const host_header = env('HOST_HEADER', '').toLowerCase(); diff --git a/packages/adapter-bun/src/routes-util.js b/packages/adapter-bun/src/routes-util.js index 942165cbdc13..3017e26a38f1 100644 --- a/packages/adapter-bun/src/routes-util.js +++ b/packages/adapter-bun/src/routes-util.js @@ -1,5 +1,5 @@ /** @import { BunFile, BunRequest, Serve } from 'bun' */ -import { manifest, base, embed } from 'MANIFEST'; +import { app_dir, base, embed } from 'MANIFEST'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; @@ -174,7 +174,7 @@ function file_route(file, meta, extra_headers = {}) { * @returns {Array<[string, RouteHandler]>} */ export function client_asset(url, filename = url, meta) { - const immutable = url.startsWith(`${manifest.appDir}/immutable/`); + const immutable = url.startsWith(`${app_dir}/immutable/`); const route = file_route( resolve_file('client', filename), meta, From ac11ed1f818dd887a0e7ece2ae226913138f691d Mon Sep 17 00:00:00 2001 From: Ottomated Date: Sun, 23 Aug 2026 18:44:47 -0700 Subject: [PATCH 10/59] fix bun tests --- packages/adapter-bun/test/adapter.spec.ts | 11 ++++++++--- packages/adapter-bun/test/handler.spec.ts | 12 ++---------- packages/adapter-bun/test/mocks.ts | 6 +++--- packages/adapter-bun/test/routes.spec.ts | 6 +++--- 4 files changed, 16 insertions(+), 19 deletions(-) diff --git a/packages/adapter-bun/test/adapter.spec.ts b/packages/adapter-bun/test/adapter.spec.ts index a5963fa9f94f..34c514151ac5 100644 --- a/packages/adapter-bun/test/adapter.spec.ts +++ b/packages/adapter-bun/test/adapter.spec.ts @@ -121,7 +121,7 @@ describe('Bun build configuration', () => { const files = bun_build.mock.calls[0][0].files; expect(files[manifest_file]).toBe( - 'export const manifest = {"appDir":"_app"};\n' + + 'export const app_dir = "/docs/_app";\n' + 'export const base = "/docs";\n' + 'export const embed = false;\n' + 'export const env_prefix = "APP_";\n' + @@ -130,7 +130,9 @@ describe('Bun build configuration', () => { expect(files[options_file]).toBe( 'export default {"hostname":"127.0.0.1","port":4000,"development":true};' ); - expect(builder.generateManifest).toHaveBeenCalledWith({ relativePath: './' }); + expect(builder.writeServerEntrypoint).toHaveBeenCalledWith( + '.svelte-kit/output/bun-tmp/server.js' + ); }); test('resolves generated runtime modules through the Bun plugin', async () => { @@ -144,7 +146,7 @@ describe('Bun build configuration', () => { ); const resolve_module = on_resolve.mock.calls[0][1]; expect(resolve_module({ path: 'SERVER' })).toEqual({ - path: '.svelte-kit/output/server/index.js' + path: '.svelte-kit/output/bun-tmp/server.js' }); expect(resolve_module({ path: 'MANIFEST' })).toEqual({ path: manifest_file }); expect(resolve_module({ path: 'ROUTES' })).toEqual({ path: routes_file }); @@ -588,6 +590,9 @@ function create_builder({ warn: mock((_message: string) => {}), info: mock((_message: string) => {}) }, + getAppPath: () => `${base}/_app`, + writeServerEntrypoint: mock(() => {}), + getBuildDirectory: (dir: string) => `.svelte-kit/output/${dir}`, getServerDirectory: () => '.svelte-kit/output/server', writeClient: mock(() => client_files), writePrerendered: mock(() => prerendered_files), diff --git a/packages/adapter-bun/test/handler.spec.ts b/packages/adapter-bun/test/handler.spec.ts index bc1bd272827f..7735ede19973 100644 --- a/packages/adapter-bun/test/handler.spec.ts +++ b/packages/adapter-bun/test/handler.spec.ts @@ -14,7 +14,6 @@ afterEach(() => { test('initializes SvelteKit with Bun environment variables and server-readable assets', async () => { const loaded = await load_handler(); - expect(loaded.construct).toHaveBeenCalledWith(loaded.manifest); expect(loaded.init).toHaveBeenCalledWith({ env: Bun.env, read: expect.any(Function) @@ -196,23 +195,18 @@ async function load_handler({ envPrefix = '', response = new Response('ok') }: { origin?: string; envPrefix?: string; response?: Response } = {}) { - const manifest = { appDir: '_app' }; const stream = new ReadableStream(); const asset = { stream: mock(() => stream) }; - const construct = mock((_value: unknown) => {}); const init = mock(async (_options: any) => {}); const respond = mock(async (_request: Request, _options: any) => response); class Server { - constructor(value: unknown) { - construct(value); - } init = init; respond = respond; } - mock.module('SERVER', () => ({ Server })); - mock_manifest({ manifest, origin, env_prefix: envPrefix }); + mock.module('SERVER', () => ({ server: new Server() })); + mock_manifest({ app_dir: '_app', origin, env_prefix: envPrefix }); mock_routes({ server_assets: new Map([['asset.txt', asset]]) }); const request_ip = mock((_request: Request): any => ({ @@ -227,9 +221,7 @@ async function load_handler({ return { handler, - manifest, stream, - construct, init, respond, request_ip, diff --git a/packages/adapter-bun/test/mocks.ts b/packages/adapter-bun/test/mocks.ts index 150e00cbd4d4..d1366ccad4b3 100644 --- a/packages/adapter-bun/test/mocks.ts +++ b/packages/adapter-bun/test/mocks.ts @@ -5,19 +5,19 @@ import { mock } from 'bun:test'; // exports that src modules pull from these build-generated specifiers export function mock_manifest({ - manifest, + app_dir, base, embed, origin, env_prefix = '' }: { - manifest?: unknown; + app_dir?: string; base?: string; embed?: boolean; origin?: string; env_prefix?: string; } = {}) { - mock.module('MANIFEST', () => ({ manifest, base, embed, origin, env_prefix })); + mock.module('MANIFEST', () => ({ app_dir, base, embed, origin, env_prefix })); } export function mock_routes({ diff --git a/packages/adapter-bun/test/routes.spec.ts b/packages/adapter-bun/test/routes.spec.ts index 1c736217457c..23fbc87a94c0 100644 --- a/packages/adapter-bun/test/routes.spec.ts +++ b/packages/adapter-bun/test/routes.spec.ts @@ -78,7 +78,7 @@ test('a root deployment registers routes with a leading slash', async () => { }); test('immutable SvelteKit assets receive a long-lived cache policy', async () => { - const { routes } = await load_routes({ appDir: '_app' }); + const { routes } = await load_routes({ app_dir: '_app' }); const request = new Request('http://localhost/'); const immutable = ( @@ -283,8 +283,8 @@ test('prerendered redirects retain their status and location', async () => { expect((handler as any).GET.headers.get('location')).toBe('/new'); }); -async function load_routes({ base = '/', embed = false, appDir = '_app' } = {}) { - mock_manifest({ manifest: { appDir }, base, embed }); +async function load_routes({ base = '/', embed = false, app_dir = '_app' } = {}) { + mock_manifest({ app_dir, base, embed }); // the real Bun.file runs, with the spy recording resolved paths; the files it // points at need not exist because nothing reads their contents const file = spyOn(Bun, 'file'); From f48dc2d333148bfaea9bf5b8a60fa014b72e0341 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Sun, 23 Aug 2026 19:00:35 -0700 Subject: [PATCH 11/59] fix bun again --- packages/adapter-bun/index.js | 2 +- packages/adapter-bun/test/adapter.spec.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/adapter-bun/index.js b/packages/adapter-bun/index.js index d5d4d71ec120..0cc2ede1059f 100644 --- a/packages/adapter-bun/index.js +++ b/packages/adapter-bun/index.js @@ -142,7 +142,7 @@ export default function (opts = {}) { const virtual_files = { [manifest_file]: - `export const app_dir = ${JSON.stringify(builder.getAppPath())};\n` + + `export const app_dir = ${JSON.stringify(builder.config.appDir)};\n` + `export const base = ${JSON.stringify(builder.config.paths.base || '/')};\n` + `export const embed = ${JSON.stringify(!!buildOptions.compile)};\n` + `export const env_prefix = ${JSON.stringify(envPrefix)};\n` + diff --git a/packages/adapter-bun/test/adapter.spec.ts b/packages/adapter-bun/test/adapter.spec.ts index 34c514151ac5..0115ede3916a 100644 --- a/packages/adapter-bun/test/adapter.spec.ts +++ b/packages/adapter-bun/test/adapter.spec.ts @@ -121,7 +121,7 @@ describe('Bun build configuration', () => { const files = bun_build.mock.calls[0][0].files; expect(files[manifest_file]).toBe( - 'export const app_dir = "/docs/_app";\n' + + 'export const app_dir = "_app";\n' + 'export const base = "/docs";\n' + 'export const embed = false;\n' + 'export const env_prefix = "APP_";\n' + @@ -578,7 +578,7 @@ function create_builder({ instrumentation?: boolean; } = {}) { return { - config: { outDir: '.svelte-kit', paths: { base, origin } }, + config: { outDir: '.svelte-kit', paths: { base, origin }, appDir: '_app' }, routes, prerendered: { pages: new Map(prerendered_pages), From 687f11e7f2adfc8c83059ccecead63020bef9a64 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Mon, 24 Aug 2026 10:47:16 -0700 Subject: [PATCH 12/59] add service worker to manifest --- packages/kit/src/core/adapt/builder.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/kit/src/core/adapt/builder.js b/packages/kit/src/core/adapt/builder.js index 12a290f5b3ff..fe6193ab66c6 100644 --- a/packages/kit/src/core/adapt/builder.js +++ b/packages/kit/src/core/adapt/builder.js @@ -106,6 +106,9 @@ export function create_builder({ return facade; }); + if (build_data.service_worker) { + app_manifest.assets.push({ path: build_data.service_worker }); + } return { log, From 4b231f3330430a1ce650aff65e7bf6a42d1784c3 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Mon, 24 Aug 2026 11:02:16 -0700 Subject: [PATCH 13/59] fix broken tests --- packages/kit/src/core/adapt/builder.spec.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/kit/src/core/adapt/builder.spec.js b/packages/kit/src/core/adapt/builder.spec.js index af76faba6d45..226e6436db42 100644 --- a/packages/kit/src/core/adapt/builder.spec.js +++ b/packages/kit/src/core/adapt/builder.spec.js @@ -54,8 +54,9 @@ test('copy files', () => { }); test('compress files', async () => { - // @ts-expect-error - we don't need the whole config for this test const builder = create_builder({ + // @ts-expect-error - we don't need the whole config for this test + build_data: {}, route_data: [] }); @@ -77,8 +78,9 @@ test('compress files', async () => { }); test('compress returns an empty array for a directory that does not exist', async () => { - // @ts-expect-error - we don't need the whole config for this test const builder = create_builder({ + // @ts-expect-error - we don't need the whole config for this test + build_data: {}, route_data: [] }); @@ -100,8 +102,11 @@ test('instrument generates facade with posix paths', () => { const entrypoint = join(dest, 'index.js'); const instrumentation = join(dest, 'server', 'instrumentation.server.js'); - // @ts-expect-error - we don't need the whole config for this test - const builder = create_builder({ route_data: [] }); + const builder = create_builder({ + // @ts-expect-error - we don't need the whole config for this test + build_data: {}, + route_data: [] + }); builder.instrument({ entrypoint, From c9fb18d909b066d49d94f6a5f87025b222cde0f9 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Thu, 20 Aug 2026 17:56:19 -0400 Subject: [PATCH 14/59] breaking: remove `SSRManifest` from public types --- .changeset/flat-cases-see.md | 6 ++++ .../kit/src/core/generate_manifest/index.js | 2 +- packages/kit/src/core/postbuild/analyse.js | 2 +- packages/kit/src/core/postbuild/fallback.js | 2 +- packages/kit/src/core/postbuild/prerender.js | 2 +- packages/kit/src/exports/public.d.ts | 29 +------------------ packages/kit/src/exports/vite/dev/index.js | 4 +-- .../kit/src/exports/vite/preview/index.js | 2 +- packages/kit/src/runtime/server/data/index.js | 2 +- packages/kit/src/runtime/server/fetch.js | 4 +-- packages/kit/src/runtime/server/index.js | 4 +-- packages/kit/src/runtime/server/internal.js | 3 +- packages/kit/src/runtime/server/page/index.js | 4 +-- .../kit/src/runtime/server/page/render.js | 2 +- .../runtime/server/page/respond_with_error.js | 2 +- .../src/runtime/server/page/server_routing.js | 2 +- .../src/runtime/server/remote-functions.js | 4 +-- packages/kit/src/runtime/server/respond.js | 4 +-- packages/kit/src/runtime/server/utils.js | 2 +- packages/kit/src/types/ambient-private.d.ts | 3 +- packages/kit/src/types/internal.d.ts | 28 +++++++++++++++++- packages/kit/types/index.d.ts | 26 ++++++++--------- 22 files changed, 71 insertions(+), 68 deletions(-) create mode 100644 .changeset/flat-cases-see.md diff --git a/.changeset/flat-cases-see.md b/.changeset/flat-cases-see.md new file mode 100644 index 000000000000..bcee64ec0c38 --- /dev/null +++ b/.changeset/flat-cases-see.md @@ -0,0 +1,6 @@ +--- +"@sveltejs/kit": major +--- + +breaking: remove `SSRManifest` from public types + \ No newline at end of file diff --git a/packages/kit/src/core/generate_manifest/index.js b/packages/kit/src/core/generate_manifest/index.js index 9edced21991d..d57ec766667d 100644 --- a/packages/kit/src/core/generate_manifest/index.js +++ b/packages/kit/src/core/generate_manifest/index.js @@ -102,7 +102,7 @@ export function generate_manifest({ // prettier-ignore // String representation of - /** @template {import('@sveltejs/kit').SSRManifest} T */ + /** @template {import('types').SSRManifest} T */ const manifest_expr = dedent` { appDir: ${s(build_data.app_dir)}, diff --git a/packages/kit/src/core/postbuild/analyse.js b/packages/kit/src/core/postbuild/analyse.js index dde9f386195e..58aa74c3e68d 100644 --- a/packages/kit/src/core/postbuild/analyse.js +++ b/packages/kit/src/core/postbuild/analyse.js @@ -34,7 +34,7 @@ async function analyse({ remotes, vite_config_file }) { - /** @type {import('@sveltejs/kit').SSRManifest} */ + /** @type {import('types').SSRManifest} */ const manifest = (await import(pathToFileURL(manifest_path).href)).manifest; const vite_config = await load_vite_config(vite_config_file); diff --git a/packages/kit/src/core/postbuild/fallback.js b/packages/kit/src/core/postbuild/fallback.js index 1023d7890aec..335a72bfd049 100644 --- a/packages/kit/src/core/postbuild/fallback.js +++ b/packages/kit/src/core/postbuild/fallback.js @@ -24,7 +24,7 @@ async function generate_fallback({ manifest_path, env, out_dir, origin, assets } /** @type {import('types').ServerModule} */ const { Server } = await import(pathToFileURL(`${server_root}/server/index.js`).href); - /** @type {import('@sveltejs/kit').SSRManifest} */ + /** @type {import('types').SSRManifest} */ const manifest = (await import(pathToFileURL(manifest_path).href)).manifest; set_building(); diff --git a/packages/kit/src/core/postbuild/prerender.js b/packages/kit/src/core/postbuild/prerender.js index 66010667be86..c16abe321393 100644 --- a/packages/kit/src/core/postbuild/prerender.js +++ b/packages/kit/src/core/postbuild/prerender.js @@ -48,7 +48,7 @@ async function prerender({ vite_config_file, is_tty }) { - /** @type {import('@sveltejs/kit').SSRManifest} */ + /** @type {import('types').SSRManifest} */ const manifest = (await import(pathToFileURL(manifest_path).href)).manifest; /** @type {import('types').ServerInternalModule} */ diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index 6fff2dd40cb0..d67b1ced681f 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -12,10 +12,9 @@ import { RequestOptions, RouteSegment } from '../types/private.js'; -import { BuildData, SSRNodeLoader, SSRRoute, ValidatedConfig } from 'types'; +import { SSRManifest, ValidatedConfig } from 'types'; import { Plugin } from 'vite'; import { RouteId as AppRouteId, LayoutParams as AppLayoutParams } from '$app/types'; -import { ParamMatcher } from '@sveltejs/kit/params'; import { StandardSchemaV1 } from '@standard-schema/spec'; export { PrerenderOption } from '../types/private.js'; @@ -687,32 +686,6 @@ export interface ServerInitOptions { read?: (file: string) => MaybePromise; } -/** - * Information required to instantiate a new `Server` instance. - */ -export interface SSRManifest { - /** The directory where SvelteKit keeps its stuff, including static assets (such as JS and CSS) and internally-used routes. */ - appDir: string; - /** The `base` and `appDir` settings combined without a leading slash. */ - appPath: string; - /** Static files from `config.files.assets` and the service worker (if any). */ - assets: Set; - mimeTypes: Record; - - /** @internal private fields */ - _: { - client: BuildData['client']; - nodes: SSRNodeLoader[]; - /** hashed filename -> import to that file */ - remotes: Record Promise<{ default: Record }>>; - routes: SSRRoute[]; - prerendered_routes: Set; - matchers: () => Promise>; - /** A `[file]: size` map of all assets imported by server code. */ - server_assets: Record; - }; -} - /** * The generic form of `PageServerLoad` and `LayoutServerLoad`. You should import those from `./$types` (see [generated types](https://svelte.dev/docs/kit/types#Generated-types)) * rather than using `ServerLoad` directly. diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 42b901d3abcd..92bd9c96e02b 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -1,6 +1,6 @@ -/** @import { RequestEvent, SSRManifest } from '@sveltejs/kit' */ +/** @import { RequestEvent } from '@sveltejs/kit' */ /** @import { EnvironmentModuleNode, ErrorPayload, ViteDevServer } from 'vite' */ -/** @import { ManifestData, PrerenderOption, RemoteChunk, ServerModule, SSRNode, UniversalNode, ValidatedConfig } from 'types' */ +/** @import { ManifestData, PrerenderOption, RemoteChunk, ServerModule, SSRNode, UniversalNode, ValidatedConfig, SSRManifest } from 'types' */ import process from 'node:process'; import fs from 'node:fs'; import path from 'node:path'; diff --git a/packages/kit/src/exports/vite/preview/index.js b/packages/kit/src/exports/vite/preview/index.js index 4e0187c9e1b7..c1803f66faaf 100644 --- a/packages/kit/src/exports/vite/preview/index.js +++ b/packages/kit/src/exports/vite/preview/index.js @@ -43,7 +43,7 @@ export async function preview(vite, svelte_config) { /** @type {ServerModule} */ const { Server } = await import(pathToFileURL(join(dir, 'index.js')).href); - /** @type {{ manifest: import('@sveltejs/kit').SSRManifest }} */ + /** @type {{ manifest: import('types').SSRManifest }} */ const { manifest } = await import(pathToFileURL(join(dir, 'manifest.js')).href); set_assets(assets); diff --git a/packages/kit/src/runtime/server/data/index.js b/packages/kit/src/runtime/server/data/index.js index 087d52c8fabd..c4e3da6e6475 100644 --- a/packages/kit/src/runtime/server/data/index.js +++ b/packages/kit/src/runtime/server/data/index.js @@ -13,7 +13,7 @@ import { with_version_header } from '../utils.js'; * @param {import('@sveltejs/kit').RequestEvent} event * @param {import('types').RequestState} state * @param {{ page: Pick | null }} route - * @param {import('@sveltejs/kit').SSRManifest} manifest + * @param {import('types').SSRManifest} manifest * @param {boolean[] | undefined} invalidated_data_nodes * @param {import('types').TrailingSlash} trailing_slash * @returns {Promise} diff --git a/packages/kit/src/runtime/server/fetch.js b/packages/kit/src/runtime/server/fetch.js index 2d74f65084ee..01e2b8d34aef 100644 --- a/packages/kit/src/runtime/server/fetch.js +++ b/packages/kit/src/runtime/server/fetch.js @@ -9,7 +9,7 @@ import { fork_state_for_subrequest } from './state.js'; /** * @param {{ * event: import('@sveltejs/kit').RequestEvent; - * manifest: import('@sveltejs/kit').SSRManifest; + * manifest: import('types').SSRManifest; * state: import('types').RequestState; * get_cookie_header: (url: URL, header: string | null) => string; * set_internal: (name: string, value: string, opts: import('./page/types.js').Cookie['options']) => void; @@ -193,7 +193,7 @@ function normalize_fetch_input(info, init, url) { /** * @param {Request} request - * @param {import('@sveltejs/kit').SSRManifest} manifest + * @param {import('types').SSRManifest} manifest * @param {import('types').RequestState} state * @returns {Promise} */ diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js index 0aa0213086f7..2274671925b2 100644 --- a/packages/kit/src/runtime/server/index.js +++ b/packages/kit/src/runtime/server/index.js @@ -58,10 +58,10 @@ if (DEV) { } export class Server { - /** @type {import('@sveltejs/kit').SSRManifest} */ + /** @type {import('types').SSRManifest} */ #manifest; - /** @param {import('@sveltejs/kit').SSRManifest} manifest */ + /** @param {import('types').SSRManifest} manifest */ constructor(manifest) { this.#manifest = manifest; diff --git a/packages/kit/src/runtime/server/internal.js b/packages/kit/src/runtime/server/internal.js index 2871b0d1e578..943fc4546efb 100644 --- a/packages/kit/src/runtime/server/internal.js +++ b/packages/kit/src/runtime/server/internal.js @@ -1,5 +1,4 @@ -/** @import { SSRManifest } from '@sveltejs/kit'; */ -/** @import { ServerHooks, SSROptions } from 'types'; */ +/** @import { ServerHooks, SSROptions, SSRManifest } from 'types'; */ import { restore, save } from './dev.js'; import { has_data_suffix, diff --git a/packages/kit/src/runtime/server/page/index.js b/packages/kit/src/runtime/server/page/index.js index 11e40e0eb553..a190c8d0c709 100644 --- a/packages/kit/src/runtime/server/page/index.js +++ b/packages/kit/src/runtime/server/page/index.js @@ -1,5 +1,5 @@ -/** @import { RequestEvent, SSRManifest } from '@sveltejs/kit' */ -/** @import { PageNodeIndexes, RequestState, RequiredResolveOptions, ServerDataNode, SSRNode } from 'types' */ +/** @import { RequestEvent } from '@sveltejs/kit' */ +/** @import { PageNodeIndexes, RequestState, RequiredResolveOptions, ServerDataNode, SSRNode, SSRManifest } from 'types' */ import { text } from '@sveltejs/kit'; import { Redirect } from '@sveltejs/kit/internal'; import { compact } from '../../../utils/array.js'; diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index 06d585dbd233..f995e8ec8cc5 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -36,7 +36,7 @@ import { options } from '../internal.js'; * @param {{ * branch: Array; * fetched: Array; - * manifest: import('@sveltejs/kit').SSRManifest; + * manifest: import('types').SSRManifest; * page_config: { ssr: boolean; csr: boolean }; * status: number; * error: App.Error | null; diff --git a/packages/kit/src/runtime/server/page/respond_with_error.js b/packages/kit/src/runtime/server/page/respond_with_error.js index a5e4f3b06e79..fc416a35a977 100644 --- a/packages/kit/src/runtime/server/page/respond_with_error.js +++ b/packages/kit/src/runtime/server/page/respond_with_error.js @@ -14,7 +14,7 @@ import { server_data_serializer } from './data_serializer.js'; * @param {{ * event: import('@sveltejs/kit').RequestEvent; * state: import('types').RequestState; - * manifest: import('@sveltejs/kit').SSRManifest; + * manifest: import('types').SSRManifest; * error: unknown; * resolve_opts: import('types').RequiredResolveOptions; * }} opts diff --git a/packages/kit/src/runtime/server/page/server_routing.js b/packages/kit/src/runtime/server/page/server_routing.js index aa69cad3939a..58d50a9a9a01 100644 --- a/packages/kit/src/runtime/server/page/server_routing.js +++ b/packages/kit/src/runtime/server/page/server_routing.js @@ -1,4 +1,4 @@ -/** @import { SSRManifest } from '@sveltejs/kit' */ +/** @import { SSRManifest } from 'types' */ import { base, assets } from '#app/paths'; import { relative } from '$app/paths/internal/server'; import { text } from '@sveltejs/kit'; diff --git a/packages/kit/src/runtime/server/remote-functions.js b/packages/kit/src/runtime/server/remote-functions.js index f8ac78e2a543..f60bcf4f6636 100644 --- a/packages/kit/src/runtime/server/remote-functions.js +++ b/packages/kit/src/runtime/server/remote-functions.js @@ -1,6 +1,6 @@ -/** @import { RequestEvent, SSRManifest } from '@sveltejs/kit' */ +/** @import { RequestEvent } from '@sveltejs/kit' */ /** @import { RemoteForm } from '$app/server' */ -/** @import { RemoteFormInternals, RemoteFunctionData, RemoteFunctionResponse, RemoteInternals, RequestState, ServerActionResult } from 'types' */ +/** @import { RemoteFormInternals, RemoteFunctionData, RemoteFunctionResponse, RemoteInternals, RequestState, ServerActionResult, SSRManifest } from 'types' */ import { error } from '@sveltejs/kit'; import { Redirect, SvelteKitError } from '@sveltejs/kit/internal'; diff --git a/packages/kit/src/runtime/server/respond.js b/packages/kit/src/runtime/server/respond.js index 0f479b1eaeef..cf8dd9a366b2 100644 --- a/packages/kit/src/runtime/server/respond.js +++ b/packages/kit/src/runtime/server/respond.js @@ -86,7 +86,7 @@ export const respond = propagate_context(internal_respond); /** * @param {Request} request - * @param {import('@sveltejs/kit').SSRManifest} manifest + * @param {import('types').SSRManifest} manifest * @param {import('types').RequestState} state * @returns {Promise} */ @@ -824,7 +824,7 @@ export async function internal_respond(request, manifest, state) { /** * @param {import('types').PageNodeIndexes} page - * @param {import('@sveltejs/kit').SSRManifest} manifest + * @param {import('types').SSRManifest} manifest */ export function load_page_nodes(page, manifest) { return Promise.all([ diff --git a/packages/kit/src/runtime/server/utils.js b/packages/kit/src/runtime/server/utils.js index 371f0e7ec6da..c76997a36cb5 100644 --- a/packages/kit/src/runtime/server/utils.js +++ b/packages/kit/src/runtime/server/utils.js @@ -98,7 +98,7 @@ export function serialize_uses(node) { /** * Returns `true` if the given path was prerendered - * @param {import('@sveltejs/kit').SSRManifest} manifest + * @param {import('types').SSRManifest} manifest * @param {string} pathname Should include the base and be decoded */ export function has_prerendered_path(manifest, pathname) { diff --git a/packages/kit/src/types/ambient-private.d.ts b/packages/kit/src/types/ambient-private.d.ts index 2aebbe2fab9a..74ffbddbebf3 100644 --- a/packages/kit/src/types/ambient-private.d.ts +++ b/packages/kit/src/types/ambient-private.d.ts @@ -1,7 +1,6 @@ /** Internal version of $app/server */ declare module '/server.js' { - import { SSRManifest } from '@sveltejs/kit'; - import { SSROptions, ServerHooks } from 'types'; + import { SSROptions, ServerHooks, SSRManifest } from 'types'; export const options: SSROptions; export const get_hooks: () => Promise>; diff --git a/packages/kit/src/types/internal.d.ts b/packages/kit/src/types/internal.d.ts index cdcfa5000b93..d91cf9e1d4c3 100644 --- a/packages/kit/src/types/internal.d.ts +++ b/packages/kit/src/types/internal.d.ts @@ -7,12 +7,12 @@ import { ServerInitOptions, Actions, RequestEvent, - SSRManifest, Emulator, HttpError } from '@sveltejs/kit'; import { RemoteFormIssue, RemoteQuery, RemoteLiveQuery } from '$app/server'; import { Config } from '@sveltejs/kit/vite'; +import { ParamMatcher } from '@sveltejs/kit/params'; import { ClientInit, Handle, @@ -457,6 +457,32 @@ export interface ServerNode { entries?: PrerenderEntryGenerator; } +/** + * Information required to instantiate a new `Server` instance. + */ +export interface SSRManifest { + /** The directory where SvelteKit keeps its stuff, including static assets (such as JS and CSS) and internally-used routes. */ + appDir: string; + /** The `base` and `appDir` settings combined without a leading slash. */ + appPath: string; + /** Static files from `config.files.assets` and the service worker (if any). */ + assets: Set; + mimeTypes: Record; + + /** @internal private fields */ + _: { + client: BuildData['client']; + nodes: SSRNodeLoader[]; + /** hashed filename -> import to that file */ + remotes: Record Promise<{ default: Record }>>; + routes: SSRRoute[]; + prerendered_routes: Set; + matchers: () => Promise>; + /** A `[file]: size` map of all assets imported by server code. */ + server_assets: Record; + }; +} + export interface SSRNode { /** index into the `nodes` array in the generated `client/app.js`. */ index: number; diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index b1c0cbbd38ef..94763511b64f 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -670,19 +670,6 @@ declare module '@sveltejs/kit' { read?: (file: string) => MaybePromise; } - /** - * Information required to instantiate a new `Server` instance. - */ - export interface SSRManifest { - /** The directory where SvelteKit keeps its stuff, including static assets (such as JS and CSS) and internally-used routes. */ - appDir: string; - /** The `base` and `appDir` settings combined without a leading slash. */ - appPath: string; - /** Static files from `config.files.assets` and the service worker (if any). */ - assets: Set; - mimeTypes: Record; - } - /** * The generic form of `PageServerLoad` and `LayoutServerLoad`. You should import those from `./$types` (see [generated types](https://svelte.dev/docs/kit/types#Generated-types)) * rather than using `ServerLoad` directly. @@ -922,6 +909,19 @@ declare module '@sveltejs/kit' { : T[K]; // Use the exact type for everything else }; + /** + * Information required to instantiate a new `Server` instance. + */ + interface SSRManifest { + /** The directory where SvelteKit keeps its stuff, including static assets (such as JS and CSS) and internally-used routes. */ + appDir: string; + /** The `base` and `appDir` settings combined without a leading slash. */ + appPath: string; + /** Static files from `config.files.assets` and the service worker (if any). */ + assets: Set; + mimeTypes: Record; + } + type ValidatedConfig = RecursiveRequired> & { preprocess: Config['preprocess']; }; From 6eeee7548df8de66456fd27360ccc653c65119a4 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Thu, 20 Aug 2026 18:07:50 -0400 Subject: [PATCH 15/59] chore: flatten SSRManifest --- .../kit/src/core/generate_manifest/index.js | 77 +++-- packages/kit/src/core/postbuild/analyse.js | 6 +- packages/kit/src/core/postbuild/prerender.js | 2 +- packages/kit/src/exports/vite/dev/index.js | 306 +++++++++--------- .../kit/src/exports/vite/preview/index.js | 2 +- packages/kit/src/runtime/app/paths/server.js | 4 +- packages/kit/src/runtime/app/server/index.js | 6 +- packages/kit/src/runtime/server/data/index.js | 2 +- packages/kit/src/runtime/server/fetch.js | 12 +- packages/kit/src/runtime/server/page/index.js | 6 +- .../kit/src/runtime/server/page/render.js | 2 +- .../runtime/server/page/respond_with_error.js | 4 +- .../src/runtime/server/page/server_routing.js | 24 +- .../src/runtime/server/remote-functions.js | 4 +- packages/kit/src/runtime/server/respond.js | 14 +- packages/kit/src/runtime/server/utils.js | 4 +- packages/kit/src/types/internal.d.ts | 29 +- packages/kit/types/index.d.ts | 250 +++++++++++++- 18 files changed, 495 insertions(+), 259 deletions(-) diff --git a/packages/kit/src/core/generate_manifest/index.js b/packages/kit/src/core/generate_manifest/index.js index d57ec766667d..6d199a8e3353 100644 --- a/packages/kit/src/core/generate_manifest/index.js +++ b/packages/kit/src/core/generate_manifest/index.js @@ -105,46 +105,45 @@ export function generate_manifest({ /** @template {import('types').SSRManifest} T */ const manifest_expr = dedent` { - appDir: ${s(build_data.app_dir)}, - appPath: ${s(build_data.app_path)}, + app_dir: ${s(build_data.app_dir)}, + app_path: ${s(build_data.app_path)}, assets: new Set(${s(assets)}), - mimeTypes: ${s(mime_types)}, - _: { - client: ${uneval(build_data.client)}, - nodes: [ - ${(node_paths).map(loader).join(',\n')} - ], - remotes: { - ${remotes.map((remote) => `'${remote.hash}': ${loader(join_relative(relative_path, `chunks/remote-${remote.hash}.js`))}`).join(',\n')} - }, - routes: [ - ${routes.map(route => { - if (!route.page && !route.endpoint) return; - - return dedent` - { - id: ${s(route.id)}, - pattern: ${route.pattern}, - params: ${s(route.params)}, - page: ${route.page ? `{ layouts: ${get_nodes(route.page.layouts)}, errors: ${get_nodes(route.page.errors)}, leaf: ${reindexed.get(route.page.leaf)} }` : 'null'}, - endpoint: ${route.endpoint ? loader(join_relative(relative_path, resolve_symlinks(build_data.server_manifest, route.endpoint.file, root).chunk.file)) : 'null'} - } - `; - }).filter(Boolean).join(',\n')} - ], - prerendered_routes: new Set(${s(prerendered)}), - matchers: async () => { - ${ - uses_matchers && build_data.manifest_data.params - ? dedent` - const { params } = await import('${join_relative(relative_path, '/entries/params.js')}'); - return params; - ` - : 'return {};' - } - }, - server_assets: ${s(files)} - } + mime_types: ${s(mime_types)}, + client: ${uneval(build_data.client)}, + + nodes: [ + ${(node_paths).map(loader).join(',\n')} + ], + remotes: { + ${remotes.map((remote) => `'${remote.hash}': ${loader(join_relative(relative_path, `chunks/remote-${remote.hash}.js`))}`).join(',\n')} + }, + routes: [ + ${routes.map(route => { + if (!route.page && !route.endpoint) return; + + return dedent` + { + id: ${s(route.id)}, + pattern: ${route.pattern}, + params: ${s(route.params)}, + page: ${route.page ? `{ layouts: ${get_nodes(route.page.layouts)}, errors: ${get_nodes(route.page.errors)}, leaf: ${reindexed.get(route.page.leaf)} }` : 'null'}, + endpoint: ${route.endpoint ? loader(join_relative(relative_path, resolve_symlinks(build_data.server_manifest, route.endpoint.file, root).chunk.file)) : 'null'} + } + `; + }).filter(Boolean).join(',\n')} + ], + prerendered_routes: new Set(${s(prerendered)}), + matchers: async () => { + ${ + uses_matchers && build_data.manifest_data.params + ? dedent` + const { params } = await import('${join_relative(relative_path, '/entries/params.js')}'); + return params; + ` + : 'return {};' + } + }, + server_assets: ${s(files)} } `; diff --git a/packages/kit/src/core/postbuild/analyse.js b/packages/kit/src/core/postbuild/analyse.js index 58aa74c3e68d..b28d8efe42de 100644 --- a/packages/kit/src/core/postbuild/analyse.js +++ b/packages/kit/src/core/postbuild/analyse.js @@ -65,7 +65,7 @@ async function analyse({ remotes: new Map() }; - const nodes = await Promise.all(manifest._.nodes.map((loader) => loader())); + const nodes = await Promise.all(manifest.nodes.map((loader) => loader())); // analyse nodes for (const node of nodes) { @@ -88,7 +88,7 @@ async function analyse({ } // analyse routes - for (const route of manifest._.routes) { + for (const route of manifest.routes) { const page = route.page && analyse_page( @@ -147,7 +147,7 @@ async function analyse({ // analyse remotes for (const remote of remotes) { - const loader = manifest._.remotes[remote.hash]; + const loader = manifest.remotes[remote.hash]; const { default: functions } = await loader(); const exports = new Map(); diff --git a/packages/kit/src/core/postbuild/prerender.js b/packages/kit/src/core/postbuild/prerender.js index c16abe321393..2d07b4df5e17 100644 --- a/packages/kit/src/core/postbuild/prerender.js +++ b/packages/kit/src/core/postbuild/prerender.js @@ -659,7 +659,7 @@ async function prerender({ /** @type {Array} */ const prerender_functions = []; - for (const loader of Object.values(manifest._.remotes)) { + for (const loader of Object.values(manifest.remotes)) { const module = await loader(); for (const fn of Object.values(module.default)) { diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 92bd9c96e02b..2a82321e387a 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -181,176 +181,172 @@ export async function dev( } manifest = { - appDir: svelte_config.appDir, - appPath: svelte_config.appDir, + app_dir: svelte_config.appDir, + app_path: svelte_config.appDir, assets: new Set(manifest_data.assets.map((asset) => asset.file)), - mimeTypes: get_mime_lookup(manifest_data), - _: { - client: { - start: `${get_runtime_base(root)}/client/entry.js`, - app: `${to_fs(svelte_config.outDir)}/generated/dev/client/app.js`, - imports: [], - stylesheets: [], - fonts: [], - uses_env_dynamic_public: true, - nodes: - svelte_config.router.resolution === 'client' - ? undefined - : manifest_data.nodes.map((node, i) => { - if (node.component || node.universal) { - return `${svelte_config.paths.base}${to_fs(svelte_config.outDir)}/generated/dev/client/nodes/${i}.js`; - } - }), - // `css` is not necessary in dev, as the JS file from `nodes` will reference the CSS file - routes: - svelte_config.router.resolution === 'client' - ? undefined - : compact( - manifest_data.routes.map((route) => { - if (!route.page) return; - - return { - id: route.id, - pattern: route.pattern, - params: route.params, - layouts: route.page.layouts.map((l) => - l !== undefined ? [!!manifest_data.nodes[l].server, l] : undefined - ), - errors: route.page.errors, - leaf: [!!manifest_data.nodes[route.page.leaf].server, route.page.leaf] - }; - }) - ) - }, - server_assets: new Proxy( - {}, - { - has: (_, /** @type {string} */ file) => fs.existsSync(from_fs(file)), - get: (_, /** @type {string} */ file) => fs.statSync(from_fs(file)).size + mime_types: get_mime_lookup(manifest_data), + client: { + start: `${get_runtime_base(root)}/client/entry.js`, + app: `${to_fs(svelte_config.outDir)}/generated/dev/client/app.js`, + imports: [], + stylesheets: [], + fonts: [], + uses_env_dynamic_public: true, + nodes: + svelte_config.router.resolution === 'client' + ? undefined + : manifest_data.nodes.map((node, i) => { + if (node.component || node.universal) { + return `${svelte_config.paths.base}${to_fs(svelte_config.outDir)}/generated/dev/client/nodes/${i}.js`; + } + }), + // `css` is not necessary in dev, as the JS file from `nodes` will reference the CSS file + routes: + svelte_config.router.resolution === 'client' + ? undefined + : compact( + manifest_data.routes.map((route) => { + if (!route.page) return; + + return { + id: route.id, + pattern: route.pattern, + params: route.params, + layouts: route.page.layouts.map((l) => + l !== undefined ? [!!manifest_data.nodes[l].server, l] : undefined + ), + errors: route.page.errors, + leaf: [!!manifest_data.nodes[route.page.leaf].server, route.page.leaf] + }; + }) + ) + }, + server_assets: new Proxy( + {}, + { + has: (_, /** @type {string} */ file) => fs.existsSync(from_fs(file)), + get: (_, /** @type {string} */ file) => fs.statSync(from_fs(file)).size + } + ), + nodes: manifest_data.nodes.map((node, index) => { + return async () => { + const result = /** @type {SSRNode} */ ({}); + result.index = index; + result.universal_id = node.universal; + result.server_id = node.server; + + // these are unused in dev, but it's easier to include them + result.imports = []; + result.stylesheets = []; + result.fonts = []; + + /** @type {EnvironmentModuleNode[]} */ + const module_nodes = []; + + if (node.component) { + result.component = async () => { + const { module_node, module } = await resolve(/** @type {string} */ (node.component)); + + module_nodes.push(module_node); + + return module.default; + }; } - ), - nodes: manifest_data.nodes.map((node, index) => { - return async () => { - const result = /** @type {SSRNode} */ ({}); - result.index = index; - result.universal_id = node.universal; - result.server_id = node.server; - - // these are unused in dev, but it's easier to include them - result.imports = []; - result.stylesheets = []; - result.fonts = []; - - /** @type {EnvironmentModuleNode[]} */ - const module_nodes = []; - - if (node.component) { - result.component = async () => { - const { module_node, module } = await resolve( - /** @type {string} */ (node.component) - ); - - module_nodes.push(module_node); - - return module.default; - }; - } - if (node.universal) { - if (node.page_options?.ssr === false) { - result.universal = /** @type {UniversalNode} */ (node.page_options); - } else { - // TODO: explain why the file was loaded on the server if we fail to load it - const { module, module_node } = await resolve(node.universal); - module_nodes.push(module_node); - result.universal = module; - } + if (node.universal) { + if (node.page_options?.ssr === false) { + result.universal = /** @type {UniversalNode} */ (node.page_options); + } else { + // TODO: explain why the file was loaded on the server if we fail to load it + const { module, module_node } = await resolve(node.universal); + module_nodes.push(module_node); + result.universal = module; } + } - if (node.server) { - const { module } = await resolve(node.server); - result.server = module; - } + if (node.server) { + const { module } = await resolve(node.server); + result.server = module; + } - // in dev we inline all styles to avoid FOUC. this gets populated lazily so that - // components/stylesheets loaded via import() during `load` are included - result.inline_styles = async () => { - /** @type {Set} */ - const deps = new Set(); + // in dev we inline all styles to avoid FOUC. this gets populated lazily so that + // components/stylesheets loaded via import() during `load` are included + result.inline_styles = async () => { + /** @type {Set} */ + const deps = new Set(); - for (const module_node of module_nodes) { - await find_deps(vite_dev_server, module_node, deps); - } + for (const module_node of module_nodes) { + await find_deps(vite_dev_server, module_node, deps); + } - /** @type {Record} */ - const styles = {}; - - for (const dep of deps) { - if (vite.isCSSRequest(dep.url) && !vite_css_query_regex.test(dep.url)) { - const inlineCssUrl = dep.url.includes('?') - ? dep.url.replace('?', '?inline&') - : dep.url + '?inline'; - try { - const mod = await runner.import(inlineCssUrl); - styles[dep.url] = mod.default; - } catch { - // this can happen with dynamically imported modules, I think - // because the Vite module graph doesn't distinguish between - // static and dynamic imports? TODO investigate, submit fix - } + /** @type {Record} */ + const styles = {}; + + for (const dep of deps) { + if (vite.isCSSRequest(dep.url) && !vite_css_query_regex.test(dep.url)) { + const inlineCssUrl = dep.url.includes('?') + ? dep.url.replace('?', '?inline&') + : dep.url + '?inline'; + try { + const mod = await runner.import(inlineCssUrl); + styles[dep.url] = mod.default; + } catch { + // this can happen with dynamically imported modules, I think + // because the Vite module graph doesn't distinguish between + // static and dynamic imports? TODO investigate, submit fix } } + } - return styles; - }; - - return result; + return styles; }; - }), - prerendered_routes: new Set(), - get remotes() { - return Object.fromEntries( - get_remotes().map((remote) => [ - remote.hash, - () => runner.import(remote.file).then((module) => ({ default: module })) - ]) - ); - }, - routes: compact( - manifest_data.routes.map((route) => { - if (!route.page && !route.endpoint) return null; - - const endpoint = route.endpoint; - - return { - id: route.id, - pattern: route.pattern, - params: route.params, - page: route.page, - endpoint: endpoint - ? async () => { - const url = path.resolve(root, endpoint.file); - return await loud_ssr_load_module(url); - } - : null, - endpoint_id: endpoint?.file - }; - }) - ), - matchers: async () => { - if (!manifest_data.params) return {}; - const url = path.resolve(root, manifest_data.params); - const module = await runner.import(url); + return result; + }; + }), + prerendered_routes: new Set(), + get remotes() { + return Object.fromEntries( + get_remotes().map((remote) => [ + remote.hash, + () => runner.import(remote.file).then((module) => ({ default: module })) + ]) + ); + }, + routes: compact( + manifest_data.routes.map((route) => { + if (!route.page && !route.endpoint) return null; + + const endpoint = route.endpoint; + + return { + id: route.id, + pattern: route.pattern, + params: route.params, + page: route.page, + endpoint: endpoint + ? async () => { + const url = path.resolve(root, endpoint.file); + return await loud_ssr_load_module(url); + } + : null, + endpoint_id: endpoint?.file + }; + }) + ), + matchers: async () => { + if (!manifest_data.params) return {}; - if (!module.params) { - throw new Error( - `${manifest_data.params} does not export \`params\` from \`defineParams\`` - ); - } + const url = path.resolve(root, manifest_data.params); + const module = await runner.import(url); - return module.params; + if (!module.params) { + throw new Error( + `${manifest_data.params} does not export \`params\` from \`defineParams\`` + ); } + + return module.params; } }; } @@ -642,7 +638,7 @@ export async function dev( throw new Error('Could not determine clientAddress'); }, read: (file) => { - if (file in manifest._.server_assets) { + if (file in manifest.server_assets) { return fs.readFileSync(from_fs(file)); } diff --git a/packages/kit/src/exports/vite/preview/index.js b/packages/kit/src/exports/vite/preview/index.js index c1803f66faaf..f78e5993b63b 100644 --- a/packages/kit/src/exports/vite/preview/index.js +++ b/packages/kit/src/exports/vite/preview/index.js @@ -220,7 +220,7 @@ export async function preview(vite, svelte_config) { throw new Error('Could not determine clientAddress'); }, read: (file) => { - if (file in manifest._.server_assets) { + if (file in manifest.server_assets) { return fs.readFileSync(join(dir, file)); } diff --git a/packages/kit/src/runtime/app/paths/server.js b/packages/kit/src/runtime/app/paths/server.js index 1804f22b6ec0..3628b9b4440d 100644 --- a/packages/kit/src/runtime/app/paths/server.js +++ b/packages/kit/src/runtime/app/paths/server.js @@ -83,8 +83,8 @@ export async function match(url) { resolved_path = resolved_path.slice(base.length) || '/'; } - const matchers = await manifest._.matchers(); - const result = find_route(resolved_path, manifest._.routes, matchers); + const matchers = await manifest.matchers(); + const result = find_route(resolved_path, manifest.routes, matchers); if (result) { return { diff --git a/packages/kit/src/runtime/app/server/index.js b/packages/kit/src/runtime/app/server/index.js index b1d97a5f3a6d..2ecec85b1282 100644 --- a/packages/kit/src/runtime/app/server/index.js +++ b/packages/kit/src/runtime/app/server/index.js @@ -59,9 +59,9 @@ export function read(asset) { : asset.slice(assets.length + 1) ); - if (file in manifest._.server_assets) { - const length = manifest._.server_assets[file]; - const type = manifest.mimeTypes[file.slice(file.lastIndexOf('.'))]; + if (file in manifest.server_assets) { + const length = manifest.server_assets[file]; + const type = manifest.mime_types[file.slice(file.lastIndexOf('.'))]; return new Response(read_implementation(file), { headers: { diff --git a/packages/kit/src/runtime/server/data/index.js b/packages/kit/src/runtime/server/data/index.js index c4e3da6e6475..b0c04f0a38db 100644 --- a/packages/kit/src/runtime/server/data/index.js +++ b/packages/kit/src/runtime/server/data/index.js @@ -52,7 +52,7 @@ export async function render_data( } // == because it could be undefined (in dev) or null (in build, because of JSON.stringify) - const node = n == undefined ? n : await manifest._.nodes[n](); + const node = n == undefined ? n : await manifest.nodes[n](); // load this. for the child, return as is. for the final result, stream things return load_server_data({ event: new_event, diff --git a/packages/kit/src/runtime/server/fetch.js b/packages/kit/src/runtime/server/fetch.js index 01e2b8d34aef..a54233705c80 100644 --- a/packages/kit/src/runtime/server/fetch.js +++ b/packages/kit/src/runtime/server/fetch.js @@ -87,24 +87,24 @@ export function create_fetch({ event, manifest, state, get_cookie_header, set_in ).slice(1); const filename_html = `${filename}/index.html`; // path may also match path/index.html - const is_asset = manifest.assets.has(filename) || filename in manifest._.server_assets; + const is_asset = manifest.assets.has(filename) || filename in manifest.server_assets; const is_asset_html = - manifest.assets.has(filename_html) || filename_html in manifest._.server_assets; + manifest.assets.has(filename_html) || filename_html in manifest.server_assets; if (is_asset || is_asset_html) { const file = is_asset ? filename : filename_html; if (state.read) { const type = is_asset - ? manifest.mimeTypes[filename.slice(filename.lastIndexOf('.'))] + ? manifest.mime_types[filename.slice(filename.lastIndexOf('.'))] : 'text/html'; return new Response(state.read(file), { headers: type ? { 'content-type': type } : {} }); - } else if (read_implementation && file in manifest._.server_assets) { - const length = manifest._.server_assets[file]; - const type = manifest.mimeTypes[file.slice(file.lastIndexOf('.'))]; + } else if (read_implementation && file in manifest.server_assets) { + const length = manifest.server_assets[file]; + const type = manifest.mime_types[file.slice(file.lastIndexOf('.'))]; return new Response(read_implementation(file), { headers: { diff --git a/packages/kit/src/runtime/server/page/index.js b/packages/kit/src/runtime/server/page/index.js index a190c8d0c709..245b6db0d107 100644 --- a/packages/kit/src/runtime/server/page/index.js +++ b/packages/kit/src/runtime/server/page/index.js @@ -46,7 +46,7 @@ export async function render_page(event, state, page, manifest, nodes, resolve_o } if (is_action_json_request(event)) { - const node = await manifest._.nodes[page.leaf](); + const node = await manifest.nodes[page.leaf](); return handle_action_json_request(event, state, node?.server); } @@ -276,7 +276,7 @@ export async function render_page(event, state, page, manifest, nodes, resolve_o const status = error.status; for (const { error: index, idx } of nearest_error_pages(i, branch, page.errors)) { - const node = await manifest._.nodes[index](); + const node = await manifest.nodes[index](); data_serializer.set_max_nodes(idx); @@ -378,6 +378,6 @@ function load_error_components(ssr, branch, page, manifest) { if (!ssr) return undefined; return build_error_chain(branch, page.errors, (idx) => - manifest._.nodes[idx]?.().then((e) => e.component?.()) + manifest.nodes[idx]?.().then((e) => e.component?.()) ); } diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index f995e8ec8cc5..0f9a908cd0ef 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -72,7 +72,7 @@ export async function render_response({ } } - const { client } = manifest._; + const client = manifest.client; const modulepreloads = new Set(client?.imports); const stylesheets = new Set(client?.stylesheets); diff --git a/packages/kit/src/runtime/server/page/respond_with_error.js b/packages/kit/src/runtime/server/page/respond_with_error.js index fc416a35a977..0fec4fcf9ac2 100644 --- a/packages/kit/src/runtime/server/page/respond_with_error.js +++ b/packages/kit/src/runtime/server/page/respond_with_error.js @@ -30,7 +30,7 @@ export async function respond_with_error({ event, state, manifest, error, resolv const fetched = []; try { const branch = []; - const default_layout = await manifest._.nodes[0](); // 0 is always the root layout + const default_layout = await manifest.nodes[0](); // 0 is always the root layout const nodes = new PageNodes([default_layout]); const ssr = nodes.ssr(); const csr = nodes.csr(); @@ -71,7 +71,7 @@ export async function respond_with_error({ event, state, manifest, error, resolv data }, { - node: await manifest._.nodes[1](), // 1 is always the root error + node: await manifest.nodes[1](), // 1 is always the root error data: null, server_data: null } diff --git a/packages/kit/src/runtime/server/page/server_routing.js b/packages/kit/src/runtime/server/page/server_routing.js index 58d50a9a9a01..142f7c75ef29 100644 --- a/packages/kit/src/runtime/server/page/server_routing.js +++ b/packages/kit/src/runtime/server/page/server_routing.js @@ -9,7 +9,7 @@ import { get_relative_path } from '../../utils.js'; /** * @param {import('types').SSRClientRoute} route * @param {URL} url - * @param {NonNullable} client + * @param {NonNullable} client * @returns {string} */ export function generate_route_object(route, url, client) { @@ -66,19 +66,19 @@ function create_client_import(import_path, url) { * @returns {Promise} */ export async function resolve_route(resolved_path, url, manifest) { - if (!manifest._.client?.routes) { + if (!manifest.client?.routes) { return text('Server-side route resolution disabled', { status: 400 }); } try { - const matchers = await manifest._.matchers(); - const result = find_route(resolved_path, manifest._.client.routes, matchers); + const matchers = await manifest.matchers(); + const result = find_route(resolved_path, manifest.client.routes, matchers); return create_server_routing_response( result?.route ?? null, result?.params ?? {}, url, - manifest._.client + manifest.client ).response; } catch { return text('Error resolving route', { status: 500 }); @@ -103,25 +103,25 @@ export async function resolve_route(resolved_path, url, manifest) { * @returns {Response} */ export function resolve_route_by_id(route_id, url, manifest) { - if (!manifest._.client?.routes) { + if (!manifest.client?.routes) { return text('Server-side route resolution disabled', { status: 400 }); } try { - const route = manifest._.client.routes.find((r) => r.id === route_id); + const route = manifest.client.routes.find((r) => r.id === route_id); if (route) { - return create_server_routing_response(route, null, url, manifest._.client).response; + return create_server_routing_response(route, null, url, manifest.client).response; } // `client.routes` only contains routes with a `+page`, so a miss above doesn't mean the // route doesn't exist — it might be a `+server.js`-only route. `_.routes` includes those // (with `page: null`), so we can distinguish "exists but has no code" from "unknown". - if (manifest._.routes.some((r) => r.id === route_id && !r.page)) { + if (manifest.routes.some((r) => r.id === route_id && !r.page)) { return text('export const endpoint_only = true;', { headers: js_headers() }); } - return create_server_routing_response(null, null, url, manifest._.client).response; + return create_server_routing_response(null, null, url, manifest.client).response; } catch { return text('Error resolving route', { status: 500 }); } @@ -137,7 +137,7 @@ function js_headers() { * @param {import('types').SSRClientRoute | null} route * @param {Partial> | null} params * @param {URL} url - * @param {NonNullable} client + * @param {NonNullable} client * @returns {{response: Response, body: string}} */ export function create_server_routing_response(route, params, url, client) { @@ -164,7 +164,7 @@ export function create_server_routing_response(route, params, url, client) { * * @param {import('types').SSRClientRoute} route * @param {URL} url - * @param {NonNullable} client + * @param {NonNullable} client * @returns {string} */ function create_css_import(route, url, client) { diff --git a/packages/kit/src/runtime/server/remote-functions.js b/packages/kit/src/runtime/server/remote-functions.js index f60bcf4f6636..2a37a0cdbe9e 100644 --- a/packages/kit/src/runtime/server/remote-functions.js +++ b/packages/kit/src/runtime/server/remote-functions.js @@ -164,7 +164,7 @@ export async function handle_remote_call(event, state, manifest, id) { */ async function handle_remote_call_internal(event, state, manifest, id) { const [hash, name, additional_args] = id.split('/'); - const remotes = manifest._.remotes; + const remotes = manifest.remotes; if (!Object.hasOwn(remotes, hash)) error(404); @@ -540,7 +540,7 @@ async function handle_remote_form_post_internal(event, state, manifest, id) { // keyed (`form.for(key)`) instance can — rejoin the remaining segments const [hash, name, ...rest] = id.split('/'); const action_id = rest.join('/'); - const remotes = manifest._.remotes; + const remotes = manifest.remotes; const module = Object.hasOwn(remotes, hash) ? await remotes[hash]() : undefined; let form = /** @type {RemoteForm} */ ( diff --git a/packages/kit/src/runtime/server/respond.js b/packages/kit/src/runtime/server/respond.js index cf8dd9a366b2..cb340f7377c5 100644 --- a/packages/kit/src/runtime/server/respond.js +++ b/packages/kit/src/runtime/server/respond.js @@ -264,7 +264,7 @@ export async function internal_respond(request, manifest, state) { resolved_path = (await hooks.reroute({ url: new URL(url), fetch: event.fetch })) ?? url.pathname; - if (!manifest._.routes.length && resolved_path !== url.pathname) { + if (!manifest.routes.length && resolved_path !== url.pathname) { state.rerouted_url = denormalise_url({ request_url: request.url, resolved_path, @@ -366,8 +366,8 @@ export async function internal_respond(request, manifest, state) { if (!state.prerendering?.fallback && !skip_route_resolution) { try { - const matchers = await manifest._.matchers(); - const result = find_route(resolved_path, manifest._.routes, matchers); + const matchers = await manifest.matchers(); + const result = find_route(resolved_path, manifest.routes, matchers); if (result) { route = result.route; @@ -621,7 +621,7 @@ export async function internal_respond(request, manifest, state) { branch: [ // include the root layout because it applies to every page { - node: /** @type {SSRNode} */ (await manifest._.nodes[0]()), + node: /** @type {SSRNode} */ (await manifest.nodes[0]()), data: null, server_data: null } @@ -687,7 +687,7 @@ export async function internal_respond(request, manifest, state) { ); } else { const allowed_methods = new Set(allowed_page_methods); - const node = await manifest._.nodes[route.page.leaf](); + const node = await manifest.nodes[route.page.leaf](); if (node?.server?.actions) { allowed_methods.add('POST'); } @@ -829,8 +829,8 @@ export async function internal_respond(request, manifest, state) { export function load_page_nodes(page, manifest) { return Promise.all([ // we use == here rather than === because [undefined] serializes as "[null]" - ...page.layouts.map((n) => (n == undefined ? n : manifest._.nodes[n]())), - manifest._.nodes[page.leaf]() + ...page.layouts.map((n) => (n == undefined ? n : manifest.nodes[n]())), + manifest.nodes[page.leaf]() ]); } diff --git a/packages/kit/src/runtime/server/utils.js b/packages/kit/src/runtime/server/utils.js index c76997a36cb5..0d17af895e8d 100644 --- a/packages/kit/src/runtime/server/utils.js +++ b/packages/kit/src/runtime/server/utils.js @@ -103,8 +103,8 @@ export function serialize_uses(node) { */ export function has_prerendered_path(manifest, pathname) { return ( - manifest._.prerendered_routes.has(pathname) || - (pathname.at(-1) === '/' && manifest._.prerendered_routes.has(pathname.slice(0, -1))) + manifest.prerendered_routes.has(pathname) || + (pathname.at(-1) === '/' && manifest.prerendered_routes.has(pathname.slice(0, -1))) ); } diff --git a/packages/kit/src/types/internal.d.ts b/packages/kit/src/types/internal.d.ts index d91cf9e1d4c3..fa707c7f45d4 100644 --- a/packages/kit/src/types/internal.d.ts +++ b/packages/kit/src/types/internal.d.ts @@ -462,25 +462,22 @@ export interface ServerNode { */ export interface SSRManifest { /** The directory where SvelteKit keeps its stuff, including static assets (such as JS and CSS) and internally-used routes. */ - appDir: string; + app_dir: string; /** The `base` and `appDir` settings combined without a leading slash. */ - appPath: string; + app_path: string; /** Static files from `config.files.assets` and the service worker (if any). */ assets: Set; - mimeTypes: Record; - - /** @internal private fields */ - _: { - client: BuildData['client']; - nodes: SSRNodeLoader[]; - /** hashed filename -> import to that file */ - remotes: Record Promise<{ default: Record }>>; - routes: SSRRoute[]; - prerendered_routes: Set; - matchers: () => Promise>; - /** A `[file]: size` map of all assets imported by server code. */ - server_assets: Record; - }; + /** Map of file extensions to MIME types */ + mime_types: Record; + client: BuildData['client']; + nodes: SSRNodeLoader[]; + /** hashed filename -> import to that file */ + remotes: Record Promise<{ default: Record }>>; + routes: SSRRoute[]; + prerendered_routes: Set; + matchers: () => Promise>; + /** A `[file]: size` map of all assets imported by server code. */ + server_assets: Record; } export interface SSRNode { diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 94763511b64f..b67982415834 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -5,7 +5,9 @@ declare module '@sveltejs/kit' { import type { Plugin } from 'vite'; import type { RouteId as AppRouteId, LayoutParams as AppLayoutParams } from '$app/types'; import type { StandardSchemaV1 } from '@standard-schema/spec'; + import type { Component } from 'svelte'; import type { Config } from '@sveltejs/kit/vite'; + import type { ParamMatcher } from '@sveltejs/kit/params'; // @ts-ignore this is an optional peer dependency so could be missing. Written like this so dts-buddy preserves the ts-ignore type Span = import('@opentelemetry/api').Span; @@ -900,6 +902,97 @@ declare module '@sveltejs/kit' { dynamic: boolean; rest: boolean; } + + /** @default 'never' */ + type TrailingSlash = 'never' | 'always' | 'ignore'; + interface Asset { + file: string; + size: number; + type: string | null; + } + + interface FontDependency { + /** emitted file path, relative to the client output directory */ + file: string; + /** the source file path relative to the project root, before hashing and character sanitization */ + filename: string; + } + + interface BuildData { + app_dir: string; + app_path: string; + manifest_data: ManifestData; + out_dir: string; + service_worker: string | null; + client: { + /** Path to the client entry point. */ + start: string; + /** Path to the generated `app.js` file that contains the client manifest. Only set in case of `bundleStrategy === 'split'`. */ + app?: string; + /** JS files that the client entry point relies on. */ + imports: string[]; + /** + * JS files that represent the entry points of the layouts/pages. + * An entry is undefined if the layout/page has no component or universal file (i.e. only has a `.server.js` file). + * Only set in case of `router.resolution === 'server'`. + */ + nodes?: Array; + /** + * CSS files referenced in the entry points of the layouts/pages. + * An entry is undefined if the layout/page has no component or universal file (i.e. only has a `.server.js` file) or if has no CSS. + * Only set in case of `router.resolution === 'server'`. + */ + css?: Array; + /** + * Contains the client route manifest in a form suitable for the server which is used for server-side route resolution. + * Notably, it contains all routes, regardless of whether they are prerendered or not (those are missing in the optimized server route manifest). + * Only set in case of `router.resolution === 'server'`. + */ + routes?: SSRClientRoute[]; + stylesheets: string[]; + fonts: FontDependency[]; + /** + * Whether the client uses public dynamic env vars — `$env/dynamic/public` or `$app/env/public`. + */ + uses_env_dynamic_public: boolean; + /** Only set in case of `bundleStrategy === 'inline'`. */ + inline?: { + script: string; + style: string | undefined; + }; + } | null; + server_manifest: import('vite').Manifest; + } + + interface ManifestData { + /** Static files from `config.files.assets`. */ + assets: Asset[]; + hooks: { + client: string | null; + server: string | null; + universal: string | null; + }; + nodes: PageNode[]; + routes: RouteData[]; + params: string | null; + } + + interface PageNode { + depth: number; + /** The `+page/layout.svelte`. */ + component?: string; // TODO supply default component if it's missing (bit of an edge case) + /** The `+page/layout.js/.ts`. */ + universal?: string; + /** The `+page/layout.server.js/ts`. */ + server?: string; + parent_id?: string; + parent?: PageNode; + /** Filled with the pages that reference this layout (if this is a layout). */ + child_pages?: PageNode[]; + /** The final page options for a node if it was statically analysable */ + page_options?: PageOptions | null; + } + type RecursiveRequired = { // Recursive implementation of TypeScript's Required utility type. // Will recursively continue until it reaches a primitive or Function @@ -909,17 +1002,156 @@ declare module '@sveltejs/kit' { : T[K]; // Use the exact type for everything else }; + interface RouteParam { + name: string; + matcher: string; + optional: boolean; + rest: boolean; + chained: boolean; + } + + /** + * Represents a route segment in the app. It can either be an intermediate node + * with only layout/error pages, or a leaf, at which point either `page` and `leaf` + * or `endpoint` is set. + */ + interface RouteData { + id: string; + parent: RouteData | null; + + segment: string; + pattern: RegExp; + params: RouteParam[]; + + layout: PageNode | null; + error: PageNode | null; + leaf: PageNode | null; + + page: { + layouts: Array; + errors: Array; + leaf: number; + } | null; + + endpoint: { + file: string; + /** The final page options for the endpoint if it was statically analysable */ + page_options: PageOptions | null; + } | null; + } + + type SSRComponentLoader = () => Promise; + + interface UniversalNode { + /** Is `null` in case static analysis succeeds but the node is ssr=false */ + load?: Load; + prerender?: PrerenderOption; + ssr?: boolean; + csr?: boolean; + trailingSlash?: TrailingSlash; + config?: Record; + entries?: PrerenderEntryGenerator; + } + + interface ServerNode { + load?: ServerLoad; + prerender?: PrerenderOption; + ssr?: boolean; + csr?: boolean; + trailingSlash?: TrailingSlash; + actions?: Actions; + config?: Record; + entries?: PrerenderEntryGenerator; + } + /** * Information required to instantiate a new `Server` instance. */ interface SSRManifest { /** The directory where SvelteKit keeps its stuff, including static assets (such as JS and CSS) and internally-used routes. */ - appDir: string; + app_dir: string; /** The `base` and `appDir` settings combined without a leading slash. */ - appPath: string; + app_path: string; /** Static files from `config.files.assets` and the service worker (if any). */ assets: Set; - mimeTypes: Record; + /** Map of file extensions to MIME types */ + mime_types: Record; + client: BuildData['client']; + nodes: SSRNodeLoader[]; + /** hashed filename -> import to that file */ + remotes: Record Promise<{ default: Record }>>; + routes: SSRRoute[]; + prerendered_routes: Set; + matchers: () => Promise>; + /** A `[file]: size` map of all assets imported by server code. */ + server_assets: Record; + } + + interface SSRNode { + /** index into the `nodes` array in the generated `client/app.js`. */ + index: number; + /** external JS files that are loaded on the client. `imports[0]` is the entry point (e.g. `client/nodes/0.js`) */ + imports: string[]; + /** external CSS files that are loaded on the client */ + stylesheets: string[]; + /** external font files that are loaded on the client */ + fonts: FontDependency[]; + + universal_id?: string; + server_id?: string; + + /** + * During development, all styles are inlined for the page to avoid FOUC. + * But in production, this stores styles that are below the inline threshold. + * It returns a Promise during development because Vite needs to load the + * modules on demand. But in production, the contents have been precomputed + * during the build, so it can return synchronously. + */ + inline_styles?(): MaybePromise< + Record string)> + >; + /** Svelte component */ + component?: SSRComponentLoader; + /** +page.js or +layout.js */ + universal?: UniversalNode; + /** +page.server.js, +layout.server.js, or +server.js */ + server?: ServerNode; + } + + type SSRNodeLoader = () => Promise; + + interface PageNodeIndexes { + errors: Array; + layouts: Array; + leaf: number; + } + + type PrerenderEntryGenerator = () => MaybePromise>>; + + type SSREndpoint = Partial> & { + prerender?: PrerenderOption; + trailingSlash?: TrailingSlash; + config?: Record; + entries?: PrerenderEntryGenerator; + fallback?: RequestHandler; + }; + + interface SSRRoute { + id: string; + pattern: RegExp; + params: RouteParam[]; + page: PageNodeIndexes | null; + endpoint: (() => Promise) | null; + endpoint_id?: string; + } + + interface SSRClientRoute { + id: string; + pattern: RegExp; + params: RouteParam[]; + errors: Array; + layouts: Array<[has_server_load: boolean, node_id: number] | undefined>; + leaf: [has_server_load: boolean, node_id: number]; } type ValidatedConfig = RecursiveRequired> & { @@ -1077,7 +1309,19 @@ declare module '@sveltejs/kit' { wasNormalized: boolean; denormalize: (url?: string | URL) => URL; }; + type ValidPageOption = (typeof valid_page_options_array)[number]; + + type PageOptions = Partial<{ + [K in ValidPageOption]: K extends 'ssr' | 'csr' + ? boolean + : K extends 'prerender' + ? PrerenderOption + : K extends 'trailingSlash' + ? TrailingSlash + : any; + }>; export const VERSION: string; + const valid_page_options_array: readonly ["ssr", "prerender", "csr", "trailingSlash", "config", "entries", "load"]; export {}; } From b3bbb20b11f9a1a93e1859f983c06d83854f3962 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Sun, 16 Aug 2026 22:30:28 -0400 Subject: [PATCH 16/59] feat: move `basePath` and `prerenderedRoutes` to the public SSRManifest api --- .changeset/early-eggs-crash.md | 9 +++++++++ packages/kit/types/index.d.ts | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 .changeset/early-eggs-crash.md diff --git a/.changeset/early-eggs-crash.md b/.changeset/early-eggs-crash.md new file mode 100644 index 000000000000..c76428fb68db --- /dev/null +++ b/.changeset/early-eggs-crash.md @@ -0,0 +1,9 @@ +--- +"@sveltejs/kit": minor +"@sveltejs/adapter-vercel": minor +"@sveltejs/adapter-node": minor +"@sveltejs/adapter-cloudflare": minor +--- + +feat: move `basePath` and `prerenderedRoutes` to the public SSRManifest api + \ No newline at end of file diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index b67982415834..627c7ed9b429 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -4312,4 +4312,4 @@ declare module '$app/types' { export type AssetPath = ReturnType; } -//# sourceMappingURL=index.d.ts.map \ No newline at end of file +//# sourceMappingURL=index.d.ts.map From 424211d2b30e3157bbf88fa03a087e72332b2b05 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Tue, 11 Aug 2026 20:22:25 -0700 Subject: [PATCH 17/59] feat: allow adapters to override getRequest and setResponse --- packages/kit/src/exports/public.d.ts | 13 +++++++++++++ packages/kit/src/exports/vite/dev/index.js | 6 +++--- packages/kit/src/exports/vite/preview/index.js | 4 ++-- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index d67b1ced681f..18c14bae05e0 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -16,6 +16,7 @@ import { SSRManifest, ValidatedConfig } from 'types'; import { Plugin } from 'vite'; import { RouteId as AppRouteId, LayoutParams as AppLayoutParams } from '$app/types'; import { StandardSchemaV1 } from '@standard-schema/spec'; +import { getRequest, setResponse } from '@sveltejs/kit/node'; export { PrerenderOption } from '../types/private.js'; @@ -51,6 +52,18 @@ export interface Adapter { */ instrumentation?: () => boolean; }; + /** + * This function overrides the default behavior to convert an `http.IncomingMessage` to a `Request` object. + * To call the original setRequest function, import it from `@sveltejs/kit/node`. + * @since 3.0.0 + */ + getRequest?: typeof getRequest; + /** + * This function overrides the default behavior to write a `Response` object to an `http.ServerResponse`. + * To call the original setResponse function, import it from `@sveltejs/kit/node`. + * @since 3.0.0 + */ + setResponse?: typeof setResponse; /** * Creates an `Emulator`, which allows the adapter to influence the environment * during dev, build and prerendering. diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 2a82321e387a..ea784b496ac3 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -603,7 +603,7 @@ export async function dev( read: (file) => createReadableStream(from_fs(file)) }); - const request = getRequest({ + const request = (svelte_config.adapter.getRequest ?? getRequest)({ base, request: req, response: res @@ -656,11 +656,11 @@ export async function dev( // @ts-expect-error serve_static_middleware.handle(req, res, () => { log_dev_response(rendered.status, format_response(rendered.status, request)); - setResponse(res, rendered); + (svelte_config.adapter.setResponse ?? setResponse)(res, rendered); }); } else { log_dev_response(rendered.status, format_response(rendered.status, request)); - setResponse(res, rendered); + (svelte_config.adapter.setResponse ?? setResponse)(res, rendered); } } catch (e) { const error = coalesce_to_error(e); diff --git a/packages/kit/src/exports/vite/preview/index.js b/packages/kit/src/exports/vite/preview/index.js index f78e5993b63b..5cbbf776420d 100644 --- a/packages/kit/src/exports/vite/preview/index.js +++ b/packages/kit/src/exports/vite/preview/index.js @@ -205,13 +205,13 @@ export async function preview(vite, svelte_config) { vite.middlewares.use(async (req, res) => { const host = req.headers[':authority'] || req.headers.host; - const request = getRequest({ + const request = (svelte_config.adapter.getRequest ?? getRequest)({ base: `${protocol}://${host}`, request: req, response: res }); - setResponse( + (svelte_config.adapter.setResponse ?? setResponse)( res, await server.respond(request, { getClientAddress: () => { From c25786c717b934ddedeb49f60e095e3509f9d1ce Mon Sep 17 00:00:00 2001 From: Ottomated Date: Tue, 11 Aug 2026 20:22:47 -0700 Subject: [PATCH 18/59] changeset --- .changeset/rare-schools-itch.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/rare-schools-itch.md diff --git a/.changeset/rare-schools-itch.md b/.changeset/rare-schools-itch.md new file mode 100644 index 000000000000..3e4cd3ad8926 --- /dev/null +++ b/.changeset/rare-schools-itch.md @@ -0,0 +1,6 @@ +--- +"@sveltejs/kit": minor +--- + +feat: allow adapters to override getRequest and setResponse + \ No newline at end of file From 7bbec867ff6d61fb8145295774c698987cbe1f54 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Tue, 11 Aug 2026 20:25:27 -0700 Subject: [PATCH 19/59] generate types --- packages/kit/types/index.d.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 627c7ed9b429..953808fcc784 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -4,6 +4,7 @@ declare module '@sveltejs/kit' { import type { Plugin } from 'vite'; import type { RouteId as AppRouteId, LayoutParams as AppLayoutParams } from '$app/types'; + import type { getRequest, setResponse } from '@sveltejs/kit/node'; import type { StandardSchemaV1 } from '@standard-schema/spec'; import type { Component } from 'svelte'; import type { Config } from '@sveltejs/kit/vite'; @@ -40,6 +41,18 @@ declare module '@sveltejs/kit' { */ instrumentation?: () => boolean; }; + /** + * This function overrides the default behavior to convert an `http.IncomingMessage` to a `Request` object. + * To call the original setRequest function, import it from `@sveltejs/kit/node`. + * @since 3.0.0 + */ + getRequest?: typeof getRequest; + /** + * This function overrides the default behavior to write a `Response` object to an `http.ServerResponse`. + * To call the original setResponse function, import it from `@sveltejs/kit/node`. + * @since 3.0.0 + */ + setResponse?: typeof setResponse; /** * Creates an `Emulator`, which allows the adapter to influence the environment * during dev, build and prerendering. From 7786c6c871fd25eccf8e65c11fbe02d76b47078b Mon Sep 17 00:00:00 2001 From: Ottomated Date: Tue, 11 Aug 2026 20:39:26 -0700 Subject: [PATCH 20/59] optional chaining --- packages/kit/src/exports/vite/dev/index.js | 6 +++--- packages/kit/src/exports/vite/preview/index.js | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index ea784b496ac3..1db46c785c60 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -603,7 +603,7 @@ export async function dev( read: (file) => createReadableStream(from_fs(file)) }); - const request = (svelte_config.adapter.getRequest ?? getRequest)({ + const request = (svelte_config.adapter?.getRequest ?? getRequest)({ base, request: req, response: res @@ -656,11 +656,11 @@ export async function dev( // @ts-expect-error serve_static_middleware.handle(req, res, () => { log_dev_response(rendered.status, format_response(rendered.status, request)); - (svelte_config.adapter.setResponse ?? setResponse)(res, rendered); + (svelte_config.adapter?.setResponse ?? setResponse)(res, rendered); }); } else { log_dev_response(rendered.status, format_response(rendered.status, request)); - (svelte_config.adapter.setResponse ?? setResponse)(res, rendered); + (svelte_config.adapter?.setResponse ?? setResponse)(res, rendered); } } catch (e) { const error = coalesce_to_error(e); diff --git a/packages/kit/src/exports/vite/preview/index.js b/packages/kit/src/exports/vite/preview/index.js index 5cbbf776420d..53012c87eb55 100644 --- a/packages/kit/src/exports/vite/preview/index.js +++ b/packages/kit/src/exports/vite/preview/index.js @@ -205,13 +205,13 @@ export async function preview(vite, svelte_config) { vite.middlewares.use(async (req, res) => { const host = req.headers[':authority'] || req.headers.host; - const request = (svelte_config.adapter.getRequest ?? getRequest)({ + const request = (svelte_config.adapter?.getRequest ?? getRequest)({ base: `${protocol}://${host}`, request: req, response: res }); - (svelte_config.adapter.setResponse ?? setResponse)( + (svelte_config.adapter?.setResponse ?? setResponse)( res, await server.respond(request, { getClientAddress: () => { From e3449002456feae5ffce0ed0fab5445eeb16c28c Mon Sep 17 00:00:00 2001 From: Ottomated Date: Tue, 11 Aug 2026 21:59:17 -0700 Subject: [PATCH 21/59] docs --- .../docs/25-build-and-deploy/99-writing-adapters.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/documentation/docs/25-build-and-deploy/99-writing-adapters.md b/documentation/docs/25-build-and-deploy/99-writing-adapters.md index c788a6571125..342b51ae350d 100644 --- a/documentation/docs/25-build-and-deploy/99-writing-adapters.md +++ b/documentation/docs/25-build-and-deploy/99-writing-adapters.md @@ -13,6 +13,8 @@ type AdapterSpecificOptions = any; // @filename: index.js // ---cut--- +import { getRequest, setResponse } from '@sveltejs/kit/node'; + /** @param {AdapterSpecificOptions} options */ export default function (options) { /** @type {import('@sveltejs/kit').Adapter} */ @@ -40,6 +42,15 @@ export default function (options) { // Return `false if it can't, or throw a descriptive error. } }, + getRequest(options) { + const request = getRequest(options); + // modify the Request object here if needed + return request; + }, + setResponse(res, response) { + // handle WebSockets here, for example + setResponse(res, response); + }, vite: { plugins: { // add plugins here to integrate with Vite @@ -53,7 +64,7 @@ export default function (options) { } ``` -Of these, `name` and `adapt` are required. `emulate`, `vite`, and `supports` are optional. +Of these, `name` and `adapt` are required. `emulate`, `vite`, `getRequest`, `setResponse`, and `supports` are optional. Within the `adapt` method, there are a number of things that an adapter should do: From f00e7235e18481a7bca0a364bef5f6111f089d6c Mon Sep 17 00:00:00 2001 From: Ottomated Date: Thu, 13 Aug 2026 14:36:42 -0700 Subject: [PATCH 22/59] regen types --- packages/kit/types/index.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 953808fcc784..667d7b8a9a8f 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -4,8 +4,8 @@ declare module '@sveltejs/kit' { import type { Plugin } from 'vite'; import type { RouteId as AppRouteId, LayoutParams as AppLayoutParams } from '$app/types'; - import type { getRequest, setResponse } from '@sveltejs/kit/node'; import type { StandardSchemaV1 } from '@standard-schema/spec'; + import type { getRequest, setResponse } from '@sveltejs/kit/node'; import type { Component } from 'svelte'; import type { Config } from '@sveltejs/kit/vite'; import type { ParamMatcher } from '@sveltejs/kit/params'; @@ -4325,4 +4325,4 @@ declare module '$app/types' { export type AssetPath = ReturnType; } -//# sourceMappingURL=index.d.ts.map +//# sourceMappingURL=index.d.ts.map \ No newline at end of file From afe953c764ea6851906079a0c7af9a9eeae06ed6 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Tue, 11 Aug 2026 20:01:14 -0700 Subject: [PATCH 23/59] remove cloudflare `platform`, emulate the `cloudflare:workers` module instead --- .changeset/fuzzy-kings-sit.md | 6 + packages/adapter-cloudflare/ambient.d.ts | 17 -- packages/adapter-cloudflare/globals.d.ts | 6 + packages/adapter-cloudflare/index.d.ts | 1 - packages/adapter-cloudflare/index.js | 154 ++++++++++++------ packages/adapter-cloudflare/package.json | 6 +- .../src/virtual-cloudflare-workers.js | 149 +++++++++++++++++ .../test/apps/workers/.gitignore | 1 + .../test/apps/workers/config/wrangler.jsonc | 3 + .../test/apps/workers/package.json | 1 + .../apps/workers/src/routes/ctx/+server.js | 6 +- .../workers/src/routes/prerender/+server.js | 12 ++ .../test/apps/workers/test/test.js | 10 +- packages/adapter-cloudflare/tsconfig.json | 10 +- pnpm-lock.yaml | 6 + 15 files changed, 309 insertions(+), 79 deletions(-) create mode 100644 .changeset/fuzzy-kings-sit.md delete mode 100644 packages/adapter-cloudflare/ambient.d.ts create mode 100644 packages/adapter-cloudflare/globals.d.ts create mode 100644 packages/adapter-cloudflare/src/virtual-cloudflare-workers.js create mode 100644 packages/adapter-cloudflare/test/apps/workers/src/routes/prerender/+server.js diff --git a/.changeset/fuzzy-kings-sit.md b/.changeset/fuzzy-kings-sit.md new file mode 100644 index 000000000000..e08a9fad8bce --- /dev/null +++ b/.changeset/fuzzy-kings-sit.md @@ -0,0 +1,6 @@ +--- +"@sveltejs/adapter-cloudflare": major +--- + +breaking: remove cloudflare `platform`, emulate the `cloudflare:workers` module instead + \ No newline at end of file diff --git a/packages/adapter-cloudflare/ambient.d.ts b/packages/adapter-cloudflare/ambient.d.ts deleted file mode 100644 index d3c19971446c..000000000000 --- a/packages/adapter-cloudflare/ambient.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { - CacheStorage, - IncomingRequestCfProperties, - ExecutionContext -} from '@cloudflare/workers-types'; - -declare global { - namespace App { - export interface Platform { - // we should not type `env` here because it will override any type set by - // the user in `src/app.d.ts` - ctx: ExecutionContext; - caches: CacheStorage; - cf?: IncomingRequestCfProperties; - } - } -} diff --git a/packages/adapter-cloudflare/globals.d.ts b/packages/adapter-cloudflare/globals.d.ts new file mode 100644 index 000000000000..a3ffd17c0f3a --- /dev/null +++ b/packages/adapter-cloudflare/globals.d.ts @@ -0,0 +1,6 @@ +import type { PlatformProxy, CacheStorage } from 'wrangler'; + +declare global { + var __platform_proxy: PlatformProxy; + var caches: CacheStorage; +} diff --git a/packages/adapter-cloudflare/index.d.ts b/packages/adapter-cloudflare/index.d.ts index b9062094bd08..9c7266f124e0 100644 --- a/packages/adapter-cloudflare/index.d.ts +++ b/packages/adapter-cloudflare/index.d.ts @@ -1,5 +1,4 @@ import { Adapter } from '@sveltejs/kit'; -import './ambient.js'; import { GetPlatformProxyOptions } from 'wrangler'; export default function plugin(options?: AdapterOptions): Adapter; diff --git a/packages/adapter-cloudflare/index.js b/packages/adapter-cloudflare/index.js index cc81b4c6b32e..9a8ccfdf2d19 100644 --- a/packages/adapter-cloudflare/index.js +++ b/packages/adapter-cloudflare/index.js @@ -1,4 +1,7 @@ -import { copyFileSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +/** @import { Plugin } from 'vite' */ +/** @import { GetPlatformProxyOptions } from 'wrangler' */ + +import fs from 'node:fs'; import path from 'node:path'; import process from 'node:process'; import { fileURLToPath } from 'node:url'; @@ -10,27 +13,34 @@ import { parse_redirects, append_headers } from './utils.js'; +import { exactRegex } from '@rolldown/pluginutils'; const name = '@sveltejs/adapter-cloudflare'; /** @type {typeof import('./index.js').default} */ export default function (options = {}) { + // Add a random query so we can reliably string-replace the stub + const stub_import = + import.meta.resolve('./src/virtual-cloudflare-workers.js') + '?' + crypto.randomUUID(); return { name, async adapt(builder) { - if (existsSync('_routes.json') || existsSync(`${builder.config.files.assets}/_routes.json`)) { + if ( + fs.existsSync('_routes.json') || + fs.existsSync(`${builder.config.files.assets}/_routes.json`) + ) { throw new Error( "Cloudflare Pages' _routes.json should be configured from the adapter option of the SvelteKit plugin in your vite.config.js. See https://svelte.dev/docs/kit/adapter-cloudflare#Options-routes" ); } - if (existsSync(`${builder.config.files.assets}/_headers`)) { + if (fs.existsSync(`${builder.config.files.assets}/_headers`)) { throw new Error( `The _headers file should be placed in the project root rather than the ${builder.config.files.assets} directory` ); } - if (existsSync(`${builder.config.files.assets}/_redirects`)) { + if (fs.existsSync(`${builder.config.files.assets}/_redirects`)) { throw new Error( `The _redirects file should be placed in the project root rather than the ${builder.config.files.assets} directory` ); @@ -69,15 +79,17 @@ export default function (options = {}) { const files = fileURLToPath(new URL('./files', import.meta.url).href); const tmp = builder.getBuildDirectory('cloudflare-tmp'); - rmSync(dest, { force: true, recursive: true }); - rmSync(worker_dest, { force: true, recursive: true }); + fs.rmSync(dest, { force: true, recursive: true }); + fs.rmSync(worker_dest, { force: true, recursive: true }); + + fs.mkdirSync(dest, { recursive: true }); + fs.mkdirSync(tmp, { recursive: true }); - mkdirSync(dest, { recursive: true }); - mkdirSync(tmp, { recursive: true }); + replace_stub(builder.getServerDirectory(), stub_import); // client assets and prerendered pages const assets_dest = `${dest}${builder.config.paths.base}`; - mkdirSync(assets_dest, { recursive: true }); + fs.mkdirSync(assets_dest, { recursive: true }); if ( building_for_cloudflare_pages || wrangler_config.assets?.not_found_handling === '404-page' @@ -89,7 +101,7 @@ export default function (options = {}) { if (options.fallback === 'spa') { await builder.generateFallback(fallback); } else { - writeFileSync(fallback, 'Not Found'); + fs.writeFileSync(fallback, 'Not Found'); } } const client_assets = builder.writeClient(assets_dest); @@ -131,19 +143,19 @@ export default function (options = {}) { const headers_dest = `${dest}/_headers`; /** @type {string | undefined} */ let headers; - if (existsSync(headers_src)) { - headers = readFileSync(headers_src, 'utf-8'); + if (fs.existsSync(headers_src)) { + headers = fs.readFileSync(headers_src, 'utf-8'); } - writeFileSync(headers_dest, generate_headers(builder.getAppPath(), headers)); + fs.writeFileSync(headers_dest, generate_headers(builder.getAppPath(), headers)); // _redirects const redirects_src = '_redirects'; const redirects_dest = `${dest}/_redirects`; - if (existsSync(redirects_src)) { - copyFileSync(redirects_src, redirects_dest); + if (fs.existsSync(redirects_src)) { + fs.copyFileSync(redirects_src, redirects_dest); } if (builder.prerendered.redirects.size > 0) { - writeFileSync(redirects_dest, generate_redirects(builder.prerendered.redirects), { + fs.writeFileSync(redirects_dest, generate_redirects(builder.prerendered.redirects), { flag: 'a' }); } @@ -156,12 +168,12 @@ export default function (options = {}) { // Worker but instead let the rules in the `_redirects` file take over. /** @type {string[]} */ let redirects = []; - if (existsSync(redirects_dest)) { - const redirect_rules = readFileSync(redirects_dest, 'utf8'); + if (fs.existsSync(redirects_dest)) { + const redirect_rules = fs.readFileSync(redirects_dest, 'utf8'); redirects = parse_redirects(redirect_rules); } - writeFileSync( + fs.writeFileSync( `${dest}/_routes.json`, JSON.stringify( get_routes_json(builder, client_assets, redirects, options.routes ?? {}), @@ -170,46 +182,59 @@ export default function (options = {}) { ) ); } else { - writeFileSync(`${dest}/.assetsignore`, generate_assetsignore(), { flag: 'a' }); + fs.writeFileSync(`${dest}/.assetsignore`, generate_assetsignore(), { flag: 'a' }); } }, - emulate() { - // we want to invoke `getPlatformProxy` only once, but await it only when it is accessed. - // If we would await it here, it would hang indefinitely because the platform proxy only resolves once a request happens - const get_emulated = async () => { - const proxy = await getPlatformProxy(options.platformProxy); - const platform = { - env: proxy.env, - ctx: proxy.ctx, - context: proxy.ctx, // deprecated in favor of ctx - caches: proxy.caches, - cf: proxy.cf - }; - /** @type {Record} */ - const env = {}; - const prerender_platform = /** @type {App.Platform} */ (/** @type {unknown} */ ({ env })); - for (const key in proxy.env) { - Object.defineProperty(env, key, { - get: () => { - throw new Error(`Cannot access platform.env.${key} in a prerenderable route`); - } - }); - } - return { platform, prerender_platform }; - }; - - let emulated; - - return { - platform: async ({ prerender }) => { - emulated ??= await get_emulated(); - return prerender ? emulated.prerender_platform : emulated.platform; - } - }; - }, supports: { read: () => true, instrumentation: () => true + }, + vite: { + plugins: { + pre: [ + virtual_workers_module( + { + configPath: options.config, + ...options.platformProxy + }, + stub_import + ) + ] + } + } + }; +} + +/** + * @param {GetPlatformProxyOptions} options + * @param {string} stub_import + * @returns {Plugin} + */ +function virtual_workers_module(options, stub_import) { + const setup = async () => { + if (globalThis.__platform_proxy) return; + const proxy = await getPlatformProxy(options); + globalThis.__platform_proxy = proxy; + globalThis.caches = proxy.caches; + }; + return { + name: 'vite-plugin-adapter-cloudflare-virtual-workers-module', + buildStart: setup, + buildEnd: () => { + void globalThis.__platform_proxy?.dispose(); + // @ts-expect-error + globalThis.__platform_proxy = undefined; + }, + configureServer: setup, + configurePreviewServer: setup, + resolveId: { + filter: { id: exactRegex('cloudflare:workers') }, + handler() { + return { + id: stub_import, + external: true + }; + } } }; } @@ -293,3 +318,24 @@ function validate_wrangler_config(config_file = undefined) { function posixify(str) { return str.replace(/\\/g, '/'); } + +/** + * + * @param {string} directory + * @param {string} stub_import + */ +function replace_stub(directory, stub_import) { + // recurse, find stub_import, replace with "cloudflare:workers" + const files = fs.readdirSync(directory); + for (const file of files) { + const file_path = path.join(directory, file); + if (fs.statSync(file_path).isDirectory()) { + replace_stub(file_path, stub_import); + } else { + const contents = fs.readFileSync(file_path, 'utf8'); + if (contents.includes(stub_import)) { + fs.writeFileSync(file_path, contents.replaceAll(stub_import, 'cloudflare:workers')); + } + } + } +} diff --git a/packages/adapter-cloudflare/package.json b/packages/adapter-cloudflare/package.json index 4ad741a03261..1188f9b4528d 100644 --- a/packages/adapter-cloudflare/package.json +++ b/packages/adapter-cloudflare/package.json @@ -27,11 +27,11 @@ }, "types": "index.d.ts", "files": [ + "src/virtual-cloudflare-workers.js", "files/worker.js", "index.js", "utils.js", - "index.d.ts", - "ambient.d.ts" + "index.d.ts" ], "scripts": { "lint": "oxfmt --check .", @@ -46,9 +46,11 @@ }, "devDependencies": { "@playwright/test": "catalog:", + "@rolldown/pluginutils": "^1.0.1", "@sveltejs/kit": "workspace:^", "@types/node": "catalog:", "typescript": "catalog:typescript-native", + "vite": "catalog:", "vitest": "catalog:" }, "peerDependencies": { diff --git a/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js b/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js new file mode 100644 index 000000000000..7be779d9be34 --- /dev/null +++ b/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js @@ -0,0 +1,149 @@ +import { AsyncLocalStorage } from 'node:async_hooks'; +/** @type {boolean} */ +let prerendering; +try { + const { prerendering: prerendering_value } = await import('$app/env/internal'); + console.log(prerendering_value); + prerendering = prerendering_value; +} catch { + // this will throw during analysis, which is when prerendering happens + prerendering = true; +} + +const als = new AsyncLocalStorage(); + +const proxy = globalThis.__platform_proxy; + +function get_current_env() { + return als.getStore() ?? proxy.env; +} + +export const env = new Proxy( + {}, + { + get(_, prop) { + if (prerendering) { + throw new Error(`Cannot access cloudflare:workers in a prerenderable route`); + } + const inner = get_current_env(); + if (inner) { + return Reflect.get(inner, prop); + } + return undefined; + }, + + set(_, prop, newValue) { + if (prerendering) { + throw new Error(`Cannot access cloudflare:workers in a prerenderable route`); + } + const inner = get_current_env(); + if (inner) { + return Reflect.set(inner, prop, newValue); + } + return true; + }, + + has(_, prop) { + if (prerendering) { + throw new Error(`Cannot access cloudflare:workers in a prerenderable route`); + } + const inner = get_current_env(); + if (inner) { + return Reflect.has(inner, prop); + } + return false; + }, + + ownKeys(_) { + if (prerendering) { + throw new Error(`Cannot access cloudflare:workers in a prerenderable route`); + } + const inner = get_current_env(); + if (inner) { + return Reflect.ownKeys(inner); + } + return []; + }, + + deleteProperty(_, prop) { + if (prerendering) { + throw new Error(`Cannot access cloudflare:workers in a prerenderable route`); + } + const inner = get_current_env(); + if (inner) { + return Reflect.deleteProperty(inner, prop); + } + return true; + }, + + defineProperty(_, prop, attr) { + if (prerendering) { + throw new Error(`Cannot access cloudflare:workers in a prerenderable route`); + } + const inner = get_current_env(); + if (inner) { + return Reflect.defineProperty(inner, prop, attr); + } + return true; + }, + + getOwnPropertyDescriptor(_, prop) { + if (prerendering) { + throw new Error(`Cannot access cloudflare:workers in a prerenderable route`); + } + const inner = get_current_env(); + if (inner) { + return Reflect.getOwnPropertyDescriptor(inner, prop); + } + return undefined; + } + } +); + +/** + * @param {unknown} newEnv + * @param {() => unknown} fn + */ +export function withEnv(newEnv, fn) { + if (prerendering) { + throw new Error(`Cannot access cloudflare:workers in a prerenderable route`); + } + return als.run(newEnv, fn); +} +/** + * @param {unknown} newEnv + * @param {unknown} newExports + * @param {() => unknown} fn + */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export function withEnvAndExports(newEnv, newExports, fn) { + if (prerendering) { + throw new Error(`Cannot access cloudflare:workers in a prerenderable route`); + } + return als.run(newEnv, fn); +} + +// no-ops +export const exports = new Proxy( + {}, + { + get() { + throw new Error('exports is not available in dev mode'); + }, + has() { + throw new Error('exports is not available in dev mode'); + } + } +); +export function waitUntil() {} +export const cache = { + purge() {} +}; +export const tracing = { + enterSpan() { + throw new Error('tracing is not available in dev mode'); + }, + startActiveSpan() { + throw new Error('tracing is not available in dev mode'); + } +}; diff --git a/packages/adapter-cloudflare/test/apps/workers/.gitignore b/packages/adapter-cloudflare/test/apps/workers/.gitignore index de2e15da78f4..63d9a515f164 100644 --- a/packages/adapter-cloudflare/test/apps/workers/.gitignore +++ b/packages/adapter-cloudflare/test/apps/workers/.gitignore @@ -5,3 +5,4 @@ node_modules # Cloudflare .wrangler /dist +worker-configuration.d.ts diff --git a/packages/adapter-cloudflare/test/apps/workers/config/wrangler.jsonc b/packages/adapter-cloudflare/test/apps/workers/config/wrangler.jsonc index 8c2d83f8c21a..372a381e9b61 100644 --- a/packages/adapter-cloudflare/test/apps/workers/config/wrangler.jsonc +++ b/packages/adapter-cloudflare/test/apps/workers/config/wrangler.jsonc @@ -7,5 +7,8 @@ "assets": { "directory": "../dist/public", "binding": "ASSETS" + }, + "vars": { + "FOO": "from wrangler.jsonc" } } diff --git a/packages/adapter-cloudflare/test/apps/workers/package.json b/packages/adapter-cloudflare/test/apps/workers/package.json index c59a5adc2774..51b3622abe70 100644 --- a/packages/adapter-cloudflare/test/apps/workers/package.json +++ b/packages/adapter-cloudflare/test/apps/workers/package.json @@ -6,6 +6,7 @@ "dev": "vite dev", "build": "vite build", "preview": "wrangler dev dist/index.js --config config/wrangler.jsonc", + "prepare": "wrangler types --config config/wrangler.jsonc || echo ''", "test:dev": "DEV=true playwright test", "test:build": "playwright test", "test": "pnpm test:dev && pnpm test:build" diff --git a/packages/adapter-cloudflare/test/apps/workers/src/routes/ctx/+server.js b/packages/adapter-cloudflare/test/apps/workers/src/routes/ctx/+server.js index e26ae2cbbd71..b21afacc72d4 100644 --- a/packages/adapter-cloudflare/test/apps/workers/src/routes/ctx/+server.js +++ b/packages/adapter-cloudflare/test/apps/workers/src/routes/ctx/+server.js @@ -1,3 +1,5 @@ -export function GET({ platform }) { - return new Response(platform?.ctx.waitUntil ? 'ctx works' : 'ctx does not work'); +import { env } from 'cloudflare:workers'; + +export function GET() { + return new Response(env.FOO); } diff --git a/packages/adapter-cloudflare/test/apps/workers/src/routes/prerender/+server.js b/packages/adapter-cloudflare/test/apps/workers/src/routes/prerender/+server.js new file mode 100644 index 000000000000..0bc773d04e72 --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/workers/src/routes/prerender/+server.js @@ -0,0 +1,12 @@ +import { env } from 'cloudflare:workers'; + +export function GET() { + try { + env.FOO; + return new Response('no error'); + } catch (e) { + return new Response(String(e)); + } +} + +export const prerender = true; diff --git a/packages/adapter-cloudflare/test/apps/workers/test/test.js b/packages/adapter-cloudflare/test/apps/workers/test/test.js index 44832c70890f..c692d2d7ba98 100644 --- a/packages/adapter-cloudflare/test/apps/workers/test/test.js +++ b/packages/adapter-cloudflare/test/apps/workers/test/test.js @@ -7,9 +7,9 @@ test('worker', async ({ page }) => { await expect(page.locator('h1')).toContainText('Sum: 3'); }); -test('ctx', async ({ request }) => { +test('cloudflare:workers', async ({ request }) => { const res = await request.get('/ctx'); - expect(await res.text()).toBe('ctx works'); + expect(await res.text()).toBe('from wrangler.jsonc'); }); test('read from $app/server works', async ({ request }) => { @@ -20,3 +20,9 @@ test('read from $app/server works', async ({ request }) => { const response = await request.get('/read'); expect(await response.text()).toBe(content); }); + +test('prerendering throws', async ({ request }) => { + if (process.env.DEV) return; + const res = await request.get('/prerender'); + expect(await res.text()).toContain('Cannot access cloudflare:workers in a prerenderable route'); +}); diff --git a/packages/adapter-cloudflare/tsconfig.json b/packages/adapter-cloudflare/tsconfig.json index ce628354cbad..2e16130e2b76 100644 --- a/packages/adapter-cloudflare/tsconfig.json +++ b/packages/adapter-cloudflare/tsconfig.json @@ -15,5 +15,13 @@ "lib": ["es2024"], "types": ["node"] }, - "include": ["index.js", "utils.js", "utils.spec.js", "vitest.config.js", "test/utils.js"] + "include": [ + "index.js", + "src", + "utils.js", + "utils.spec.js", + "vitest.config.js", + "globals.d.ts", + "test/utils.js" + ] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d10919e5d9e6..d8d50a3026de 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -191,6 +191,9 @@ importers: '@playwright/test': specifier: 'catalog:' version: 1.62.1 + '@rolldown/pluginutils': + specifier: ^1.0.1 + version: 1.0.1 '@sveltejs/kit': specifier: workspace:^ version: link:../kit @@ -200,6 +203,9 @@ importers: typescript: specifier: catalog:typescript-native version: 7.0.2 + vite: + specifier: 'catalog:' + version: 8.2.1(@types/node@22.19.19)(esbuild@0.28.1)(jiti@2.4.2)(yaml@2.9.0) vitest: specifier: 'catalog:' version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.19.19)(@vitest/browser-playwright@4.1.10)(jsdom@30.0.1)(vite@8.2.1(@types/node@22.19.19)(esbuild@0.28.1)(jiti@2.4.2)(yaml@2.9.0)) From fa7d5cbe0e85f0f79bcc5dcdb210aaf1c25f5ca6 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Tue, 11 Aug 2026 20:31:03 -0700 Subject: [PATCH 24/59] support Request.cf --- packages/adapter-cloudflare/index.js | 8 ++++++++ .../test/apps/workers/src/routes/cf/+server.js | 3 +++ .../test/apps/workers/src/routes/{ctx => env}/+server.js | 0 .../adapter-cloudflare/test/apps/workers/test/test.js | 8 +++++++- 4 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 packages/adapter-cloudflare/test/apps/workers/src/routes/cf/+server.js rename packages/adapter-cloudflare/test/apps/workers/src/routes/{ctx => env}/+server.js (100%) diff --git a/packages/adapter-cloudflare/index.js b/packages/adapter-cloudflare/index.js index 9a8ccfdf2d19..c465eb546220 100644 --- a/packages/adapter-cloudflare/index.js +++ b/packages/adapter-cloudflare/index.js @@ -14,6 +14,7 @@ import { append_headers } from './utils.js'; import { exactRegex } from '@rolldown/pluginutils'; +import { getRequest } from '@sveltejs/kit/node'; const name = '@sveltejs/adapter-cloudflare'; @@ -189,6 +190,13 @@ export default function (options = {}) { read: () => true, instrumentation: () => true }, + getRequest(options) { + const request = getRequest(options); + /** @type {import('@cloudflare/workers-types').Request} */ ( + /** @type {unknown} */ (request) + ).cf = globalThis.__platform_proxy?.cf; + return request; + }, vite: { plugins: { pre: [ diff --git a/packages/adapter-cloudflare/test/apps/workers/src/routes/cf/+server.js b/packages/adapter-cloudflare/test/apps/workers/src/routes/cf/+server.js new file mode 100644 index 000000000000..677e42171f0e --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/workers/src/routes/cf/+server.js @@ -0,0 +1,3 @@ +export function GET({ request }) { + return Response.json(request.cf); +} diff --git a/packages/adapter-cloudflare/test/apps/workers/src/routes/ctx/+server.js b/packages/adapter-cloudflare/test/apps/workers/src/routes/env/+server.js similarity index 100% rename from packages/adapter-cloudflare/test/apps/workers/src/routes/ctx/+server.js rename to packages/adapter-cloudflare/test/apps/workers/src/routes/env/+server.js diff --git a/packages/adapter-cloudflare/test/apps/workers/test/test.js b/packages/adapter-cloudflare/test/apps/workers/test/test.js index c692d2d7ba98..8b3ba355fb55 100644 --- a/packages/adapter-cloudflare/test/apps/workers/test/test.js +++ b/packages/adapter-cloudflare/test/apps/workers/test/test.js @@ -8,10 +8,16 @@ test('worker', async ({ page }) => { }); test('cloudflare:workers', async ({ request }) => { - const res = await request.get('/ctx'); + const res = await request.get('/env'); expect(await res.text()).toBe('from wrangler.jsonc'); }); +test('Request.cf', async ({ request }) => { + const res = await request.get('cf'); + const cf = await res.json(); + expect(cf.colo).toBeDefined(); +}); + test('read from $app/server works', async ({ request }) => { const content = fs.readFileSync( path.resolve(import.meta.dirname, '../src/routes/read/file.txt'), From e75e46030dcb68bf82926118835932ddaf83028b Mon Sep 17 00:00:00 2001 From: Ottomated Date: Tue, 11 Aug 2026 20:46:45 -0700 Subject: [PATCH 25/59] fix ai stuff --- packages/adapter-cloudflare/package.json | 4 ++-- .../adapter-cloudflare/src/virtual-cloudflare-workers.js | 2 +- pnpm-lock.yaml | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/adapter-cloudflare/package.json b/packages/adapter-cloudflare/package.json index 1188f9b4528d..44a952913abc 100644 --- a/packages/adapter-cloudflare/package.json +++ b/packages/adapter-cloudflare/package.json @@ -42,11 +42,11 @@ "test": "pnpm test:unit && pnpm test:e2e" }, "dependencies": { - "@cloudflare/workers-types": "^5.20260809.1" + "@cloudflare/workers-types": "^5.20260809.1", + "@rolldown/pluginutils": "^1.0.1" }, "devDependencies": { "@playwright/test": "catalog:", - "@rolldown/pluginutils": "^1.0.1", "@sveltejs/kit": "workspace:^", "@types/node": "catalog:", "typescript": "catalog:typescript-native", diff --git a/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js b/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js index 7be779d9be34..f1cbb1f53c5d 100644 --- a/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js +++ b/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js @@ -2,8 +2,8 @@ import { AsyncLocalStorage } from 'node:async_hooks'; /** @type {boolean} */ let prerendering; try { + // @ts-expect-error internal types are not published const { prerendering: prerendering_value } = await import('$app/env/internal'); - console.log(prerendering_value); prerendering = prerendering_value; } catch { // this will throw during analysis, which is when prerendering happens diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d8d50a3026de..76936b369029 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -184,6 +184,9 @@ importers: '@cloudflare/workers-types': specifier: ^5.20260809.1 version: 5.20260809.1 + '@rolldown/pluginutils': + specifier: ^1.0.1 + version: 1.0.1 wrangler: specifier: ^4.118.0 version: 4.120.1(@cloudflare/workers-types@5.20260809.1) @@ -191,9 +194,6 @@ importers: '@playwright/test': specifier: 'catalog:' version: 1.62.1 - '@rolldown/pluginutils': - specifier: ^1.0.1 - version: 1.0.1 '@sveltejs/kit': specifier: workspace:^ version: link:../kit From 4d6ed3e0e3b7f7fac8a291f103b4973b1924944b Mon Sep 17 00:00:00 2001 From: Ottomated Date: Tue, 11 Aug 2026 21:30:34 -0700 Subject: [PATCH 26/59] don't run proxy during build --- packages/adapter-cloudflare/index.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/adapter-cloudflare/index.js b/packages/adapter-cloudflare/index.js index c465eb546220..160fe7cf1ce2 100644 --- a/packages/adapter-cloudflare/index.js +++ b/packages/adapter-cloudflare/index.js @@ -227,12 +227,6 @@ function virtual_workers_module(options, stub_import) { }; return { name: 'vite-plugin-adapter-cloudflare-virtual-workers-module', - buildStart: setup, - buildEnd: () => { - void globalThis.__platform_proxy?.dispose(); - // @ts-expect-error - globalThis.__platform_proxy = undefined; - }, configureServer: setup, configurePreviewServer: setup, resolveId: { From c92a75fd4324fc6589a57dc03b5ba42f0a88a879 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Tue, 11 Aug 2026 21:54:32 -0700 Subject: [PATCH 27/59] remove platform from built worker --- packages/adapter-cloudflare/files/worker.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/adapter-cloudflare/files/worker.js b/packages/adapter-cloudflare/files/worker.js index 47b478476762..4fef8368d98c 100644 --- a/packages/adapter-cloudflare/files/worker.js +++ b/packages/adapter-cloudflare/files/worker.js @@ -33,7 +33,7 @@ const initialized = server.init({ /** @type {import('@cloudflare/workers-types').ExportedHandler} */ export default { - async fetch(req, env, ctx) { + async fetch(req, env) { if (!origin) { origin = new URL(req.url).origin; } @@ -88,12 +88,6 @@ export default { // dynamically-generated pages return await server.respond(req, { - platform: { - env, - ctx, - caches, - cf: req.cf - }, getClientAddress() { return /** @type {string} */ (req.headers.get('cf-connecting-ip')); } From db3ef5ee1b92db85afe10c227eefda3a75aa8252 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Tue, 11 Aug 2026 21:54:37 -0700 Subject: [PATCH 28/59] update docs --- .../60-adapter-cloudflare.md | 48 +++++-------------- .../35-migrating-to-sveltekit-3.md | 36 +++++++++++++- 2 files changed, 46 insertions(+), 38 deletions(-) diff --git a/documentation/docs/25-build-and-deploy/60-adapter-cloudflare.md b/documentation/docs/25-build-and-deploy/60-adapter-cloudflare.md index a7f16b855eae..c607808005d4 100644 --- a/documentation/docs/25-build-and-deploy/60-adapter-cloudflare.md +++ b/documentation/docs/25-build-and-deploy/60-adapter-cloudflare.md @@ -130,62 +130,36 @@ Functions contained in the [`/functions` directory](https://developers.cloudflar ## Runtime APIs -The [`env`](https://developers.cloudflare.com/workers/runtime-apis/fetch-event#parameters) object contains your project's [bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/), which consist of KV/DO namespaces, etc. It is passed to SvelteKit via the `platform` property, along with [`ctx`](https://developers.cloudflare.com/workers/runtime-apis/context/), [`caches`](https://developers.cloudflare.com/workers/runtime-apis/cache/), and [`cf`](https://developers.cloudflare.com/workers/runtime-apis/request/#incomingrequestcfproperties), meaning that you can access it in hooks and endpoints: +The [`env`](https://developers.cloudflare.com/workers/runtime-apis/fetch-event#parameters) object contains your project's [bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/), which consist of KV/DO namespaces, etc. It is available on the [`cloudflare:workers`](https://developers.cloudflare.com/workers/runtime-apis/bindings/#importing-env-as-a-global) module. ```js // @filename: ambient.d.ts import { DurableObjectNamespace } from '@cloudflare/workers-types'; -declare global { - namespace App { - interface Platform { - env: { - YOUR_DURABLE_OBJECT_NAMESPACE: DurableObjectNamespace; - }; - } - } +declare module 'cloudflare:workers' { + export const env: { + YOUR_DURABLE_OBJECT_NAMESPACE: DurableObjectNamespace; + }; } // @filename: +server.js // ---cut--- // @errors: 2355 2322 /// file: +server.js +import { env } from 'cloudflare:workers'; + /** @type {import('./$types').RequestHandler} */ -export async function POST({ request, platform }) { - const x = platform?.env.YOUR_DURABLE_OBJECT_NAMESPACE.idFromName('x'); +export async function POST() { + const x = env.YOUR_DURABLE_OBJECT_NAMESPACE.idFromName('x'); } ``` > [!NOTE] SvelteKit's built-in [`$app/env/*` modules](environment-variables) should be preferred for environment variables. -To make these types available to your app, install [`wrangler`](https://www.npmjs.com/package/wrangler), run [`wrangler types`](https://developers.cloudflare.com/workers/languages/typescript/), and reference them in your `src/app.d.ts`: - -```ts -// @filename: ambient.d.ts -import { KVNamespace, DurableObjectNamespace } from '@cloudflare/workers-types'; - -namespace Cloudflare { - export interface Env { - YOUR_KV_NAMESPACE: KVNamespace; - YOUR_DURABLE_OBJECT_NAMESPACE: DurableObjectNamespace; - } -} -/// file: src/app.d.ts -// ---cut--- - -declare global { - namespace App { - interface Platform { -+++ env: Cloudflare.Env;+++ - } - } -} - -export {}; -``` +To make these types available to your app, install [`wrangler`](https://www.npmjs.com/package/wrangler) and run [`wrangler types`](https://developers.cloudflare.com/workers/languages/typescript/). ### Testing locally -Cloudflare specific values in the `platform` property are emulated during dev and preview modes. Local [bindings](https://developers.cloudflare.com/workers/wrangler/configuration/#bindings) are created based on your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/) and are used to populate `platform.env` during development and preview. Use the adapter config [`platformProxy` option](#Options-platformProxy) to change your preferences for the bindings. +Cloudflare specific values are emulated during dev and preview modes. Local [bindings](https://developers.cloudflare.com/workers/wrangler/configuration/#bindings) are created based on your [wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/) and are used to populate `env` during development and preview. Use the adapter config [`platformProxy` option](#Options-platformProxy) to change your preferences for the bindings. > [!NOTE] [Durable Objects](https://developers.cloudflare.com/durable-objects/) and [Workflows](https://developers.cloudflare.com/workflows/), which require custom classes to be exported from your worker, are not currently supported. diff --git a/documentation/docs/60-appendix/35-migrating-to-sveltekit-3.md b/documentation/docs/60-appendix/35-migrating-to-sveltekit-3.md index 71426de4b435..97b2b8a2c82a 100644 --- a/documentation/docs/60-appendix/35-migrating-to-sveltekit-3.md +++ b/documentation/docs/60-appendix/35-migrating-to-sveltekit-3.md @@ -453,9 +453,43 @@ All first-party adapters now require SvelteKit 3, alongside these adapter-specif ### `adapter-cloudflare` +Cloudflare-specific APIs are no longer available on `platform`. Instead, find them where you would expect on a Cloudflare worker: + +- `env`, `ctx.waitUntil`, and other `ctx` properties should be imported from `cloudflare:workers`: +```js +// @filename: ambient.d.ts +declare module 'cloudflare:workers' { + export const env: { KV: { get(): Promise } }; + export function waitUntil(promise: Promise): void; +} +// ---cut--- +import { env, waitUntil } from 'cloudflare:workers'; + +const value = await env.KV.get('key'); +``` +- `cf` is now a property of the `Request` object: +```js +/// file: src/routes/cf/+server.js +export async function GET({ request }) { + const { country } = request.cf; +} +``` +- `caches` is now a global variable: +```js +/// file: src/routes/cache/+server.js +const caches = { + async open(name) { + return { match(request) {} } + } +}; +let request = new Request(); +// ---cut--- +const cache = caches.open('foo'); +await myCache.match(request); +``` + - minimum `wrangler` is now `^4.67.0` - `@cloudflare/workers-types` upgraded -- `platform.context` removed in favour of `platform.ctx` ### `adapter-node` From 17291426f1e07ecf0e971db537778dc131957078 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Thu, 13 Aug 2026 14:23:45 -0700 Subject: [PATCH 29/59] typo in docs --- documentation/docs/60-appendix/35-migrating-to-sveltekit-3.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/docs/60-appendix/35-migrating-to-sveltekit-3.md b/documentation/docs/60-appendix/35-migrating-to-sveltekit-3.md index 97b2b8a2c82a..8634889d2417 100644 --- a/documentation/docs/60-appendix/35-migrating-to-sveltekit-3.md +++ b/documentation/docs/60-appendix/35-migrating-to-sveltekit-3.md @@ -484,7 +484,7 @@ const caches = { }; let request = new Request(); // ---cut--- -const cache = caches.open('foo'); +const myCache = caches.open('foo'); await myCache.match(request); ``` From e118590a779f31fb425ca2c36f79deb6389516d1 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Fri, 14 Aug 2026 13:31:35 -0700 Subject: [PATCH 30/59] add dev/preview comment --- packages/kit/src/exports/public.d.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index 18c14bae05e0..317aafbe6c70 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -53,14 +53,14 @@ export interface Adapter { instrumentation?: () => boolean; }; /** - * This function overrides the default behavior to convert an `http.IncomingMessage` to a `Request` object. - * To call the original setRequest function, import it from `@sveltejs/kit/node`. + * This function overrides the default behavior in vite dev and preview mode to convert an `http.IncomingMessage` + * to a `Request` object. To call the original setRequest function, import it from `@sveltejs/kit/node`. * @since 3.0.0 */ getRequest?: typeof getRequest; /** - * This function overrides the default behavior to write a `Response` object to an `http.ServerResponse`. - * To call the original setResponse function, import it from `@sveltejs/kit/node`. + * This function overrides the default behavior in vite dev and preview mode to write a `Response` object + * to an `http.ServerResponse`. To call the original setResponse function, import it from `@sveltejs/kit/node`. * @since 3.0.0 */ setResponse?: typeof setResponse; From 31a1e0eb8220cfa16cedf60b0548b279cb53c55f Mon Sep 17 00:00:00 2001 From: Ottomated Date: Fri, 14 Aug 2026 13:44:42 -0700 Subject: [PATCH 31/59] types --- packages/kit/types/index.d.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 667d7b8a9a8f..b9200789cf72 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -42,14 +42,14 @@ declare module '@sveltejs/kit' { instrumentation?: () => boolean; }; /** - * This function overrides the default behavior to convert an `http.IncomingMessage` to a `Request` object. - * To call the original setRequest function, import it from `@sveltejs/kit/node`. + * This function overrides the default behavior in vite dev and preview mode to convert an `http.IncomingMessage` + * to a `Request` object. To call the original setRequest function, import it from `@sveltejs/kit/node`. * @since 3.0.0 */ getRequest?: typeof getRequest; /** - * This function overrides the default behavior to write a `Response` object to an `http.ServerResponse`. - * To call the original setResponse function, import it from `@sveltejs/kit/node`. + * This function overrides the default behavior in vite dev and preview mode to write a `Response` object + * to an `http.ServerResponse`. To call the original setResponse function, import it from `@sveltejs/kit/node`. * @since 3.0.0 */ setResponse?: typeof setResponse; From 339220a26c2ccfad86c193e81213406bc8c051fa Mon Sep 17 00:00:00 2001 From: Ottomated Date: Fri, 14 Aug 2026 16:38:05 -0700 Subject: [PATCH 32/59] apply comments --- .../25-build-and-deploy/60-adapter-cloudflare.md | 6 +++--- .../60-appendix/35-migrating-to-sveltekit-3.md | 15 ++++++++------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/documentation/docs/25-build-and-deploy/60-adapter-cloudflare.md b/documentation/docs/25-build-and-deploy/60-adapter-cloudflare.md index c607808005d4..5e745a04fe85 100644 --- a/documentation/docs/25-build-and-deploy/60-adapter-cloudflare.md +++ b/documentation/docs/25-build-and-deploy/60-adapter-cloudflare.md @@ -4,7 +4,7 @@ title: Cloudflare To deploy to [Cloudflare Workers](https://workers.cloudflare.com/) or [Cloudflare Pages](https://pages.cloudflare.com/), use [`adapter-cloudflare`](https://github.com/sveltejs/kit/tree/main/packages/adapter-cloudflare). -This adapter will be installed by default when you use [`adapter-auto`](adapter-auto). If you plan on staying with Cloudflare, you can switch from [`adapter-auto`](adapter-auto) to using this adapter directly so that `event.platform` is emulated during local development, type declarations are automatically applied, and the ability to set Cloudflare-specific options is provided. +This adapter will be installed by default when you use [`adapter-auto`](adapter-auto). If you plan on staying with Cloudflare, you can switch from [`adapter-auto`](adapter-auto) to using this adapter directly so that `cloudflare:workers` is emulated during local development, type declarations are automatically applied, and the ability to set Cloudflare-specific options is provided. ## Comparisons @@ -53,7 +53,7 @@ Path to your [Wrangler configuration file](https://developers.cloudflare.com/wor ### platformProxy -Preferences for the emulated `platform.env` local bindings. See the [getPlatformProxy](https://developers.cloudflare.com/workers/wrangler/api/#parameters-1) Wrangler API documentation for a full list of options. +Preferences for the emulated `env` local bindings. See the [getPlatformProxy](https://developers.cloudflare.com/workers/wrangler/api/#parameters-1) Wrangler API documentation for a full list of options. ### fallback @@ -159,7 +159,7 @@ To make these types available to your app, install [`wrangler`](https://www.npmj ### Testing locally -Cloudflare specific values are emulated during dev and preview modes. Local [bindings](https://developers.cloudflare.com/workers/wrangler/configuration/#bindings) are created based on your [wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/) and are used to populate `env` during development and preview. Use the adapter config [`platformProxy` option](#Options-platformProxy) to change your preferences for the bindings. +Cloudflare specific values are emulated during dev and preview modes. Local [bindings](https://developers.cloudflare.com/workers/wrangler/configuration/#bindings) are created based on your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/) and are used to populate `env` during development and preview. Use the adapter config [`platformProxy` option](#Options-platformProxy) to change your preferences for the bindings. > [!NOTE] [Durable Objects](https://developers.cloudflare.com/durable-objects/) and [Workflows](https://developers.cloudflare.com/workflows/), which require custom classes to be exported from your worker, are not currently supported. diff --git a/documentation/docs/60-appendix/35-migrating-to-sveltekit-3.md b/documentation/docs/60-appendix/35-migrating-to-sveltekit-3.md index 8634889d2417..af19f3b6b978 100644 --- a/documentation/docs/60-appendix/35-migrating-to-sveltekit-3.md +++ b/documentation/docs/60-appendix/35-migrating-to-sveltekit-3.md @@ -470,6 +470,12 @@ const value = await env.KV.get('key'); - `cf` is now a property of the `Request` object: ```js /// file: src/routes/cf/+server.js +// @filename: ambient.d.ts +interface Request { + cf: import('@cloudflare/workers-types').IncomingRequestCfProperties; +} +// @filename: index.js +// ---cut--- export async function GET({ request }) { const { country } = request.cf; } @@ -477,14 +483,9 @@ export async function GET({ request }) { - `caches` is now a global variable: ```js /// file: src/routes/cache/+server.js -const caches = { - async open(name) { - return { match(request) {} } - } -}; -let request = new Request(); +let request = new Request(''); // ---cut--- -const myCache = caches.open('foo'); +const myCache = await caches.open('foo'); await myCache.match(request); ``` From 922ee66917f22d1e79016ea6b384209d708e1356 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Fri, 14 Aug 2026 16:38:14 -0700 Subject: [PATCH 33/59] mock tracing --- .../src/virtual-cloudflare-workers.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js b/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js index f1cbb1f53c5d..a87f9abeb454 100644 --- a/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js +++ b/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js @@ -139,11 +139,20 @@ export function waitUntil() {} export const cache = { purge() {} }; +class Span { + get isTraced() { return false; } + setAttribute() {} + end() {} + } +/** @type {import('@cloudflare/workers-types').Tracing} */ export const tracing = { - enterSpan() { - throw new Error('tracing is not available in dev mode'); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + enterSpan(name, callback, ...args) { + return callback(new Span(), ...args); }, - startActiveSpan() { - throw new Error('tracing is not available in dev mode'); - } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + startActiveSpan(name, callback, ...args) { + return callback(new Span(), ...args); + }, + Span, }; From 1036de3811a2c1da1ac6fff7e2bcd1342c491263 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Fri, 14 Aug 2026 21:15:45 -0700 Subject: [PATCH 34/59] type safety for virtual module --- .../src/virtual-cloudflare-workers.js | 46 +++++++++++-------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js b/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js index a87f9abeb454..64b0d8a91d4a 100644 --- a/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js +++ b/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js @@ -18,6 +18,8 @@ function get_current_env() { return als.getStore() ?? proxy.env; } +/** @typedef {import('@cloudflare/workers-types').CloudflareWorkersModule} Module */ + export const env = new Proxy( {}, { @@ -100,23 +102,15 @@ export const env = new Proxy( } ); -/** - * @param {unknown} newEnv - * @param {() => unknown} fn - */ +/** @type {Module['withEnv']} */ export function withEnv(newEnv, fn) { if (prerendering) { throw new Error(`Cannot access cloudflare:workers in a prerenderable route`); } return als.run(newEnv, fn); } -/** - * @param {unknown} newEnv - * @param {unknown} newExports - * @param {() => unknown} fn - */ -// eslint-disable-next-line @typescript-eslint/no-unused-vars -export function withEnvAndExports(newEnv, newExports, fn) { +/** @type {Module['withEnvAndExports']} */ +export function withEnvAndExports(newEnv, _, fn) { if (prerendering) { throw new Error(`Cannot access cloudflare:workers in a prerenderable route`); } @@ -135,24 +129,36 @@ export const exports = new Proxy( } } ); +/** @type {Module['waitUntil']} */ export function waitUntil() {} +/** @type {Module['cache']} */ export const cache = { - purge() {} + purge() { + return Promise.resolve({ success: true, errors: [] }); + } }; class Span { - get isTraced() { return false; } - setAttribute() {} - end() {} + get isTraced() { + return false; + } + setAttribute() { + return this; } + setAttributes() { + return this; + } + end() {} +} /** @type {import('@cloudflare/workers-types').Tracing} */ export const tracing = { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - enterSpan(name, callback, ...args) { + enterSpan(_, callback, ...args) { return callback(new Span(), ...args); }, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - startActiveSpan(name, callback, ...args) { + startActiveSpan(_, callback, ...args) { return callback(new Span(), ...args); }, - Span, + startSpan(_) { + return new Span(); + }, + Span }; From a9f47fae4d2f798dc5cf52f938749dd550e97cff Mon Sep 17 00:00:00 2001 From: Ottomated Date: Fri, 14 Aug 2026 21:16:26 -0700 Subject: [PATCH 35/59] use global proxy's existance as prerendering signal --- .../src/virtual-cloudflare-workers.js | 28 ++++++------------- packages/kit/src/exports/public.d.ts | 2 +- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js b/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js index 64b0d8a91d4a..dba4342df17e 100644 --- a/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js +++ b/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js @@ -1,14 +1,4 @@ import { AsyncLocalStorage } from 'node:async_hooks'; -/** @type {boolean} */ -let prerendering; -try { - // @ts-expect-error internal types are not published - const { prerendering: prerendering_value } = await import('$app/env/internal'); - prerendering = prerendering_value; -} catch { - // this will throw during analysis, which is when prerendering happens - prerendering = true; -} const als = new AsyncLocalStorage(); @@ -24,7 +14,7 @@ export const env = new Proxy( {}, { get(_, prop) { - if (prerendering) { + if (!proxy) { throw new Error(`Cannot access cloudflare:workers in a prerenderable route`); } const inner = get_current_env(); @@ -35,7 +25,7 @@ export const env = new Proxy( }, set(_, prop, newValue) { - if (prerendering) { + if (!proxy) { throw new Error(`Cannot access cloudflare:workers in a prerenderable route`); } const inner = get_current_env(); @@ -46,7 +36,7 @@ export const env = new Proxy( }, has(_, prop) { - if (prerendering) { + if (!proxy) { throw new Error(`Cannot access cloudflare:workers in a prerenderable route`); } const inner = get_current_env(); @@ -57,7 +47,7 @@ export const env = new Proxy( }, ownKeys(_) { - if (prerendering) { + if (!proxy) { throw new Error(`Cannot access cloudflare:workers in a prerenderable route`); } const inner = get_current_env(); @@ -68,7 +58,7 @@ export const env = new Proxy( }, deleteProperty(_, prop) { - if (prerendering) { + if (!proxy) { throw new Error(`Cannot access cloudflare:workers in a prerenderable route`); } const inner = get_current_env(); @@ -79,7 +69,7 @@ export const env = new Proxy( }, defineProperty(_, prop, attr) { - if (prerendering) { + if (!proxy) { throw new Error(`Cannot access cloudflare:workers in a prerenderable route`); } const inner = get_current_env(); @@ -90,7 +80,7 @@ export const env = new Proxy( }, getOwnPropertyDescriptor(_, prop) { - if (prerendering) { + if (!proxy) { throw new Error(`Cannot access cloudflare:workers in a prerenderable route`); } const inner = get_current_env(); @@ -104,14 +94,14 @@ export const env = new Proxy( /** @type {Module['withEnv']} */ export function withEnv(newEnv, fn) { - if (prerendering) { + if (!proxy) { throw new Error(`Cannot access cloudflare:workers in a prerenderable route`); } return als.run(newEnv, fn); } /** @type {Module['withEnvAndExports']} */ export function withEnvAndExports(newEnv, _, fn) { - if (prerendering) { + if (!proxy) { throw new Error(`Cannot access cloudflare:workers in a prerenderable route`); } return als.run(newEnv, fn); diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index 317aafbe6c70..964c347a3567 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -59,7 +59,7 @@ export interface Adapter { */ getRequest?: typeof getRequest; /** - * This function overrides the default behavior in vite dev and preview mode to write a `Response` object + * This function overrides the default behavior in vite dev and preview mode to write a `Response` object * to an `http.ServerResponse`. To call the original setResponse function, import it from `@sveltejs/kit/node`. * @since 3.0.0 */ From cecdc77985a620c8751aeae2b73c7bb960d4c233 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Fri, 14 Aug 2026 21:20:31 -0700 Subject: [PATCH 36/59] types!! --- packages/kit/types/index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index b9200789cf72..1f75fd31e75f 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -48,7 +48,7 @@ declare module '@sveltejs/kit' { */ getRequest?: typeof getRequest; /** - * This function overrides the default behavior in vite dev and preview mode to write a `Response` object + * This function overrides the default behavior in vite dev and preview mode to write a `Response` object * to an `http.ServerResponse`. To call the original setResponse function, import it from `@sveltejs/kit/node`. * @since 3.0.0 */ From b73ee3907c1235dc4fbe9d7ceafc02561b1ecfe1 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Fri, 14 Aug 2026 22:53:42 -0700 Subject: [PATCH 37/59] fix types --- packages/adapter-cloudflare/globals.d.ts | 3 +-- packages/adapter-cloudflare/index.js | 2 +- packages/adapter-cloudflare/package.json | 2 +- .../src/virtual-cloudflare-workers.js | 4 ++-- packages/adapter-cloudflare/tsconfig.json | 2 +- pnpm-lock.yaml | 20 +++++++++---------- 6 files changed, 16 insertions(+), 17 deletions(-) diff --git a/packages/adapter-cloudflare/globals.d.ts b/packages/adapter-cloudflare/globals.d.ts index a3ffd17c0f3a..a64ccbc1392f 100644 --- a/packages/adapter-cloudflare/globals.d.ts +++ b/packages/adapter-cloudflare/globals.d.ts @@ -1,6 +1,5 @@ -import type { PlatformProxy, CacheStorage } from 'wrangler'; +import type { PlatformProxy } from 'wrangler'; declare global { var __platform_proxy: PlatformProxy; - var caches: CacheStorage; } diff --git a/packages/adapter-cloudflare/index.js b/packages/adapter-cloudflare/index.js index 160fe7cf1ce2..f33ab895271c 100644 --- a/packages/adapter-cloudflare/index.js +++ b/packages/adapter-cloudflare/index.js @@ -223,7 +223,7 @@ function virtual_workers_module(options, stub_import) { if (globalThis.__platform_proxy) return; const proxy = await getPlatformProxy(options); globalThis.__platform_proxy = proxy; - globalThis.caches = proxy.caches; + /** @type {any} */ (globalThis).caches = proxy.caches; }; return { name: 'vite-plugin-adapter-cloudflare-virtual-workers-module', diff --git a/packages/adapter-cloudflare/package.json b/packages/adapter-cloudflare/package.json index 44a952913abc..3cbbc57b78c0 100644 --- a/packages/adapter-cloudflare/package.json +++ b/packages/adapter-cloudflare/package.json @@ -42,7 +42,7 @@ "test": "pnpm test:unit && pnpm test:e2e" }, "dependencies": { - "@cloudflare/workers-types": "^5.20260809.1", + "@cloudflare/workers-types": "^5.20260813.1", "@rolldown/pluginutils": "^1.0.1" }, "devDependencies": { diff --git a/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js b/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js index dba4342df17e..35721552a1c7 100644 --- a/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js +++ b/packages/adapter-cloudflare/src/virtual-cloudflare-workers.js @@ -8,7 +8,7 @@ function get_current_env() { return als.getStore() ?? proxy.env; } -/** @typedef {import('@cloudflare/workers-types').CloudflareWorkersModule} Module */ +/** @typedef {typeof import('cloudflare:workers')} Module */ export const env = new Proxy( {}, @@ -139,7 +139,7 @@ class Span { } end() {} } -/** @type {import('@cloudflare/workers-types').Tracing} */ +/** @type {Module['tracing']} */ export const tracing = { enterSpan(_, callback, ...args) { return callback(new Span(), ...args); diff --git a/packages/adapter-cloudflare/tsconfig.json b/packages/adapter-cloudflare/tsconfig.json index 2e16130e2b76..700eb7ca2df9 100644 --- a/packages/adapter-cloudflare/tsconfig.json +++ b/packages/adapter-cloudflare/tsconfig.json @@ -13,7 +13,7 @@ // taken from the Cloudflare Workers TypeScript template https://github.com/cloudflare/workers-sdk/blob/main/packages/create-cloudflare/templates/hello-world/ts/tsconfig.json "target": "es2024", "lib": ["es2024"], - "types": ["node"] + "types": ["node", "@cloudflare/workers-types"] }, "include": [ "index.js", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 76936b369029..6551b4808228 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -182,14 +182,14 @@ importers: packages/adapter-cloudflare: dependencies: '@cloudflare/workers-types': - specifier: ^5.20260809.1 - version: 5.20260809.1 + specifier: ^5.20260813.1 + version: 5.20260813.1 '@rolldown/pluginutils': specifier: ^1.0.1 version: 1.0.1 wrangler: specifier: ^4.118.0 - version: 4.120.1(@cloudflare/workers-types@5.20260809.1) + version: 4.120.1(@cloudflare/workers-types@5.20260813.1) devDependencies: '@playwright/test': specifier: 'catalog:' @@ -229,7 +229,7 @@ importers: version: 8.2.1(@types/node@22.19.19)(esbuild@0.28.1)(jiti@2.4.2)(yaml@2.9.0) wrangler: specifier: 'catalog:' - version: 4.120.1(@cloudflare/workers-types@5.20260809.1) + version: 4.120.1(@cloudflare/workers-types@5.20260813.1) packages/adapter-cloudflare/test/apps/workers: devDependencies: @@ -250,7 +250,7 @@ importers: version: 8.2.1(@types/node@22.19.19)(esbuild@0.28.1)(jiti@2.4.2)(yaml@2.9.0) wrangler: specifier: 'catalog:' - version: 4.120.1(@cloudflare/workers-types@5.20260809.1) + version: 4.120.1(@cloudflare/workers-types@5.20260813.1) packages/adapter-netlify: dependencies: @@ -1883,8 +1883,8 @@ packages: cpu: [x64] os: [win32] - '@cloudflare/workers-types@5.20260809.1': - resolution: {integrity: sha512-sBM+0I5lCY9LgTnorn/N2UyrA6KVbUzj9tncxwH8v6sH8tVeAyJj+i0z3xXWY4l4fd1oDcwt8CGf50vGwVISYQ==} + '@cloudflare/workers-types@5.20260813.1': + resolution: {integrity: sha512-RQNfm7xD10hNHEQZFxQPmyGMJ9+aDGPcdFZ0x1LtmjRoLFcgZkGvfaqJAbOMQBAUFSESO3bYJS4p9mOLv28Ihg==} '@cspotcode/source-map-support@0.8.1': resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} @@ -4815,7 +4815,7 @@ snapshots: '@cloudflare/workerd-windows-64@1.20260804.1': optional: true - '@cloudflare/workers-types@5.20260809.1': {} + '@cloudflare/workers-types@5.20260813.1': {} '@cspotcode/source-map-support@0.8.1': dependencies: @@ -7196,7 +7196,7 @@ snapshots: '@cloudflare/workerd-linux-arm64': 1.20260804.1 '@cloudflare/workerd-windows-64': 1.20260804.1 - wrangler@4.120.1(@cloudflare/workers-types@5.20260809.1): + wrangler@4.120.1(@cloudflare/workers-types@5.20260813.1): dependencies: '@cloudflare/kv-asset-handler': 0.5.0 '@cloudflare/unenv-preset': 2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260804.1) @@ -7207,7 +7207,7 @@ snapshots: unenv: 2.0.0-rc.24 workerd: 1.20260804.1 optionalDependencies: - '@cloudflare/workers-types': 5.20260809.1 + '@cloudflare/workers-types': 5.20260813.1 fsevents: 2.3.3 transitivePeerDependencies: - bufferutil From 670430390da9f75cdaf11f1c1b272a030734922e Mon Sep 17 00:00:00 2001 From: Ottomated Date: Fri, 14 Aug 2026 23:02:36 -0700 Subject: [PATCH 38/59] remove prepare script --- packages/adapter-cloudflare/test/apps/workers/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/adapter-cloudflare/test/apps/workers/package.json b/packages/adapter-cloudflare/test/apps/workers/package.json index 51b3622abe70..816115baeaf1 100644 --- a/packages/adapter-cloudflare/test/apps/workers/package.json +++ b/packages/adapter-cloudflare/test/apps/workers/package.json @@ -6,7 +6,7 @@ "dev": "vite dev", "build": "vite build", "preview": "wrangler dev dist/index.js --config config/wrangler.jsonc", - "prepare": "wrangler types --config config/wrangler.jsonc || echo ''", + "generate:types": "wrangler types --config config/wrangler.jsonc", "test:dev": "DEV=true playwright test", "test:build": "playwright test", "test": "pnpm test:dev && pnpm test:build" From e9309791d858ec800985d3c4944f43cdaf3a1d3f Mon Sep 17 00:00:00 2001 From: Ottomated Date: Sat, 15 Aug 2026 09:26:50 -0400 Subject: [PATCH 39/59] doc types --- .../docs/25-build-and-deploy/60-adapter-cloudflare.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/documentation/docs/25-build-and-deploy/60-adapter-cloudflare.md b/documentation/docs/25-build-and-deploy/60-adapter-cloudflare.md index 5e745a04fe85..5271950a043e 100644 --- a/documentation/docs/25-build-and-deploy/60-adapter-cloudflare.md +++ b/documentation/docs/25-build-and-deploy/60-adapter-cloudflare.md @@ -133,18 +133,16 @@ Functions contained in the [`/functions` directory](https://developers.cloudflar The [`env`](https://developers.cloudflare.com/workers/runtime-apis/fetch-event#parameters) object contains your project's [bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/), which consist of KV/DO namespaces, etc. It is available on the [`cloudflare:workers`](https://developers.cloudflare.com/workers/runtime-apis/bindings/#importing-env-as-a-global) module. ```js +/// file: +server.js // @filename: ambient.d.ts -import { DurableObjectNamespace } from '@cloudflare/workers-types'; - declare module 'cloudflare:workers' { export const env: { - YOUR_DURABLE_OBJECT_NAMESPACE: DurableObjectNamespace; + YOUR_DURABLE_OBJECT_NAMESPACE: import('@cloudflare/workers-types').DurableObjectNamespace; }; } // @filename: +server.js // ---cut--- // @errors: 2355 2322 -/// file: +server.js import { env } from 'cloudflare:workers'; /** @type {import('./$types').RequestHandler} */ From db9e1f396bfd72ed273f2ff4226077b146984eb2 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Sat, 15 Aug 2026 09:28:11 -0400 Subject: [PATCH 40/59] doc types 2 --- documentation/docs/60-appendix/35-migrating-to-sveltekit-3.md | 1 + 1 file changed, 1 insertion(+) diff --git a/documentation/docs/60-appendix/35-migrating-to-sveltekit-3.md b/documentation/docs/60-appendix/35-migrating-to-sveltekit-3.md index af19f3b6b978..5ab90908fbaf 100644 --- a/documentation/docs/60-appendix/35-migrating-to-sveltekit-3.md +++ b/documentation/docs/60-appendix/35-migrating-to-sveltekit-3.md @@ -475,6 +475,7 @@ interface Request { cf: import('@cloudflare/workers-types').IncomingRequestCfProperties; } // @filename: index.js +// @errors: 7031 // ---cut--- export async function GET({ request }) { const { country } = request.cf; From a7516ea7ff98ee9c1a0092891040ab67517c8f74 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Fri, 14 Aug 2026 16:28:22 -0700 Subject: [PATCH 41/59] build cloudflare environment --- .../adapter-cloudflare/files/worker-dev.js | 1 + packages/adapter-cloudflare/index.js | 66 ++++++++++- packages/adapter-cloudflare/package.json | 2 + pnpm-lock.yaml | 103 ++++++++++++++++++ 4 files changed, 169 insertions(+), 3 deletions(-) create mode 100644 packages/adapter-cloudflare/files/worker-dev.js diff --git a/packages/adapter-cloudflare/files/worker-dev.js b/packages/adapter-cloudflare/files/worker-dev.js new file mode 100644 index 000000000000..ff8b4c56321a --- /dev/null +++ b/packages/adapter-cloudflare/files/worker-dev.js @@ -0,0 +1 @@ +export default {}; diff --git a/packages/adapter-cloudflare/index.js b/packages/adapter-cloudflare/index.js index f33ab895271c..3231647effe0 100644 --- a/packages/adapter-cloudflare/index.js +++ b/packages/adapter-cloudflare/index.js @@ -15,8 +15,9 @@ import { } from './utils.js'; import { exactRegex } from '@rolldown/pluginutils'; import { getRequest } from '@sveltejs/kit/node'; +import { cloudflare } from '@cloudflare/vite-plugin'; -const name = '@sveltejs/adapter-cloudflare'; +const files = fileURLToPath(new URL('./files', import.meta.url).href); /** @type {typeof import('./index.js').default} */ export default function (options = {}) { @@ -24,7 +25,7 @@ export default function (options = {}) { const stub_import = import.meta.resolve('./src/virtual-cloudflare-workers.js') + '?' + crypto.randomUUID(); return { - name, + name: '@sveltejs/adapter-cloudflare', async adapt(builder) { if ( fs.existsSync('_routes.json') || @@ -77,7 +78,6 @@ export default function (options = {}) { } } - const files = fileURLToPath(new URL('./files', import.meta.url).href); const tmp = builder.getBuildDirectory('cloudflare-tmp'); fs.rmSync(dest, { force: true, recursive: true }); @@ -200,6 +200,7 @@ export default function (options = {}) { vite: { plugins: { pre: [ + ...cloudflare_vite_plugins(options.config), virtual_workers_module( { configPath: options.config, @@ -213,6 +214,65 @@ export default function (options = {}) { }; } +/** + * @param {string | undefined} config_path + * @returns {Plugin[]} + */ +function cloudflare_vite_plugins(config_path) { + /** @type {string} */ + let out_dir = path.resolve(process.cwd(), '.svelte-kit'); + return [ + { + name: 'vite-plugin-adapter-cloudflare-disable-workers-build', + apply: 'build', + config(user_config) { + // We need to disable @cloudflare/vite-plugin's buildApp hook, + // which is set here: https://github.com/cloudflare/workers-sdk/blob/main/packages/vite-plugin-cloudflare/src/plugins/config.ts#L88 + // and does not run if config.builder.buildApp is set. + if (!user_config.builder?.buildApp) { + user_config.builder ??= {}; + user_config.builder.buildApp = async () => {}; + } + }, + configResolved(config) { + const plugin = config.plugins.find( + (plugin) => plugin.name === 'vite-plugin-sveltekit-setup' + ); + const options = plugin?.api?.options; + if (!options) throw new Error('vite-plugin-sveltekit-setup not found'); + out_dir = path.resolve(process.cwd(), options.kit.outDir); + } + }, + ...cloudflare({ + configPath: config_path, + config: (user_config) => { + const worker_contents = fs + .readFileSync(`${files}/worker.js`, 'utf8') + .replace('SERVER', `../output/server/index.js`) + .replace('MANIFEST', '../cloudflare-tmp/manifest.js') + .replace('ASSETS', user_config.assets?.binding ?? 'ASSETS'); + const worker_out = `${out_dir}/cloudflare/worker.js`; + fs.writeFileSync(worker_out, worker_contents); + user_config.main = worker_out; + } + // for now, disable all cloudflare plugins during dev & preview + }).map((p) => { + if (typeof p.apply === 'function') { + const old_apply = p.apply; + p.apply = (config, env) => { + if (env.command !== 'build') return false; + return old_apply(config, env); + }; + } else if (p.apply === 'serve') { + p.apply = () => false; + } else { + p.apply = 'build'; + } + return p; + }) + ]; +} + /** * @param {GetPlatformProxyOptions} options * @param {string} stub_import diff --git a/packages/adapter-cloudflare/package.json b/packages/adapter-cloudflare/package.json index 3cbbc57b78c0..39377efdf2f6 100644 --- a/packages/adapter-cloudflare/package.json +++ b/packages/adapter-cloudflare/package.json @@ -29,6 +29,7 @@ "files": [ "src/virtual-cloudflare-workers.js", "files/worker.js", + "files/worker-dev.js", "index.js", "utils.js", "index.d.ts" @@ -42,6 +43,7 @@ "test": "pnpm test:unit && pnpm test:e2e" }, "dependencies": { + "@cloudflare/vite-plugin": "^1.51.1", "@cloudflare/workers-types": "^5.20260813.1", "@rolldown/pluginutils": "^1.0.1" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6551b4808228..cbabec134894 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -181,6 +181,9 @@ importers: packages/adapter-cloudflare: dependencies: + '@cloudflare/vite-plugin': + specifier: ^1.51.1 + version: 1.51.1(vite@8.2.1(@types/node@22.19.19)(esbuild@0.28.1)(jiti@2.4.2)(yaml@2.9.0))(wrangler@4.120.1(@cloudflare/workers-types@5.20260809.1)) '@cloudflare/workers-types': specifier: ^5.20260813.1 version: 5.20260813.1 @@ -1853,30 +1856,67 @@ packages: workerd: optional: true + '@cloudflare/vite-plugin@1.51.1': + resolution: {integrity: sha512-qVTv67W3yLPIVZcq3L7z6yoqNGYA6W9PzDwHMGeku7gNhimNj+Bf84A8ukLdfz2EpVkzLam4pPbqoQYY8bawJA==} + hasBin: true + peerDependencies: + vite: ^6.1.0 || ^7.0.0 || ^8.0.0 + wrangler: ^4.120.0 + + '@cloudflare/workerd-darwin-64@1.20260801.1': + resolution: {integrity: sha512-wuJWbXpKvncJi1P0GKS+iYpN5tHdb7JPJJ/+6ZQe8zzovHHVMkLJPNBsgWpqeUhpM3g9qTwEKd2rglNKejuh5A==} + engines: {node: '>=16'} + cpu: [x64] + os: [darwin] + '@cloudflare/workerd-darwin-64@1.20260804.1': resolution: {integrity: sha512-191/PPEFicRK2wK69eXzSjnLgHL79k7zR2VUpyIr8rhFgGns1b5bTHgnBuUrgU2LPbEtwbE5eL2hJ0uhLAFEow==} engines: {node: '>=16'} cpu: [x64] os: [darwin] + '@cloudflare/workerd-darwin-arm64@1.20260801.1': + resolution: {integrity: sha512-kwoZiTpnhNrF3+APx84Q/oAqvJ3sU9yefGagwm/ASaH/2W19x0vghkW/r4qCoHCK0WW7EPugZ+aXjgPMRtlq1Q==} + engines: {node: '>=16'} + cpu: [arm64] + os: [darwin] + '@cloudflare/workerd-darwin-arm64@1.20260804.1': resolution: {integrity: sha512-aI2cAFLsrNkSz3kLSQrgrO9ICTfY7jb2h0jgaWDE9mQLQQDfpXeYrKWt07tTgMmeNP79o9w3NZe3aqtt8mTGHQ==} engines: {node: '>=16'} cpu: [arm64] os: [darwin] + '@cloudflare/workerd-linux-64@1.20260801.1': + resolution: {integrity: sha512-r0vAxCZH+Jih9Unm1yoyiByPNWNgawcKciOHDm5Q37ZVGOkKLsT9AtLe3yLSaul76WrKqtf+JP2n0WW32VBLJg==} + engines: {node: '>=16'} + cpu: [x64] + os: [linux] + '@cloudflare/workerd-linux-64@1.20260804.1': resolution: {integrity: sha512-KBCjxBIlN2jucfQGaTK4EgmPsWzQgYR/zYhPfi3mkWwdoTyG1dgrt2aizKps/SYse85ci/SOxojKk0/K7vstPw==} engines: {node: '>=16'} cpu: [x64] os: [linux] + '@cloudflare/workerd-linux-arm64@1.20260801.1': + resolution: {integrity: sha512-zWgpdZtSozvIgzQNmQiDSF8yEOQJUkRAWNsDXXzAAoy+fCn8YUoSibj3mpFSbZRvbUldeBEaW6SCdC2VEMkhNQ==} + engines: {node: '>=16'} + cpu: [arm64] + os: [linux] + '@cloudflare/workerd-linux-arm64@1.20260804.1': resolution: {integrity: sha512-7lswfarBZ7xkHpTFrNb74ExwOltIalVaASc+GOYfCNtnwFxK/JZSVByfm/hnZEBtrHU7fMY2z7dYT64rwS0pHg==} engines: {node: '>=16'} cpu: [arm64] os: [linux] + '@cloudflare/workerd-windows-64@1.20260801.1': + resolution: {integrity: sha512-2oQz+Ksu4ji6e/+ZoYX+tWQEcxAii2p7l+iR8kx48W1llMalaufAsmVxTlk+3/vrM7D3/2c0iK448e0UQTcIMg==} + engines: {node: '>=16'} + cpu: [x64] + os: [win32] + '@cloudflare/workerd-windows-64@1.20260804.1': resolution: {integrity: sha512-GOgRWYtxISN5rAWfx3K7zubt3xEn0/ZPFrbcLL+GwT4ouCKyNoHqfT1cPNB6Nx7t8BHEjnuTG7gkHO4Wd5ORdg==} engines: {node: '>=16'} @@ -3930,6 +3970,10 @@ packages: mdn-data@2.27.1: resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + miniflare@5.20260801.1-alpha: + resolution: {integrity: sha512-BHPVzIDA6mbx7LefxpvkXW7DHx9FKB9GorZatbnrrFTt3CVMU8zuUpbgyCuebwDKcTTOZos43ta8GQ0eMVEpxA==} + engines: {node: '>=22.0.0'} + miniflare@5.20260804.0-alpha: resolution: {integrity: sha512-UDegnTukIpJCI/lNyQXU7B8kYFLcMZihlxqqOocnc62tV8AO6XofmFmZm3ZLDXTWZpkZnLLJ2sKCSlTIEXO3dQ==} engines: {node: '>=22.0.0'} @@ -4568,6 +4612,11 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + workerd@1.20260801.1: + resolution: {integrity: sha512-/g9JGTyqnHtoIscpBHqKD8swE2V4StBs2i69PmLiOhH45OP95jCFICl4F1hKlgN57rqfni5LiCitttoX5OOkVA==} + engines: {node: '>=16'} + hasBin: true + workerd@1.20260804.1: resolution: {integrity: sha512-b0P38g5/ssemwWxd/mafNYggEZ0ere7PCiUH6RCHkgqjRhhXTP45nDiL2L3iIvi8uF2IjNrGpVgMXEunCaeY/w==} engines: {node: '>=16'} @@ -4794,24 +4843,58 @@ snapshots: '@cloudflare/kv-asset-handler@0.5.0': {} + '@cloudflare/unenv-preset@2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260801.1)': + dependencies: + unenv: 2.0.0-rc.24 + optionalDependencies: + workerd: 1.20260801.1 + '@cloudflare/unenv-preset@2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260804.1)': dependencies: unenv: 2.0.0-rc.24 optionalDependencies: workerd: 1.20260804.1 + '@cloudflare/vite-plugin@1.51.1(vite@8.2.1(@types/node@22.19.19)(esbuild@0.28.1)(jiti@2.4.2)(yaml@2.9.0))(wrangler@4.120.1(@cloudflare/workers-types@5.20260809.1))': + dependencies: + '@cloudflare/unenv-preset': 2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260801.1) + miniflare: 5.20260801.1-alpha + unenv: 2.0.0-rc.24 + vite: 8.2.1(@types/node@22.19.19)(esbuild@0.28.1)(jiti@2.4.2)(yaml@2.9.0) + workerd: 1.20260801.1 + wrangler: 4.120.1(@cloudflare/workers-types@5.20260809.1) + ws: 8.21.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@cloudflare/workerd-darwin-64@1.20260801.1': + optional: true + '@cloudflare/workerd-darwin-64@1.20260804.1': optional: true + '@cloudflare/workerd-darwin-arm64@1.20260801.1': + optional: true + '@cloudflare/workerd-darwin-arm64@1.20260804.1': optional: true + '@cloudflare/workerd-linux-64@1.20260801.1': + optional: true + '@cloudflare/workerd-linux-64@1.20260804.1': optional: true + '@cloudflare/workerd-linux-arm64@1.20260801.1': + optional: true + '@cloudflare/workerd-linux-arm64@1.20260804.1': optional: true + '@cloudflare/workerd-windows-64@1.20260801.1': + optional: true + '@cloudflare/workerd-windows-64@1.20260804.1': optional: true @@ -6561,6 +6644,18 @@ snapshots: mdn-data@2.27.1: {} + miniflare@5.20260801.1-alpha: + dependencies: + '@cspotcode/source-map-support': 0.8.1 + sharp: 0.35.2 + undici: 7.29.0 + workerd: 1.20260801.1 + ws: 8.21.0 + youch: 4.1.0-beta.10 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + miniflare@5.20260804.0-alpha: dependencies: '@cspotcode/source-map-support': 0.8.1 @@ -7188,6 +7283,14 @@ snapshots: word-wrap@1.2.5: {} + workerd@1.20260801.1: + optionalDependencies: + '@cloudflare/workerd-darwin-64': 1.20260801.1 + '@cloudflare/workerd-darwin-arm64': 1.20260801.1 + '@cloudflare/workerd-linux-64': 1.20260801.1 + '@cloudflare/workerd-linux-arm64': 1.20260801.1 + '@cloudflare/workerd-windows-64': 1.20260801.1 + workerd@1.20260804.1: optionalDependencies: '@cloudflare/workerd-darwin-64': 1.20260804.1 From 80de57007e82e004cf9c08c18c1367c3d70693bb Mon Sep 17 00:00:00 2001 From: Ottomated Date: Sat, 15 Aug 2026 09:31:18 -0400 Subject: [PATCH 42/59] fix lockfile --- pnpm-lock.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cbabec134894..ab3492b2ccca 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -183,7 +183,7 @@ importers: dependencies: '@cloudflare/vite-plugin': specifier: ^1.51.1 - version: 1.51.1(vite@8.2.1(@types/node@22.19.19)(esbuild@0.28.1)(jiti@2.4.2)(yaml@2.9.0))(wrangler@4.120.1(@cloudflare/workers-types@5.20260809.1)) + version: 1.51.1(vite@8.2.1(@types/node@22.19.19)(esbuild@0.28.1)(jiti@2.4.2)(yaml@2.9.0))(wrangler@4.120.1(@cloudflare/workers-types@5.20260813.1)) '@cloudflare/workers-types': specifier: ^5.20260813.1 version: 5.20260813.1 @@ -4855,14 +4855,14 @@ snapshots: optionalDependencies: workerd: 1.20260804.1 - '@cloudflare/vite-plugin@1.51.1(vite@8.2.1(@types/node@22.19.19)(esbuild@0.28.1)(jiti@2.4.2)(yaml@2.9.0))(wrangler@4.120.1(@cloudflare/workers-types@5.20260809.1))': + '@cloudflare/vite-plugin@1.51.1(vite@8.2.1(@types/node@22.19.19)(esbuild@0.28.1)(jiti@2.4.2)(yaml@2.9.0))(wrangler@4.120.1(@cloudflare/workers-types@5.20260813.1))': dependencies: '@cloudflare/unenv-preset': 2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260801.1) miniflare: 5.20260801.1-alpha unenv: 2.0.0-rc.24 vite: 8.2.1(@types/node@22.19.19)(esbuild@0.28.1)(jiti@2.4.2)(yaml@2.9.0) workerd: 1.20260801.1 - wrangler: 4.120.1(@cloudflare/workers-types@5.20260809.1) + wrangler: 4.120.1(@cloudflare/workers-types@5.20260813.1) ws: 8.21.0 transitivePeerDependencies: - bufferutil From a59357c357d2c4431eca91354cad6c8559b450d3 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Sun, 16 Aug 2026 15:02:14 -0400 Subject: [PATCH 43/59] wip --- packages/adapter-cloudflare/index.js | 42 ++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/packages/adapter-cloudflare/index.js b/packages/adapter-cloudflare/index.js index 3231647effe0..73312ae0a90e 100644 --- a/packages/adapter-cloudflare/index.js +++ b/packages/adapter-cloudflare/index.js @@ -225,13 +225,31 @@ function cloudflare_vite_plugins(config_path) { { name: 'vite-plugin-adapter-cloudflare-disable-workers-build', apply: 'build', - config(user_config) { + config(config) { // We need to disable @cloudflare/vite-plugin's buildApp hook, // which is set here: https://github.com/cloudflare/workers-sdk/blob/main/packages/vite-plugin-cloudflare/src/plugins/config.ts#L88 // and does not run if config.builder.buildApp is set. - if (!user_config.builder?.buildApp) { - user_config.builder ??= {}; - user_config.builder.buildApp = async () => {}; + if (!config.builder?.buildApp) { + config.builder ??= {}; + config.builder.buildApp = async () => {}; + } + config.environments ??= {}; + config.environments.ssr ??= {}; + config.environments.ssr.build ??= {}; + config.environments.ssr.build.rolldownOptions ??= {}; + const input = config.environments.ssr.build.rolldownOptions.input; + if (typeof input === 'object' && 'index' in input) { + input.server = input.index; + delete input.index; + } + }, + resolveId: { + filter: { id: [exactRegex('SERVER'), exactRegex('MANIFEST')] }, + handler(id, importer, options) { + return { + id: `./${id.toLowerCase()}.js`, + external: true + }; } }, configResolved(config) { @@ -240,18 +258,26 @@ function cloudflare_vite_plugins(config_path) { ); const options = plugin?.api?.options; if (!options) throw new Error('vite-plugin-sveltekit-setup not found'); - out_dir = path.resolve(process.cwd(), options.kit.outDir); + out_dir = path.resolve(process.cwd(), options.outDir); } }, ...cloudflare({ configPath: config_path, + viteEnvironment: { + name: 'ssr' + }, config: (user_config) => { + if ( + !user_config.compatibility_flags.includes('nodejs_compat') && + !user_config.compatibility_flags.includes('nodejs_als') + ) { + user_config.compatibility_flags.push('nodejs_als'); + } const worker_contents = fs .readFileSync(`${files}/worker.js`, 'utf8') - .replace('SERVER', `../output/server/index.js`) - .replace('MANIFEST', '../cloudflare-tmp/manifest.js') .replace('ASSETS', user_config.assets?.binding ?? 'ASSETS'); - const worker_out = `${out_dir}/cloudflare/worker.js`; + const worker_out = `${out_dir}/cloudflare-tmp/worker.js`; + fs.mkdirSync(path.dirname(worker_out), { recursive: true }); fs.writeFileSync(worker_out, worker_contents); user_config.main = worker_out; } From 7bdb76c401337180b5f7b683aa876a697307f377 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Sun, 16 Aug 2026 17:53:06 +0800 Subject: [PATCH 44/59] wip --- packages/kit/src/core/adapt/builder.js | 18 +- packages/kit/src/core/config/index.js | 12 +- packages/kit/src/core/config/options.js | 6 +- packages/kit/src/core/postbuild/analyse.js | 326 ++++++++---------- packages/kit/src/core/postbuild/fallback.js | 141 ++++++-- packages/kit/src/core/postbuild/prerender.js | 320 ++++++++++------- packages/kit/src/exports/vite/dev/index.js | 25 +- packages/kit/src/exports/vite/index.js | 6 +- .../kit/src/runtime/server/page/render.js | 10 +- packages/kit/src/types/internal.d.ts | 2 + 10 files changed, 515 insertions(+), 351 deletions(-) diff --git a/packages/kit/src/core/adapt/builder.js b/packages/kit/src/core/adapt/builder.js index fe6193ab66c6..56b1594a5e97 100644 --- a/packages/kit/src/core/adapt/builder.js +++ b/packages/kit/src/core/adapt/builder.js @@ -110,6 +110,9 @@ export function create_builder({ app_manifest.assets.push({ path: build_data.service_worker }); } + /** @type {string | null} */ + let client = null; + return { log, rimraf: (dir) => rmSync(dir, { force: true, recursive: true }), @@ -170,15 +173,24 @@ export function create_builder({ }, async generateFallback(dest) { - const manifest_path = `${config.outDir}/output/server/manifest-full.js`; + const has_server_build = !!Object.keys(build_data.server_manifest).length; + const manifest_path = has_server_build + ? `${config.outDir}/output/server/manifest-full.js` + : null; const env = loadEnv(vite_config.mode, config.env.dir, ''); + if (!has_server_build) { + client ??= devalue.stringify(build_data.client); + } + const fallback = await generate_fallback({ manifest_path, env, - out_dir: config.outDir, origin: config.paths.origin || 'http://sveltekit-prerender', - assets: config.files.assets + assets: config.files.assets, + out: `${config.outDir}/output`, + vite_config_file: /** @type {string} */ (vite_config.configFile), + client }); if (existsSync(dest)) { diff --git a/packages/kit/src/core/config/index.js b/packages/kit/src/core/config/index.js index 31ba3baa2952..d077ef26c33b 100644 --- a/packages/kit/src/core/config/index.js +++ b/packages/kit/src/core/config/index.js @@ -1,6 +1,6 @@ /** @import { Config } from '@sveltejs/kit/vite' */ /** @import { ValidatedConfig } from 'types' */ -/** @import { ResolvedConfig } from 'vite' */ +/** @import { InlineConfig, ResolvedConfig } from 'vite' */ import fs from 'node:fs'; import path from 'node:path'; import process from 'node:process'; @@ -113,11 +113,17 @@ export function load_error_page(config) { /** * @param {string} [config] * @param {typeof import('vite')} [vite] + * @param {'build' | 'serve'} [command] + * @param {InlineConfig} [inline_config] */ -export async function load_vite_config(config, vite) { +export async function load_vite_config(config, vite, command = 'build', inline_config) { vite ??= /** @type {typeof import('vite')} */ (await import_peer('vite', process.cwd())); - return vite.resolveConfig({ configFile: config }, 'build', process.env.MODE ?? 'production'); + return vite.resolveConfig( + { configFile: config, ...inline_config }, + command, + process.env.MODE ?? 'production' + ); } /** diff --git a/packages/kit/src/core/config/options.js b/packages/kit/src/core/config/options.js index 9deb5193047a..9bd25b5edc95 100644 --- a/packages/kit/src/core/config/options.js +++ b/packages/kit/src/core/config/options.js @@ -1,5 +1,6 @@ /** @import { ValidatedConfig } from 'types' */ /** @import { Validator } from './types.js' */ +import process from 'node:process'; import { styleText } from 'node:util'; const directives = object({ @@ -305,7 +306,10 @@ const options = { ), version: object({ - name: string(Date.now().toString()), + // Date.now() returns a different value each time the config is loaded + // so we try to preserve the initial value in forked processes by passing it + // through environment variables + name: string((process.env.SVELTEKIT_APP_VERSION ??= Date.now().toString())), pollInterval: number(3_600_000) }), diff --git a/packages/kit/src/core/postbuild/analyse.js b/packages/kit/src/core/postbuild/analyse.js index b28d8efe42de..11a171e5e35f 100644 --- a/packages/kit/src/core/postbuild/analyse.js +++ b/packages/kit/src/core/postbuild/analyse.js @@ -1,171 +1,186 @@ -/** @import { RemoteChunk } from 'types' */ -import { join } from 'node:path'; -import { pathToFileURL } from 'node:url'; +import process from 'node:process'; import { validate_server_exports } from '../../utils/exports.js'; -import { extract_svelte_config, load_vite_config } from '../config/index.js'; +import { load_vite_config } from '../config/index.js'; import { forked } from '../../utils/fork.js'; import { BODY_DEPENDENT_METHODS, ENDPOINT_METHODS } from '../../constants.js'; import { has_server_load, resolve_route } from '../../utils/routing.js'; -import { check_feature } from '../../utils/features.js'; -import { createReadableStream } from '@sveltejs/kit/node'; import { PageNodes } from '../../utils/page_nodes.js'; +import { get_runner } from '../../runner.js'; +import { get_runtime_base } from '../utils.js'; +import { import_peer } from '../../utils/import.js'; export default forked(import.meta.url, analyse); /** * @param {{ - * hash: boolean; - * manifest_path: string; - * manifest_data: import('types').ManifestData; - * server_manifest: import('vite').Manifest; - * tracked_features: Record; - * env: Record; - * remotes: RemoteChunk[]; * vite_config_file: string | undefined; + * hash: boolean; * }} opts */ -async function analyse({ - hash, - manifest_path, - manifest_data, - server_manifest, - tracked_features, - env, - remotes, - vite_config_file -}) { - /** @type {import('types').SSRManifest} */ - const manifest = (await import(pathToFileURL(manifest_path).href)).manifest; - - const vite_config = await load_vite_config(vite_config_file); - const config = extract_svelte_config(vite_config); - const server_root = join(config.outDir, 'output'); - - /** @type {import('types').ServerInternalModule} */ - const internal = await import(pathToFileURL(`${server_root}/server/internal.js`).href); - - // configure `import { building } from '$app/env'` — - // essential we do this before analysing the code - internal.set_building(); - - // set `read` and `manifest`, in case they're used in initialisation - internal.set_manifest(manifest); - internal.set_read_implementation((file) => createReadableStream(`${server_root}/server/${file}`)); - - // `set_env` lives in a separate module that imports the user's `src/env` config. We import it - // *after* `set_building()` so that `building`-dependent expressions resolve correctly - /** @type {typeof import('/env/config.js')} */ - const { set_env } = await import(pathToFileURL(`${server_root}/server/env.js`).href); - set_env(env); +async function analyse({ vite_config_file, hash }) { + const vite = /** @type {typeof import('vite')} */ (await import_peer('vite', process.cwd())); + const vite_config = await load_vite_config(vite_config_file, vite, 'serve', { + logLevel: 'silent', + server: { hmr: false } + }); /** @type {import('types').ServerMetadata} */ const metadata = { nodes: [], routes: new Map(), - remotes: new Map() + remotes: new Map(), + should_prerender: hash, + has_dynamic_routes_or_remotes: false }; - const nodes = await Promise.all(manifest.nodes.map((loader) => loader())); - - // analyse nodes - for (const node of nodes) { - if (hash && node.universal) { - const options = Object.keys(node.universal).filter((o) => o !== 'load'); - if (options.length > 0) { - throw new Error( - `Page options are ignored when \`router.type === 'hash'\` (${node.universal_id} has ${options - .filter((o) => o !== 'load') - .map((o) => `'${o}'`) - .join(', ')})` - ); - } + const vite_dev_server = await vite.createServer(vite_config); + await vite_dev_server.listen(); + + try { + // initialise the server with a request so that the SSR manifest gets built + if (!vite_dev_server.resolvedUrls?.local[0]) { + throw new Error('failed to resolve vite dev server url'); } + await fetch(new URL('/_app/building', vite_dev_server.resolvedUrls?.local[0])); - metadata.nodes[node.index] = { - has_server_load: has_server_load(node), - has_universal_load: node.universal?.load !== undefined - }; - } + // configure `import { building } from '$app/env'` — + // essential we do this before analysing the code + const runner = get_runner(vite, vite_dev_server); + const internal = /** @type {typeof import('../../runtime/app/env/server.js')} */ ( + await runner.import(`${get_runtime_base(vite_config.root)}/app/env/server.js`) + ); + internal.set_building(); - // analyse routes - for (const route of manifest.routes) { - const page = - route.page && - analyse_page( - route.page.layouts.map((n) => (n === undefined ? n : nodes[n])), - nodes[route.page.leaf] - ); + /** @type {{ manifest: import('types').SSRManifest }} */ + // @ts-expect-error we've added `__sveltekit` to the Vite dev server object + const { manifest } = vite_dev_server.__sveltekit; - const endpoint = route.endpoint && analyse_endpoint(route, await route.endpoint()); + const nodes = await Promise.all(manifest.nodes.map((loader) => loader())); - if (page?.prerender && endpoint?.prerender) { - throw new Error(`Cannot prerender a route with both +page and +server files (${route.id})`); - } - - if (page?.config && endpoint?.config) { - for (const key in { ...page.config, ...endpoint.config }) { - if (JSON.stringify(page.config[key]) !== JSON.stringify(endpoint.config[key])) { + // analyse nodes + for (const node of nodes) { + if (hash && node.universal) { + const options = Object.keys(node.universal).filter((o) => o !== 'load'); + if (options.length > 0) { throw new Error( - `Mismatched route config for ${route.id} — the +page and +server files must export the same config, if any` + `Page options are ignored when \`router.type === 'hash'\` (${node.universal_id} has ${options + .filter((o) => o !== 'load') + .map((o) => `'${o}'`) + .join(', ')})` ); } } - } - const route_config = page?.config ?? endpoint?.config ?? {}; - const prerender = page?.prerender ?? endpoint?.prerender; - - if (prerender !== true) { - for (const feature of list_features( - route, - manifest_data, - server_manifest, - tracked_features - )) { - check_feature(route.id, route_config, feature, config.adapter); + // load the component module so that Vite processes imported remote functions + try { + await node.component?.(); + } catch { + // ignore errors from loading the component code; we just want Vite + // to process the modules through its pipeline + } + + if (node.server?.actions) { + metadata.has_dynamic_routes_or_remotes = true; } + + metadata.nodes[node.index] = { + has_server_load: has_server_load(node), + has_universal_load: node.universal?.load !== undefined + }; } - const page_methods = page?.methods ?? []; - const api_methods = endpoint?.methods ?? []; - const entries = page?.entries ?? endpoint?.entries; - - metadata.routes.set(route.id, { - config: route_config, - methods: Array.from(new Set([...page_methods, ...api_methods])), - page: { - methods: page_methods - }, - api: { - methods: api_methods - }, - prerender, - entries: - entries && (await entries()).map((entry_object) => resolve_route(route.id, entry_object)) - }); - } + // analyse routes + for (const route of manifest.routes) { + const page = + route.page && + analyse_page( + route.page.layouts.map((n) => (n === undefined ? n : nodes[n])), + nodes[route.page.leaf] + ); - // analyse remotes - for (const remote of remotes) { - const loader = manifest.remotes[remote.hash]; - const { default: functions } = await loader(); + const endpoint = route.endpoint && analyse_endpoint(route, await route.endpoint()); - const exports = new Map(); + if (page?.prerender && endpoint?.prerender) { + throw new Error(`Cannot prerender a route with both +page and +server files (${route.id})`); + } + + if (page?.config && endpoint?.config) { + for (const key in { ...page.config, ...endpoint.config }) { + if (JSON.stringify(page.config[key]) !== JSON.stringify(endpoint.config[key])) { + throw new Error( + `Mismatched route config for ${route.id} — the +page and +server files must export the same config, if any` + ); + } + } + } - for (const name in functions) { - const internals = /** @type {import('types').RemoteInternals} */ (functions[name].__); - const type = internals.type; + const route_config = page?.config ?? endpoint?.config ?? {}; + const prerender = page?.prerender ?? endpoint?.prerender; - exports.set(name, { - type, - dynamic: type !== 'prerender' || internals.dynamic + if (prerender !== undefined || prerender !== false) { + metadata.should_prerender = true; + } + + const page_methods = page?.methods ?? []; + const api_methods = endpoint?.methods ?? []; + const entries = page?.entries ?? endpoint?.entries; + + if ( + (page?.methods.includes('GET') && page.ssr && page.prerender !== true) || // non-prerendered SSR-ed page + (api_methods.length && endpoint?.prerender !== true) // non-prerendered endpoint + ) { + metadata.has_dynamic_routes_or_remotes = true; + } + + metadata.routes.set(route.id, { + config: route_config, + methods: Array.from(new Set([...page_methods, ...api_methods])), + page: { + methods: page_methods + }, + api: { + methods: api_methods + }, + prerender, + entries: + entries && (await entries()).map((entry_object) => resolve_route(route.id, entry_object)) }); } - metadata.remotes.set(remote.hash, exports); - } + // analyse remotes + for (const [remote_hash, loader] of Object.entries(manifest.remotes)) { + const { default: functions } = await loader(); + + const exports = new Map(); + + for (const name in functions) { + const internals = /** @type {import('types').RemoteInternals} */ (functions[name].__); + const type = internals.type; + const dynamic = type !== 'prerender' || internals.dynamic; + + exports.set(name, { + type, + dynamic + }); + + if (internals.type === 'prerender') { + metadata.should_prerender = true; + } - return { metadata }; + if (dynamic) { + metadata.has_dynamic_routes_or_remotes = true; + } + } + + metadata.remotes.set(remote_hash, exports); + } + + await vite_dev_server.close(); + + return { metadata }; + } finally { + await vite_dev_server.close(); + } } /** @@ -221,60 +236,7 @@ function analyse_page(layouts, leaf) { config: nodes.get_config(), entries: leaf.universal?.entries ?? leaf.server?.entries, methods, - prerender: nodes.prerender() + prerender: nodes.prerender(), + ssr: nodes.ssr() }; } - -/** - * @param {import('types').SSRRoute} route - * @param {import('types').ManifestData} manifest_data - * @param {import('vite').Manifest} server_manifest - * @param {Record} tracked_features - */ -function list_features(route, manifest_data, server_manifest, tracked_features) { - const features = new Set(); - - const route_data = /** @type {import('types').RouteData} */ ( - manifest_data.routes.find((r) => r.id === route.id) - ); - - const visited = new Set(); - /** @param {string} id */ - function visit(id) { - if (visited.has(id)) return; - visited.add(id); - - const chunk = server_manifest[id]; - if (!chunk) return; - - if (chunk.file in tracked_features) { - for (const feature of tracked_features[chunk.file]) { - features.add(feature); - } - } - - if (chunk.imports) { - for (const id of chunk.imports) { - visit(id); - } - } - } - - let page_node = route_data?.leaf; - while (page_node) { - if (page_node.server) visit(page_node.server); - page_node = page_node.parent ?? null; - } - - if (route_data.endpoint) { - visit(route_data.endpoint.file); - } - - if (manifest_data.hooks.server) { - // TODO if hooks.server.js imports `read`, it will be in the entry chunk - // we don't currently account for that case - visit(manifest_data.hooks.server); - } - - return Array.from(features); -} diff --git a/packages/kit/src/core/postbuild/fallback.js b/packages/kit/src/core/postbuild/fallback.js index 335a72bfd049..b9fc38301669 100644 --- a/packages/kit/src/core/postbuild/fallback.js +++ b/packages/kit/src/core/postbuild/fallback.js @@ -1,53 +1,148 @@ import { readFileSync } from 'node:fs'; import { join } from 'node:path'; +import process from 'node:process'; import { pathToFileURL } from 'node:url'; +import * as devalue from 'devalue'; import { forked } from '../../utils/fork.js'; import { stackless } from '../../utils/error.js'; +import { import_peer } from '../../utils/import.js'; +import { load_vite_config } from '../config/index.js'; +import { get_runtime_base } from '../utils.js'; +import { get_runner } from '../../runner.js'; export default forked(import.meta.url, generate_fallback); /** * @param {{ - * manifest_path: string; + * manifest_path: string | null; * env: Record - * out_dir: string; + * out: string; * origin: string; * assets: string; + * vite_config_file: string; + * client: string; * }} opts */ -async function generate_fallback({ manifest_path, env, out_dir, origin, assets }) { - const server_root = join(out_dir, 'output'); +async function generate_fallback({ + manifest_path, + env, + out, + origin, + assets, + vite_config_file, + client +}) { + const { server, vite_dev_server } = manifest_path + ? await get_ssr_build_server({ manifest_path, out, env }) + : await get_ssr_vite_server({ vite_config_file, env, client }); - /** @type {import('types').ServerInternalModule} */ - const { set_building } = await import(pathToFileURL(`${server_root}/server/internal.js`).href); + try { + const response = await server.respond(new Request(origin + '/[fallback]'), { + getClientAddress: () => { + throw new Error('Cannot read clientAddress during prerendering'); + }, + prerendering: { + fallback: true, + dependencies: new Map(), + remote_responses: new Map(), + resolved_route_ids: new Set() + }, + read: (file) => readFileSync(join(assets, file)) + }); - /** @type {import('types').ServerModule} */ - const { Server } = await import(pathToFileURL(`${server_root}/server/index.js`).href); + if (response.ok) { + return await response.text(); + } + } finally { + await vite_dev_server?.close(); + } + throw stackless('Could not create a fallback page'); +} + +/** + * @param {object} opts + * @param {string} opts.manifest_path + * @param {string} opts.out + * @param {Record} opts.env + */ +async function get_ssr_build_server({ manifest_path, out, env }) { /** @type {import('types').SSRManifest} */ const manifest = (await import(pathToFileURL(manifest_path).href)).manifest; + /** @type {import('types').ServerInternalModule} */ + const { set_building } = await import(pathToFileURL(`${out}/server/internal.js`).href); + + // configure `import { building } from `$app/env` — + // essential we do this before analysing the code set_building(); + /** @type {import('types').ServerModule} */ + const { Server } = await import(pathToFileURL(`${out}/server/index.js`).href); + const server = new Server(manifest); await server.init({ env }); - const response = await server.respond(new Request(origin + '/[fallback]'), { - getClientAddress: () => { - throw new Error('Cannot read clientAddress during prerendering'); - }, - prerendering: { - fallback: true, - dependencies: new Map(), - remote_responses: new Map(), - resolved_route_ids: new Set() - }, - read: (file) => readFileSync(join(assets, file)) + return { + server, + vite_dev_server: null + }; +} + +/** + * @param {object}opts + * @param {string} opts.vite_config_file + * @param {Record} opts.env + * @param {string} opts.client + */ +async function get_ssr_vite_server({ vite_config_file, env, client }) { + const vite = /** @type {typeof import('vite')} */ (await import_peer('vite', process.cwd())); + const vite_config = await load_vite_config(vite_config_file, vite, 'serve', { + logLevel: 'silent', + server: { hmr: false } }); - if (response.ok) { - return await response.text(); - } + const vite_dev_server = await vite.createServer(vite_config); + await vite_dev_server.listen(0); - throw stackless('Could not create a fallback page'); + try { + // initialise the server with a request so that the SSR manifest gets built + if (!vite_dev_server.resolvedUrls?.local[0]) { + throw new Error('failed to resolve vite dev server url'); + } + await fetch(new URL('/_app/building', vite_dev_server.resolvedUrls?.local[0])); + + const runtime_base = get_runtime_base(vite_config.root); + + const runner = get_runner(vite, vite_dev_server); + const env_internal = /** @type {typeof import('../../runtime/app/env/server.js')} */ ( + await runner.import(`${runtime_base}/app/env/server.js`) + ); + + // configure `import { building } from `$app/env` — + // essential we do this before analysing the code + env_internal.set_building(); + + // `set_env` and `Server` live in modules that import the user's `src/env` config. We import them + // *after* `set_building()` so that `building`-dependent expressions resolve correctly + + /** @type {import('types').ServerModule} */ + const { Server } = await runner.import(`${runtime_base}/server/index.js`); + + /** @type {{ manifest: import('types').SSRManifest }} */ + // @ts-expect-error we've added `__sveltekit` to the Vite dev server object + const { manifest } = vite_dev_server.__sveltekit; + manifest.client = devalue.parse(client); + + const server = new Server(manifest); + await server.init({ env }); + + return { + server, + vite_dev_server + }; + } catch (error) { + await vite_dev_server.close(); + throw error; + } } diff --git a/packages/kit/src/core/postbuild/prerender.js b/packages/kit/src/core/postbuild/prerender.js index 2d07b4df5e17..3143d44d577d 100644 --- a/packages/kit/src/core/postbuild/prerender.js +++ b/packages/kit/src/core/postbuild/prerender.js @@ -1,3 +1,4 @@ +/** @import { Prerendered, PrerenderMap } from 'types' */ import process from 'node:process'; import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; @@ -6,7 +7,7 @@ import { walk } from '../../utils/filesystem.js'; import { noop } from '../../utils/functions.js'; import { decode_uri, is_root_relative, resolve } from '../../utils/url.js'; import { escape_html } from '../../utils/escape.js'; -import { logger } from '../utils.js'; +import { get_runtime_base, logger } from '../utils.js'; import { extract_svelte_config, load_vite_config } from '../config/index.js'; import { get_route_segments } from '../../utils/routing.js'; import { queue } from './queue.js'; @@ -17,6 +18,10 @@ import { createReadableStream } from '@sveltejs/kit/node'; import generate_fallback from './fallback.js'; import { stringify_remote_arg } from '../../runtime/shared.js'; import { matches_content_type } from '../../utils/http.js'; +import { import_peer } from '../../utils/import.js'; +import { get_runner } from '../../runner.js'; +import { from_fs } from '../../utils/vite.js'; +import { format_response } from '../../runtime/server/internal.js'; export default forked(import.meta.url, prerender); @@ -30,12 +35,17 @@ const SPECIAL_HASHLINKS = new Set(['', 'top']); * @param {{ * hash: boolean; * out: string; - * manifest_path: string; + * manifest_path: string | null; * metadata: import('types').ServerMetadata; * verbose: boolean; * env: Record; - * vite_config_file: string | undefined; + * vite_config_file: string; * is_tty: boolean | undefined; + * origin: string; + * assets: string; + * prerendered: Prerendered; + * prerender_map: PrerenderMap; + * client: string; * }} opts */ async function prerender({ @@ -46,29 +56,13 @@ async function prerender({ verbose, env, vite_config_file, - is_tty + is_tty, + origin, + assets, + prerendered, + prerender_map, + client }) { - /** @type {import('types').SSRManifest} */ - const manifest = (await import(pathToFileURL(manifest_path).href)).manifest; - - /** @type {import('types').ServerInternalModule} */ - const { set_building, set_prerendering, set_manifest, set_read_implementation, format_response } = - await import(pathToFileURL(`${out}/server/internal.js`).href); - - // configure `import { building } from `$app/env` — - // essential we do this before analysing the code - set_building(); - set_prerendering(); - - // `set_env` and `Server` live in modules that import the user's `src/env` config. We import them - // *after* `set_building()` so that `building`-dependent expressions resolve correctly - /** @type {typeof import('/env/config.js')} */ - const { set_env } = await import(pathToFileURL(`${out}/server/env.js`).href); - set_env(env); - - /** @type {import('types').ServerModule} */ - const { Server } = await import(pathToFileURL(`${out}/server/index.js`).href); - const throw_handled = () => { throw new Error('__handled__'); }; @@ -129,43 +123,24 @@ async function prerender({ const OK = 2; const REDIRECT = 3; - /** @type {import('types').Prerendered} */ - const prerendered = { - pages: new Map(), - assets: new Map(), - redirects: new Map(), - paths: [] - }; - - /** @type {import('types').PrerenderMap} */ - const prerender_map = new Map(); - - for (const [id, { prerender }] of metadata.routes) { - if (prerender !== undefined) { - prerender_map.set(id, prerender); - } - } - /** @type {Set} */ const prerendered_routes = new Set(); - const vite_config = await load_vite_config(vite_config_file); - - const config = extract_svelte_config(vite_config); - - const prerender_origin = config.paths.origin || 'http://sveltekit-prerender'; + const prerender_origin = origin || 'http://sveltekit-prerender'; if (hash) { const fallback = await generate_fallback({ manifest_path, env, - out_dir: config.outDir, + out, origin: prerender_origin, - assets: config.files.assets + assets, + vite_config_file, + client }); const file = output_filename('/', true); - const dest = `${config.outDir}/output/prerendered/pages/${file}`; + const dest = `${out}/prerendered/pages/${file}`; mkdirSync(dirname(dest), { recursive: true }); writeFileSync(dest, fallback); @@ -175,6 +150,11 @@ async function prerender({ return { prerendered, prerender_map }; } + const vite = /** @type {typeof import('vite')} */ (await import_peer('vite', process.cwd())); + const vite_config = await load_vite_config(vite_config_file, vite); + + const config = extract_svelte_config(vite_config); + const emulator = await config.adapter?.emulate?.(); /** @type {import('types').Logger} */ @@ -642,112 +622,196 @@ async function prerender({ } } - let should_prerender = false; + const { manifest, server, vite_dev_server } = await (manifest_path + ? get_ssr_build_server({ + manifest_path, + out, + env + }) + : get_ssr_vite_server({ vite, vite_config_file, env, client })); - for (const value of prerender_map.values()) { - if (value) { - should_prerender = true; - break; + try { + /** @type {Array} */ + const prerender_functions = []; + + for (const loader of Object.values(manifest.remotes)) { + const module = await loader(); + + for (const fn of Object.values(module.default)) { + if (fn?.__?.type === 'prerender') { + prerender_functions.push(fn.__); + } + } } - } - // the user's remote function modules may reference `read` or the `manifest` at the top-level - // so we need to set them before evaluating those modules to avoid potential runtime errors - set_manifest(manifest); - set_read_implementation((file) => createReadableStream(`${out}/server/${file}`)); + log.info('Prerendering'); - /** @type {Array} */ - const prerender_functions = []; + for (const entry of config.prerender.entries) { + if (entry === '*') { + for (const [id, prerender] of prerender_map) { + if (prerender) { + // remove optional parameters from the route + const segments = get_route_segments(id).filter((segment) => !segment.startsWith('[[')); + const processed_id = '/' + segments.join('/'); - for (const loader of Object.values(manifest.remotes)) { - const module = await loader(); + if (processed_id.includes('[')) continue; + const path = `/${get_route_segments(processed_id).join('/')}`; + void enqueue(null, config.paths.base + path); + } + } + } else { + void enqueue(null, config.paths.base + entry); + } + } - for (const fn of Object.values(module.default)) { - if (fn?.__?.type === 'prerender') { - prerender_functions.push(fn.__); - should_prerender = true; + for (const { id, entries } of route_level_entries) { + for (const entry of entries) { + void enqueue(null, config.paths.base + entry, undefined, id); + } + } + + for (const internals of prerender_functions) { + if (internals.has_arg) { + for (const arg of (await internals.inputs?.()) ?? []) { + void enqueue(null, remote_prefix + internals.id + '/' + stringify_remote_arg(arg)); + } + } else { + void enqueue(null, remote_prefix + internals.id); + } + } + + await q.done(); + progress?.clear(); + + // handle invalid fragment links + for (const [key, referrers] of expected_hashlinks) { + const index = key.indexOf('#'); + const path = key.slice(0, index); + const id = key.slice(index + 1); + + const hashlinks = actual_hashlinks.get(path); + // ignore fragment links to pages that were not prerendered + if (!hashlinks) continue; + + if (!hashlinks.has(id) && !SPECIAL_HASHLINKS.has(id)) { + handle_missing_id({ id, path, referrers: Array.from(referrers) }); + } + } + + /** @type {string[]} */ + const not_prerendered = []; + + for (const [route_id, prerender] of prerender_map) { + if (prerender === true && !prerendered_routes.has(route_id)) { + not_prerendered.push(route_id); } } - } - if (!should_prerender) { + if (not_prerendered.length > 0) { + handle_not_prerendered_route({ routes: not_prerendered }); + } + return { prerendered, prerender_map }; + } finally { + await vite_dev_server?.close(); } +} + +/** + * @param {object} opts + * @param {string} opts.manifest_path + * @param {string} opts.out + * @param {Record} opts.env + */ +async function get_ssr_build_server({ manifest_path, out, env }) { + /** @type {import('types').SSRManifest} */ + const manifest = (await import(pathToFileURL(manifest_path).href)).manifest; + + /** @type {import('types').ServerInternalModule} */ + const { set_building, set_prerendering } = await import( + pathToFileURL(`${out}/server/internal.js`).href + ); + + // configure `import { building } from `$app/env` — + // essential we do this before analysing the code + set_building(); + set_prerendering(); + + /** @type {import('types').ServerModule} */ + const { Server } = await import(pathToFileURL(`${out}/server/index.js`).href); - // only run the server after the `should_prerender` check so that we - // don't run the user's init hook unnecessarily const server = new Server(manifest); await server.init({ env, - read: (file) => createReadableStream(`${config.outDir}/output/server/${file}`) + read: (file) => createReadableStream(`${out}/server/${file}`) }); - log.info('Prerendering'); + return { + manifest, + server, + vite_dev_server: null + }; +} - for (const entry of config.prerender.entries) { - if (entry === '*') { - for (const [id, prerender] of prerender_map) { - if (prerender) { - // remove optional parameters from the route - const segments = get_route_segments(id).filter((segment) => !segment.startsWith('[[')); - const processed_id = '/' + segments.join('/'); +/** + * @param {object} opts + * @param {typeof import('vite')} opts.vite + * @param {string} opts.vite_config_file + * @param {Record} opts.env + * @param {string} opts.client + */ +async function get_ssr_vite_server({ vite, vite_config_file, env, client }) { + const vite_config = await load_vite_config(vite_config_file, vite, 'serve', { + logLevel: 'silent', + server: { hmr: false } + }); - if (processed_id.includes('[')) continue; - const path = `/${get_route_segments(processed_id).join('/')}`; - void enqueue(null, config.paths.base + path); - } - } - } else { - void enqueue(null, config.paths.base + entry); - } - } + const vite_dev_server = await vite.createServer(vite_config); + await vite_dev_server.listen(); - for (const { id, entries } of route_level_entries) { - for (const entry of entries) { - void enqueue(null, config.paths.base + entry, undefined, id); + try { + // initialise the server with a request so that the SSR manifest gets built + if (!vite_dev_server.resolvedUrls?.local[0]) { + throw new Error('failed to resolve vite dev server url'); } - } + await fetch(new URL('/_app/building', vite_dev_server.resolvedUrls?.local[0])); - for (const internals of prerender_functions) { - if (internals.has_arg) { - for (const arg of (await internals.inputs?.()) ?? []) { - void enqueue(null, remote_prefix + internals.id + '/' + stringify_remote_arg(arg)); - } - } else { - void enqueue(null, remote_prefix + internals.id); - } - } + const runtime_base = get_runtime_base(vite_config.root); - await q.done(); - progress?.clear(); + const runner = get_runner(vite, vite_dev_server); + const env_internal = /** @type {typeof import('../../runtime/app/env/server.js')} */ ( + await runner.import(`${runtime_base}/app/env/server.js`) + ); - // handle invalid fragment links - for (const [key, referrers] of expected_hashlinks) { - const index = key.indexOf('#'); - const path = key.slice(0, index); - const id = key.slice(index + 1); + // configure `import { building } from `$app/env` — + // essential we do this before analysing the code + env_internal.set_building(); + env_internal.set_prerendering(); - const hashlinks = actual_hashlinks.get(path); - // ignore fragment links to pages that were not prerendered - if (!hashlinks) continue; + // `set_env` and `Server` live in modules that import the user's `src/env` config. We import them + // *after* `set_building()` so that `building`-dependent expressions resolve correctly - if (!hashlinks.has(id) && !SPECIAL_HASHLINKS.has(id)) { - handle_missing_id({ id, path, referrers: Array.from(referrers) }); - } - } + /** @type {import('types').ServerModule} */ + const { Server } = await runner.import(`${runtime_base}/server/index.js`); - /** @type {string[]} */ - const not_prerendered = []; + /** @type {{ manifest: import('types').SSRManifest }} */ + // @ts-expect-error we've added `__sveltekit` to the Vite dev server object + const { manifest } = vite_dev_server.__sveltekit; + manifest.client = devalue.parse(client); - for (const [route_id, prerender] of prerender_map) { - if (prerender === true && !prerendered_routes.has(route_id)) { - not_prerendered.push(route_id); - } - } + const server = new Server(manifest); + await server.init({ + env, + read: (file) => createReadableStream(from_fs(file)) + }); - if (not_prerendered.length > 0) { - handle_not_prerendered_route({ routes: not_prerendered }); + return { + manifest, + server, + vite_dev_server + }; + } catch (error) { + await vite_dev_server.close(); + throw error; } - - return { prerendered, prerender_map }; } diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 1db46c785c60..cac835bf349b 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -349,6 +349,8 @@ export async function dev( return module.params; } }; + // @ts-expect-error we're adding `__sveltekit` to the Vite dev server object + vite_dev_server.__sveltekit = { manifest }; } /** @param {Error} error */ @@ -503,6 +505,24 @@ export async function dev( /** @type {Promise | undefined} */ let init_manifest; + vite_dev_server.middlewares.stack.unshift({ + route: '', + /** @type {import('vite').Connect.NextHandleFunction} */ + handle: async (req, res, next) => { + // Vite throws a Cannot read properties of undefined (reading 'wrapDynamicImport') + // if you try to run ssr.runner.import before the server has started so + // we do it inside here to avoid that + await (init_manifest ??= update_manifest()); + + if (req.url?.endsWith('/_app/building')) { + res.end(); + return; + } + + next(); + } + }); + return () => { const serve_static_middleware = vite_dev_server.middlewares.stack.find( (middleware) => @@ -514,11 +534,6 @@ export async function dev( remove_static_middlewares(vite_dev_server.middlewares); vite_dev_server.middlewares.use(async (req, res) => { - // Vite throws a Cannot read properties of undefined (reading 'wrapDynamicImport') - // if you try to run ssr.runner.import before the server has started so - // we do it inside here to avoid that - await (init_manifest ??= update_manifest()); - // Vite's base middleware strips out the base path. Restore it const original_url = req.url; req.url = req.originalUrl; diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index f46915e1068e..9fe355a2dfc3 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -332,7 +332,11 @@ function kit({ svelte_config }) { order: 'pre', async handler(config, config_env) { initial_config = config; - is_build = config_env.command === 'build'; + + // if the initial command was `build`, we want to reuse that whenever + // the plugin loads again + process.env.SVELTEKIT_BUILD ??= s(config_env.command === 'build'); + is_build = process.env.SVELTEKIT_BUILD === 'true'; kit = process_config(svelte_config, root); out_dir = posixify(kit.outDir); diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index 0f9a908cd0ef..db41f8c1fe0d 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -265,7 +265,7 @@ export async function render_response({ for (const url of node.stylesheets) stylesheets.add(url); for (const font of node.fonts) fonts.set(font.file, font); - if (node.inline_styles && !client?.inline) { + if (node.inline_styles && (!client || !client.inline)) { Object.entries(await node.inline_styles()).forEach(([filename, css]) => { if (typeof css === 'string') { inline_styles.set(filename, css); @@ -291,18 +291,18 @@ export async function render_response({ return `${assets}/${path}`; }; - const style = client?.inline + const inline_style_content = client?.inline ? client.inline?.style : Array.from(inline_styles.values()).join('\n'); - if (style) { + if (inline_style_content) { // We always inline all styles to avoid FOUC during development. // Once that's accomplished, we find and remove the style node using the // `data-sveltekit` attribute once CSR kicks in const attributes = __SVELTEKIT_DEV__ ? ['data-sveltekit'] : []; if (csp.style_needs_nonce) attributes.push(`nonce="${csp.nonce}"`); - csp.add_style(style); - head.add_style(style, attributes); + csp.add_style(inline_style_content); + head.add_style(inline_style_content, attributes); } /** diff --git a/packages/kit/src/types/internal.d.ts b/packages/kit/src/types/internal.d.ts index fa707c7f45d4..c943d825cd84 100644 --- a/packages/kit/src/types/internal.d.ts +++ b/packages/kit/src/types/internal.d.ts @@ -431,6 +431,8 @@ export interface ServerMetadata { routes: Map; /** For each hashed remote file, a map of export name -> { type, dynamic }, where `dynamic` is `false` for non-dynamic prerender functions */ remotes: Map>; + should_prerender: boolean; + has_dynamic_routes_or_remotes: boolean; } export type SSRComponentLoader = () => Promise; From 02757f8c81d9bd7edcb5a117632e75428afd1ab9 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Sun, 16 Aug 2026 18:05:50 +0800 Subject: [PATCH 45/59] empty From 5f3181c229770f9db03c86f054111ac5dd7af942 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Sun, 16 Aug 2026 18:06:03 +0800 Subject: [PATCH 46/59] empty From 85430f78ff433d337e451eeab9e0e72be5c92aa2 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Sun, 16 Aug 2026 18:16:52 +0800 Subject: [PATCH 47/59] Update env-vars.js --- packages/kit/src/exports/vite/plugins/env-vars.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/kit/src/exports/vite/plugins/env-vars.js b/packages/kit/src/exports/vite/plugins/env-vars.js index e4e8c3e8a71c..385b53b92071 100644 --- a/packages/kit/src/exports/vite/plugins/env-vars.js +++ b/packages/kit/src/exports/vite/plugins/env-vars.js @@ -85,6 +85,7 @@ export function plugin_env_vars(config, callback) { // runs once via the memo — per-process, whichever environment starts first // (environment names vary by adapter), and never in the postbuild forks, // which resolve the config without building + // TODO: does this need to rerun for build --watch ? await (generated ??= (async () => { resolved_entry = resolve_env_entry(config, resolved_config.root); await generate(); From 674784ccfc717b0e86bc2e0b001c5551325e37ef Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Sun, 16 Aug 2026 18:17:57 +0800 Subject: [PATCH 48/59] Update packages/kit/src/core/postbuild/analyse.js Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> --- packages/kit/src/core/postbuild/analyse.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/src/core/postbuild/analyse.js b/packages/kit/src/core/postbuild/analyse.js index 11a171e5e35f..9f0a22c715e2 100644 --- a/packages/kit/src/core/postbuild/analyse.js +++ b/packages/kit/src/core/postbuild/analyse.js @@ -117,7 +117,7 @@ async function analyse({ vite_config_file, hash }) { const route_config = page?.config ?? endpoint?.config ?? {}; const prerender = page?.prerender ?? endpoint?.prerender; - if (prerender !== undefined || prerender !== false) { + if (prerender !== undefined && prerender !== false) { metadata.should_prerender = true; } From 184e9f92caed37a18acf84e6e2b0957035bef8fb Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Sun, 16 Aug 2026 19:01:00 +0800 Subject: [PATCH 49/59] fix merge changes --- packages/kit/src/core/postbuild/fallback.js | 24 +++++++++++-------- packages/kit/src/core/postbuild/prerender.js | 4 ++-- packages/kit/src/types/internal.d.ts | 1 + .../test/apps/no-ssr/test/no-script.test.js | 2 ++ 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/packages/kit/src/core/postbuild/fallback.js b/packages/kit/src/core/postbuild/fallback.js index b9fc38301669..2dff9c2fc9e8 100644 --- a/packages/kit/src/core/postbuild/fallback.js +++ b/packages/kit/src/core/postbuild/fallback.js @@ -13,15 +13,15 @@ import { get_runner } from '../../runner.js'; export default forked(import.meta.url, generate_fallback); /** - * @param {{ - * manifest_path: string | null; - * env: Record - * out: string; - * origin: string; - * assets: string; - * vite_config_file: string; - * client: string; - * }} opts + * @param {object} opts + * @param {string | null} opts.manifest_path + * @param {Record} opts.env + * @param {string} opts.out + * @param {string} opts.origin + * @param {string} opts.assets + * @param {string} opts.vite_config_file + * @param {string | null} opts.client + * @returns {Promise} */ async function generate_fallback({ manifest_path, @@ -34,7 +34,11 @@ async function generate_fallback({ }) { const { server, vite_dev_server } = manifest_path ? await get_ssr_build_server({ manifest_path, out, env }) - : await get_ssr_vite_server({ vite_config_file, env, client }); + : await get_ssr_vite_server({ + vite_config_file, + env, + client: /** @type {string} */ (client) + }); try { const response = await server.respond(new Request(origin + '/[fallback]'), { diff --git a/packages/kit/src/core/postbuild/prerender.js b/packages/kit/src/core/postbuild/prerender.js index 3143d44d577d..18c18b2ae3a1 100644 --- a/packages/kit/src/core/postbuild/prerender.js +++ b/packages/kit/src/core/postbuild/prerender.js @@ -45,7 +45,7 @@ const SPECIAL_HASHLINKS = new Set(['', 'top']); * assets: string; * prerendered: Prerendered; * prerender_map: PrerenderMap; - * client: string; + * client: string | null; * }} opts */ async function prerender({ @@ -628,7 +628,7 @@ async function prerender({ out, env }) - : get_ssr_vite_server({ vite, vite_config_file, env, client })); + : get_ssr_vite_server({ vite, vite_config_file, env, client: /** @type {string} */ (client) })); try { /** @type {Array} */ diff --git a/packages/kit/src/types/internal.d.ts b/packages/kit/src/types/internal.d.ts index c943d825cd84..4f27becd1d20 100644 --- a/packages/kit/src/types/internal.d.ts +++ b/packages/kit/src/types/internal.d.ts @@ -79,6 +79,7 @@ export interface BuildData { manifest_data: ManifestData; out_dir: string; service_worker: string | null; + /** populates the SSRManifest's _.client property */ client: { /** Path to the client entry point. */ start: string; diff --git a/packages/kit/test/apps/no-ssr/test/no-script.test.js b/packages/kit/test/apps/no-ssr/test/no-script.test.js index ab489a195dd2..1a5e56e8cc01 100644 --- a/packages/kit/test/apps/no-ssr/test/no-script.test.js +++ b/packages/kit/test/apps/no-ssr/test/no-script.test.js @@ -31,3 +31,5 @@ test('styles are loaded before CSR starts for prerendered routes', async ({ page await page.goto('/styles/prerendered'); expect(requests.length).toBe(1); }); + +// TODO: test that server build is skipped From 30b7403c560134a2a569c301303749ff192494c4 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Sun, 16 Aug 2026 22:43:16 +0800 Subject: [PATCH 50/59] fixes --- packages/kit/src/core/postbuild/analyse.js | 38 +++++++-- packages/kit/src/exports/vite/dev/index.js | 1 + packages/kit/src/exports/vite/index.js | 3 +- packages/kit/src/exports/vite/utils.js | 95 ++-------------------- packages/kit/src/types/internal.d.ts | 2 +- 5 files changed, 40 insertions(+), 99 deletions(-) diff --git a/packages/kit/src/core/postbuild/analyse.js b/packages/kit/src/core/postbuild/analyse.js index 9f0a22c715e2..865528ccd72b 100644 --- a/packages/kit/src/core/postbuild/analyse.js +++ b/packages/kit/src/core/postbuild/analyse.js @@ -1,4 +1,5 @@ import process from 'node:process'; +import { createReadableStream } from '@sveltejs/kit/node'; import { validate_server_exports } from '../../utils/exports.js'; import { load_vite_config } from '../config/index.js'; import { forked } from '../../utils/fork.js'; @@ -8,16 +9,18 @@ import { PageNodes } from '../../utils/page_nodes.js'; import { get_runner } from '../../runner.js'; import { get_runtime_base } from '../utils.js'; import { import_peer } from '../../utils/import.js'; +import { from_fs } from '../../utils/vite.js'; export default forked(import.meta.url, analyse); /** * @param {{ - * vite_config_file: string | undefined; * hash: boolean; + * env: Record; + * vite_config_file: string | undefined; * }} opts */ -async function analyse({ vite_config_file, hash }) { +async function analyse({ hash, env, vite_config_file }) { const vite = /** @type {typeof import('vite')} */ (await import_peer('vite', process.cwd())); const vite_config = await load_vite_config(vite_config_file, vite, 'serve', { logLevel: 'silent', @@ -30,7 +33,7 @@ async function analyse({ vite_config_file, hash }) { routes: new Map(), remotes: new Map(), should_prerender: hash, - has_dynamic_routes_or_remotes: false + has_dynamic_server_routes_or_remotes: false }; const vite_dev_server = await vite.createServer(vite_config); @@ -46,14 +49,31 @@ async function analyse({ vite_config_file, hash }) { // configure `import { building } from '$app/env'` — // essential we do this before analysing the code const runner = get_runner(vite, vite_dev_server); - const internal = /** @type {typeof import('../../runtime/app/env/server.js')} */ ( - await runner.import(`${get_runtime_base(vite_config.root)}/app/env/server.js`) + const runtime_base = get_runtime_base(vite_config.root); + + const { set_building } = /** @type {typeof import('../../runtime/app/env/server.js')} */ ( + await runner.import(`${runtime_base}/app/env/server.js`) ); - internal.set_building(); + set_building(); + + // set `read` and `manifest`, in case they're used in initialisation + const { set_read_implementation, set_manifest } = + /** @type {typeof import('../../runtime/server/internal.js')} */ ( + await runner.import(`${runtime_base}/server/internal.js`) + ); + set_read_implementation((file) => createReadableStream(from_fs(file))); /** @type {{ manifest: import('types').SSRManifest }} */ // @ts-expect-error we've added `__sveltekit` to the Vite dev server object const { manifest } = vite_dev_server.__sveltekit; + set_manifest(manifest); + + // `set_env` lives in a separate module that imports the user's `src/env` config. We import it + // *after* `set_building()` so that `building`-dependent expressions resolve correctly + const { set_env } = /** @type {import('__sveltekit/env')} */ ( + await runner.import('__sveltekit/env') + ); + set_env(env); const nodes = await Promise.all(manifest.nodes.map((loader) => loader())); @@ -80,7 +100,7 @@ async function analyse({ vite_config_file, hash }) { } if (node.server?.actions) { - metadata.has_dynamic_routes_or_remotes = true; + metadata.has_dynamic_server_routes_or_remotes = true; } metadata.nodes[node.index] = { @@ -129,7 +149,7 @@ async function analyse({ vite_config_file, hash }) { (page?.methods.includes('GET') && page.ssr && page.prerender !== true) || // non-prerendered SSR-ed page (api_methods.length && endpoint?.prerender !== true) // non-prerendered endpoint ) { - metadata.has_dynamic_routes_or_remotes = true; + metadata.has_dynamic_server_routes_or_remotes = true; } metadata.routes.set(route.id, { @@ -168,7 +188,7 @@ async function analyse({ vite_config_file, hash }) { } if (dynamic) { - metadata.has_dynamic_routes_or_remotes = true; + metadata.has_dynamic_server_routes_or_remotes = true; } } diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index cac835bf349b..d7ab747a7357 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -180,6 +180,7 @@ export async function dev( return; } + // TODO: try to abstract the dev SSR manifest creation into a reusable function manifest = { app_dir: svelte_config.appDir, app_path: svelte_config.appDir, diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 9fe355a2dfc3..e19674333ddf 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -15,7 +15,8 @@ import { runtime_directory, logger, get_global_name } from '../../core/utils.js' import { dev } from './dev/index.js'; import { preview } from './preview/index.js'; import { - enforced_config, + clean_id, + error_for_missing_config, get_config_aliases, is_remote_module, remote_module_pattern, diff --git a/packages/kit/src/exports/vite/utils.js b/packages/kit/src/exports/vite/utils.js index 72b7683e8aa0..b0156221d270 100644 --- a/packages/kit/src/exports/vite/utils.js +++ b/packages/kit/src/exports/vite/utils.js @@ -232,95 +232,14 @@ export function error_for_missing_config(feature_name, path, value) { ); } -/** @type {EnforcedConfig} */ -export const enforced_config = { - appType: true, - base: true, - build: { - cssCodeSplit: true, - emptyOutDir: true, - lib: { - entry: true, - name: true, - formats: true - }, - manifest: true, - outDir: true, - rolldownOptions: { - input: true, - output: { - format: true, - entryFileNames: true, - chunkFileNames: true, - assetFileNames: true - }, - preserveEntrySignatures: true - }, - ssr: true - }, - publicDir: true, - resolve: { - alias: { - $app: true, - $env: true, - '': true - } - } -}; - -/** - * @param {UserConfig} config - * @param {UserConfig} resolved_config - */ -export function warn_overridden_config(config, resolved_config) { - const overridden = find_overridden_config(config, resolved_config, enforced_config, '', []); - - if (overridden.length > 0) { - console.error( - styleText( - ['bold', 'red'], - 'The following Vite config options will be overridden by SvelteKit:' - ) + overridden.map((key) => `\n - ${key}`).join('') - ); - } -} - -/** - * @param {Record} config - * @param {Record} resolved_config - * @param {EnforcedConfig} enforced_config - * @param {string} path - * @param {string[]} out used locally to compute the return value - */ -export function find_overridden_config(config, resolved_config, enforced_config, path, out) { - if (config == null || resolved_config == null) { - return out; - } - - for (const key in enforced_config) { - if (typeof config === 'object' && key in config && key in resolved_config) { - const enforced = enforced_config[key]; - const resolved = resolved_config[key]; - - if (enforced === true) { - if (comparable(config[key]) !== comparable(resolved)) { - out.push(path + key); - } - } else { - find_overridden_config(config[key], resolved, enforced, path + key + '.', out); - } - } - } - return out; -} +// taken from https://github.com/vitejs/vite/blob/main/packages/vite/src/shared/utils.ts#L31-L34 +const postfix_RE = /[?#].*$/; /** - * Normalizes a config value for comparison, since Windows paths may use backslashes - * and differ in casing (e.g. the drive letter) depending on where they came from. - * @param {any} value + * Remove any query parameters from the Rolldown ID + * @param {string} id + * @returns {string} */ -export function comparable(value) { - if (typeof value !== 'string') return value; - const normalized = posixify(value); - return process.platform === 'win32' ? normalized.toLowerCase() : normalized; +export function clean_id(id) { + return id.replace(postfix_RE, ''); } diff --git a/packages/kit/src/types/internal.d.ts b/packages/kit/src/types/internal.d.ts index 4f27becd1d20..a6658e891e32 100644 --- a/packages/kit/src/types/internal.d.ts +++ b/packages/kit/src/types/internal.d.ts @@ -433,7 +433,7 @@ export interface ServerMetadata { /** For each hashed remote file, a map of export name -> { type, dynamic }, where `dynamic` is `false` for non-dynamic prerender functions */ remotes: Map>; should_prerender: boolean; - has_dynamic_routes_or_remotes: boolean; + has_dynamic_server_routes_or_remotes: boolean; } export type SSRComponentLoader = () => Promise; From dd7e91759b0d90945923516367ae388819913f08 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Sun, 16 Aug 2026 22:49:54 +0800 Subject: [PATCH 51/59] fix check --- packages/kit/src/core/postbuild/analyse.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/src/core/postbuild/analyse.js b/packages/kit/src/core/postbuild/analyse.js index 865528ccd72b..129642525dd9 100644 --- a/packages/kit/src/core/postbuild/analyse.js +++ b/packages/kit/src/core/postbuild/analyse.js @@ -70,7 +70,7 @@ async function analyse({ hash, env, vite_config_file }) { // `set_env` lives in a separate module that imports the user's `src/env` config. We import it // *after* `set_building()` so that `building`-dependent expressions resolve correctly - const { set_env } = /** @type {import('__sveltekit/env')} */ ( + const { set_env } = /** @type {typeof import('__sveltekit/env')} */ ( await runner.import('__sveltekit/env') ); set_env(env); From fa2025920007f6645f94321f8b21455a860f2857 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Sun, 16 Aug 2026 23:00:46 +0800 Subject: [PATCH 52/59] fix --- packages/kit/src/exports/vite/index.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index e19674333ddf..697c85ddeb15 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -336,8 +336,11 @@ function kit({ svelte_config }) { // if the initial command was `build`, we want to reuse that whenever // the plugin loads again - process.env.SVELTEKIT_BUILD ??= s(config_env.command === 'build'); - is_build = process.env.SVELTEKIT_BUILD === 'true'; + // but if the user is running vitest, we don't bother with this + if (config_env.mode !== 'test') { + process.env.SVELTEKIT_BUILD ??= s(config_env.command === 'build'); + } + is_build = process.env.SVELTEKIT_BUILD === 'true' || config_env.command === 'build'; kit = process_config(svelte_config, root); out_dir = posixify(kit.outDir); From 9f8956e2a1df245379adbd853281e0dc8a558a64 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Sun, 16 Aug 2026 23:14:24 +0800 Subject: [PATCH 53/59] fix --- packages/kit/src/exports/vite/preview/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/kit/src/exports/vite/preview/index.js b/packages/kit/src/exports/vite/preview/index.js index 53012c87eb55..d4d059f4b58a 100644 --- a/packages/kit/src/exports/vite/preview/index.js +++ b/packages/kit/src/exports/vite/preview/index.js @@ -26,6 +26,7 @@ export async function preview(vite, svelte_config) { const etag = `"${Date.now()}"`; + // TODO: don't expect the server output to always be built const dir = join(svelte_config.outDir, 'output/server'); if (!fs.existsSync(`${dir}/manifest.js`)) { From 014d72cfe8e989df66f2600d95fdd1a2cfcbebaf Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Mon, 17 Aug 2026 18:05:35 +0800 Subject: [PATCH 54/59] fix preview always expecting ssr output --- .../kit/src/exports/vite/preview/index.js | 104 +++++++++++++----- 1 file changed, 74 insertions(+), 30 deletions(-) diff --git a/packages/kit/src/exports/vite/preview/index.js b/packages/kit/src/exports/vite/preview/index.js index d4d059f4b58a..9d971fa61f3f 100644 --- a/packages/kit/src/exports/vite/preview/index.js +++ b/packages/kit/src/exports/vite/preview/index.js @@ -1,8 +1,9 @@ +/** @import { Emulator, SSRManifest } from '@sveltejs/kit' */ /** @import { NextHandleFunction } from 'connect' */ /** @import { PreviewServer } from 'vite' */ /** @import { ValidatedConfig, ServerInternalModule, ServerModule } from 'types' */ import fs from 'node:fs'; -import { join } from 'node:path'; +import { join, relative } from 'node:path'; import { pathToFileURL } from 'node:url'; import { lookup } from '../../../utils/mime.js'; import sirv from 'sirv'; @@ -27,10 +28,51 @@ export async function preview(vite, svelte_config) { const etag = `"${Date.now()}"`; // TODO: don't expect the server output to always be built - const dir = join(svelte_config.outDir, 'output/server'); + const server_output_dir = join(svelte_config.outDir, 'output/server'); - if (!fs.existsSync(`${dir}/manifest.js`)) { - throw stackless(`Server files not found at ${dir}, did you run \`build\` first?`); + /** @type {{ server: InternalServer; manifest: SSRManifest; emulator: Emulator; } | null} */ + let ssr = null; + + if (fs.existsSync(server_output_dir)) { + if (!fs.existsSync(`${server_output_dir}/manifest.js`)) { + throw stackless( + `Server files not found at ${relative('.', server_output_dir)}, did you run \`build\` first?` + ); + } + + const instrumentation = join(server_output_dir, 'instrumentation.server.js'); + if (fs.existsSync(instrumentation)) { + await import(pathToFileURL(instrumentation).href); + } + + /** @type {ServerInternalModule} */ + const { set_assets } = await import(pathToFileURL(join(server_output_dir, 'internal.js')).href); + + /** @type {ServerModule} */ + const { Server } = await import(pathToFileURL(join(server_output_dir, 'index.js')).href); + + const { manifest } = await import(pathToFileURL(join(server_output_dir, 'manifest.js')).href); + + set_assets(assets); + + const server = new Server(manifest); + + try { + await server.init({ + env: loadEnv(vite_config.mode, svelte_config.env.dir, ''), + read: (file) => createReadableStream(`${server_output_dir}/${file}`) + }); + } catch (error) { + // Vite erases the error message when starting the preview server so we store + // it in the stack instead. This ensures errors thrown using `stackless` + // are still readable + if (error instanceof Error) error.stack = error.message; + throw error; + } + + const emulator = await svelte_config.adapter?.emulate?.(); + + ssr = { server, manifest, emulator }; } const instrumentation = join(dir, 'instrumentation.server.js'); @@ -203,34 +245,36 @@ export async function preview(vite, svelte_config) { ); // SSR - vite.middlewares.use(async (req, res) => { - const host = req.headers[':authority'] || req.headers.host; + if (ssr) { + vite.middlewares.use(async (req, res) => { + const host = req.headers[':authority'] || req.headers.host; + + const request = getRequest({ + base: `${protocol}://${host}`, + request: req, + response: res + }); - const request = (svelte_config.adapter?.getRequest ?? getRequest)({ - base: `${protocol}://${host}`, - request: req, - response: res + setResponse( + res, + await ssr.server.respond(request, { + getClientAddress: () => { + const { remoteAddress } = req.socket; + if (remoteAddress) return remoteAddress; + throw new Error('Could not determine clientAddress'); + }, + read: (file) => { + if (file in ssr.manifest._.server_assets) { + return fs.readFileSync(join(server_output_dir, file)); + } + + return fs.readFileSync(join(svelte_config.files.assets, file)); + }, + emulator: ssr.emulator + }) + ); }); - - (svelte_config.adapter?.setResponse ?? setResponse)( - res, - await server.respond(request, { - getClientAddress: () => { - const { remoteAddress } = req.socket; - if (remoteAddress) return remoteAddress; - throw new Error('Could not determine clientAddress'); - }, - read: (file) => { - if (file in manifest.server_assets) { - return fs.readFileSync(join(dir, file)); - } - - return fs.readFileSync(join(svelte_config.files.assets, file)); - }, - emulator - }) - ); - }); + } }; } From e180adc5e92ac2b9e6ce345f76b163a8cd772c37 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Mon, 17 Aug 2026 18:06:35 +0800 Subject: [PATCH 55/59] comment --- packages/kit/src/exports/vite/preview/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/src/exports/vite/preview/index.js b/packages/kit/src/exports/vite/preview/index.js index 9d971fa61f3f..b8905616cb8e 100644 --- a/packages/kit/src/exports/vite/preview/index.js +++ b/packages/kit/src/exports/vite/preview/index.js @@ -27,12 +27,12 @@ export async function preview(vite, svelte_config) { const etag = `"${Date.now()}"`; - // TODO: don't expect the server output to always be built const server_output_dir = join(svelte_config.outDir, 'output/server'); /** @type {{ server: InternalServer; manifest: SSRManifest; emulator: Emulator; } | null} */ let ssr = null; + // only set up the server if it was built if (fs.existsSync(server_output_dir)) { if (!fs.existsSync(`${server_output_dir}/manifest.js`)) { throw stackless( From c06e199af19beb57e206ff366a884a52a112a6b2 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Mon, 17 Aug 2026 18:23:41 +0800 Subject: [PATCH 56/59] skip ssr if output is completely prerendered --- packages/kit/src/core/postbuild/analyse.js | 14 ++++++-------- packages/kit/src/types/internal.d.ts | 2 +- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/kit/src/core/postbuild/analyse.js b/packages/kit/src/core/postbuild/analyse.js index 129642525dd9..c70cac7cc0b1 100644 --- a/packages/kit/src/core/postbuild/analyse.js +++ b/packages/kit/src/core/postbuild/analyse.js @@ -33,7 +33,7 @@ async function analyse({ hash, env, vite_config_file }) { routes: new Map(), remotes: new Map(), should_prerender: hash, - has_dynamic_server_routes_or_remotes: false + has_dynamic_endpoints: false }; const vite_dev_server = await vite.createServer(vite_config); @@ -100,7 +100,7 @@ async function analyse({ hash, env, vite_config_file }) { } if (node.server?.actions) { - metadata.has_dynamic_server_routes_or_remotes = true; + metadata.has_dynamic_endpoints = true; } metadata.nodes[node.index] = { @@ -145,11 +145,9 @@ async function analyse({ hash, env, vite_config_file }) { const api_methods = endpoint?.methods ?? []; const entries = page?.entries ?? endpoint?.entries; - if ( - (page?.methods.includes('GET') && page.ssr && page.prerender !== true) || // non-prerendered SSR-ed page - (api_methods.length && endpoint?.prerender !== true) // non-prerendered endpoint - ) { - metadata.has_dynamic_server_routes_or_remotes = true; + if (api_methods.length && endpoint?.prerender !== true) // non-prerendered endpoint + { + metadata.has_dynamic_endpoints = true; } metadata.routes.set(route.id, { @@ -188,7 +186,7 @@ async function analyse({ hash, env, vite_config_file }) { } if (dynamic) { - metadata.has_dynamic_server_routes_or_remotes = true; + metadata.has_dynamic_endpoints = true; } } diff --git a/packages/kit/src/types/internal.d.ts b/packages/kit/src/types/internal.d.ts index a6658e891e32..600aad1be973 100644 --- a/packages/kit/src/types/internal.d.ts +++ b/packages/kit/src/types/internal.d.ts @@ -433,7 +433,7 @@ export interface ServerMetadata { /** For each hashed remote file, a map of export name -> { type, dynamic }, where `dynamic` is `false` for non-dynamic prerender functions */ remotes: Map>; should_prerender: boolean; - has_dynamic_server_routes_or_remotes: boolean; + has_dynamic_endpoints: boolean; } export type SSRComponentLoader = () => Promise; From 70a21645f502dbadc4ceb52ff87166e3691e56c1 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Mon, 17 Aug 2026 18:23:44 +0800 Subject: [PATCH 57/59] changeset --- .changeset/shy-apes-prove.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/shy-apes-prove.md diff --git a/.changeset/shy-apes-prove.md b/.changeset/shy-apes-prove.md new file mode 100644 index 000000000000..9567e16cc082 --- /dev/null +++ b/.changeset/shy-apes-prove.md @@ -0,0 +1,6 @@ +--- +"@sveltejs/kit": minor +--- + +feat: skip SSR build if a server is not required + \ No newline at end of file From 03f77827f9e978c2e15ea8b233f47114a9fa4491 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Mon, 17 Aug 2026 18:39:07 +0800 Subject: [PATCH 58/59] fix hash router trying to load client code on server during dev --- packages/kit/src/core/postbuild/prerender.js | 38 +++++++++++--------- packages/kit/src/exports/vite/dev/index.js | 12 +++++-- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/packages/kit/src/core/postbuild/prerender.js b/packages/kit/src/core/postbuild/prerender.js index 18c18b2ae3a1..b5093dd77f18 100644 --- a/packages/kit/src/core/postbuild/prerender.js +++ b/packages/kit/src/core/postbuild/prerender.js @@ -43,6 +43,7 @@ const SPECIAL_HASHLINKS = new Set(['', 'top']); * is_tty: boolean | undefined; * origin: string; * assets: string; + * base: string; * prerendered: Prerendered; * prerender_map: PrerenderMap; * client: string | null; @@ -61,7 +62,8 @@ async function prerender({ assets, prerendered, prerender_map, - client + client, + base }) { const throw_handled = () => { throw new Error('__handled__'); @@ -139,7 +141,7 @@ async function prerender({ client }); - const file = output_filename('/', true); + const file = output_filename(base, '/', true); const dest = `${out}/prerendered/pages/${file}`; mkdirSync(dirname(dest), { recursive: true }); @@ -221,20 +223,6 @@ async function prerender({ const q = queue(config.prerender.concurrency); - /** - * @param {string} path - * @param {boolean} is_html - */ - function output_filename(path, is_html) { - const file = path.slice(config.paths.base.length + 1) || 'index.html'; - - if (is_html && !file.endsWith('.html')) { - return file + (file.endsWith('/') ? 'index.html' : '.html'); - } - - return file; - } - const files = new Set(walk(`${out}/client`)); files.add(`${config.appDir}/env.js`); @@ -520,7 +508,7 @@ async function prerender({ ); } - const file = output_filename(decoded, is_html); + const file = output_filename(base, decoded, is_html); const dest = `${config.outDir}/output/prerendered/${category}/${file}`; if (written.has(file)) return; @@ -815,3 +803,19 @@ async function get_ssr_vite_server({ vite, vite_config_file, env, client }) { throw error; } } + +/** + * @param {string} base + * @param {string} path + * @param {boolean} is_html + * @returns {string} + */ +function output_filename(base, path, is_html) { + const file = path.slice(base.length + 1) || 'index.html'; + + if (is_html && !file.endsWith('.html')) { + return file + (file.endsWith('/') ? 'index.html' : '.html'); + } + + return file; +} diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index d7ab747a7357..1366c4e0be6f 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -280,8 +280,16 @@ export async function dev( await find_deps(vite_dev_server, module_node, deps); } - /** @type {Record} */ - const styles = {}; + if (node.universal) { + if (node.page_options?.ssr === false || svelte_config.router.type === 'hash') { + result.universal = /** @type {UniversalNode} */ (node.page_options); + } else { + // TODO: explain why the file was loaded on the server if we fail to load it + const { module, module_node } = await resolve(node.universal); + module_nodes.push(module_node); + result.universal = module; + } + } for (const dep of deps) { if (vite.isCSSRequest(dep.url) && !vite_css_query_regex.test(dep.url)) { From 1d9c263df3de09c6b402659fd0f665e7874b91fc Mon Sep 17 00:00:00 2001 From: Ottomated Date: Mon, 17 Aug 2026 18:13:35 -0400 Subject: [PATCH 59/59] remove internal references from wrangler config --- packages/adapter-cloudflare/index.js | 13 ++++++------- .../test/apps/workers/config/wrangler.jsonc | 5 ----- packages/adapter-cloudflare/utils.js | 18 ++++-------------- 3 files changed, 10 insertions(+), 26 deletions(-) diff --git a/packages/adapter-cloudflare/index.js b/packages/adapter-cloudflare/index.js index 73312ae0a90e..34e4e9712d83 100644 --- a/packages/adapter-cloudflare/index.js +++ b/packages/adapter-cloudflare/index.js @@ -273,13 +273,12 @@ function cloudflare_vite_plugins(config_path) { ) { user_config.compatibility_flags.push('nodejs_als'); } - const worker_contents = fs - .readFileSync(`${files}/worker.js`, 'utf8') - .replace('ASSETS', user_config.assets?.binding ?? 'ASSETS'); - const worker_out = `${out_dir}/cloudflare-tmp/worker.js`; - fs.mkdirSync(path.dirname(worker_out), { recursive: true }); - fs.writeFileSync(worker_out, worker_contents); - user_config.main = worker_out; + if (!user_config.main) { + user_config.main = fileURLToPath(import.meta.resolve('./files/worker.js')); + } + user_config.assets ??= {}; + user_config.assets.binding ??= 'ASSETS'; + user_config.assets.directory = `${out_dir}/output/client`; } // for now, disable all cloudflare plugins during dev & preview }).map((p) => { diff --git a/packages/adapter-cloudflare/test/apps/workers/config/wrangler.jsonc b/packages/adapter-cloudflare/test/apps/workers/config/wrangler.jsonc index 372a381e9b61..f3b4cf2a0e47 100644 --- a/packages/adapter-cloudflare/test/apps/workers/config/wrangler.jsonc +++ b/packages/adapter-cloudflare/test/apps/workers/config/wrangler.jsonc @@ -2,12 +2,7 @@ // to test that the adapter still resolves the paths correctly { "$schema": "../node_modules/wrangler/config-schema.json", - "main": "../dist/index.js", "compatibility_date": "2026-06-24", - "assets": { - "directory": "../dist/public", - "binding": "ASSETS" - }, "vars": { "FOO": "from wrangler.jsonc" } diff --git a/packages/adapter-cloudflare/utils.js b/packages/adapter-cloudflare/utils.js index 82f5683c6e07..3042b4d5ece1 100644 --- a/packages/adapter-cloudflare/utils.js +++ b/packages/adapter-cloudflare/utils.js @@ -29,25 +29,15 @@ export function validate_worker_settings(wrangler_config) { ); } - // we need the `assets.directory` key so that the static assets are deployed - if ((wrangler_config.main || wrangler_config.assets) && !wrangler_config.assets?.directory) { + if (wrangler_config.assets?.directory) { throw new Error( - `You must specify the \`assets.directory\` key in ${config_path}. Consult https://developers.cloudflare.com/workers/static-assets/binding/#directory` + `You must remove the \`assets.directory\` key in ${config_path}. You can change the output directory by setting \`outDir\` in your SvelteKit config.` ); } - // we need the `assets.binding` key so that the Worker can access the static assets - if (wrangler_config.main && !wrangler_config.assets?.binding) { + if (wrangler_config.main?.endsWith('.svelte-kit/cloudflare/_worker.js')) { throw new Error( - `You must specify the \`assets.binding\` key in ${config_path} before deploying your Worker. Consult https://developers.cloudflare.com/workers/static-assets/binding/#binding` - ); - } - - // the user might have forgot the `main` key or should remove the `assets.binding` - // key to deploy static assets without a Worker - if (!wrangler_config.main && wrangler_config.assets?.binding) { - throw new Error( - `You must specify the \`main\` key in ${config_path} if you want to deploy a Worker alongside your static assets. Otherwise, remove the \`assets.binding\` key if you only want to deploy static assets.` + `The \`main\` key in ${config_path} is now used to specify a custom Worker entrypoint. You must remove it unless you are using a custom Worker.` ); } }