From cf8166d70716d48497990bfbf1d0f242f18fedf4 Mon Sep 17 00:00:00 2001 From: Nic Polumeyv Date: Mon, 24 Aug 2026 05:57:55 -0400 Subject: [PATCH 01/22] chore: fix flaky timeout in the options vite spec (#16910) The options app's `unit-test/vite.spec.js` spawns a nested `pnpm vitest run` with a 60s child timeout, but the test itself runs under vitest's default 5s `testTimeout`, so on a slow runner the outer test dies while the child is still booting ([example failure](https://github.com/sveltejs/kit/actions/runs/32603897316/job/97106102130)). Pass the existing `timeout` to the test, the same shape the build-errors specs use. --- packages/kit/test/apps/options/unit-test/vite.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/test/apps/options/unit-test/vite.spec.js b/packages/kit/test/apps/options/unit-test/vite.spec.js index 5540dd897461..c2ac3c3efcdf 100644 --- a/packages/kit/test/apps/options/unit-test/vite.spec.js +++ b/packages/kit/test/apps/options/unit-test/vite.spec.js @@ -6,7 +6,7 @@ const timeout = 60_000; const cwd = path.resolve(import.meta.dirname, '..'); -test('no overridden options warning', () => { +test('no overridden options warning', { timeout }, () => { const result = spawnSync( 'pnpm', ['vitest', 'run', '--config', './vite.custom.config.js', '-t', 'noop'], From 36c72567d16026fb32aafac85e682699431bfe1d Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Tue, 25 Aug 2026 00:57:17 +0800 Subject: [PATCH 02/22] chore: resolve merge conflicts with `main` (#16925) Ok nice it seems there are some natural changes to merge in so we don't need to add a dummy change --------- Co-authored-by: github-actions[bot] Co-authored-by: Nic Polumeyv Co-authored-by: Elliott Johnson Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Elliott Johnson Co-authored-by: Conduitry --- documentation/docs/20-core-concepts/30-form-actions.md | 2 ++ packages/kit/CHANGELOG.md | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/documentation/docs/20-core-concepts/30-form-actions.md b/documentation/docs/20-core-concepts/30-form-actions.md index 1a3d2ad03e40..5d076a15c101 100644 --- a/documentation/docs/20-core-concepts/30-form-actions.md +++ b/documentation/docs/20-core-concepts/30-form-actions.md @@ -6,6 +6,8 @@ A `+page.server.js` file can export _actions_, which allow you to `POST` data to When using `
`, client-side JavaScript is optional, but you can easily _progressively enhance_ your form interactions with JavaScript to provide the best user experience. +> [!NOTE] The experimental [`form` remote function](remote-functions#form) covers the same use cases as form actions, adding type safety and [single-flight mutations](remote-functions#Single-flight-mutations). Form actions are feature-complete and will continue to work, but new development is focused on remote functions, which are intended to become the recommended way to communicate with the server. Consider remote functions for new projects, keeping in mind that the API may change while the feature is experimental. + ## Default actions In the simplest case, a page declares a `default` action: diff --git a/packages/kit/CHANGELOG.md b/packages/kit/CHANGELOG.md index 41691e4f7560..6d41f032ad34 100644 --- a/packages/kit/CHANGELOG.md +++ b/packages/kit/CHANGELOG.md @@ -830,11 +830,17 @@ - feat: add `ErrorProps` to generated types ([#16272](https://github.com/sveltejs/kit/pull/16272)) +## 2.70.3 + +### Patch Changes + +- fix: avoid eagerly reading `$app/state` dependencies during module initialization ([`b61018d`](https://github.com/sveltejs/kit/commit/b61018d0526e827d1ef8cf8e21ef62b92230ae3d)) + ## 2.70.2 ### Patch Changes -- fix: prevent quadratic backtracking in `Accept` header content negotiation ([#1](https://github.com/homebase-garage/igeclouds.github.io/pull/1)) +- fix: prevent quadratic backtracking in `Accept` header content negotiation ([`82712fc`](https://github.com/sveltejs/kit/commit/82712fc02c24b1dcf5b25d7a52129cd8455f04f5)) ## 2.70.1 From 304f0c2621cbd32562dc08e08eacd889b4d98cd7 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Thu, 20 Aug 2026 16:30:00 -0400 Subject: [PATCH 03/22] 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 2db9a56220a8777457e319a8a38cec47e08549a2 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Thu, 20 Aug 2026 16:39:20 -0400 Subject: [PATCH 04/22] 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 b2230f73e42e947b3512a6880be99119aa0d9179 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Thu, 20 Aug 2026 16:50:17 -0400 Subject: [PATCH 05/22] 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 f79534e3dcad..313ee8553099 100644 --- a/packages/adapter-node/src/handler.js +++ b/packages/adapter-node/src/handler.js @@ -4,15 +4,15 @@ 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'; -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(); @@ -28,7 +28,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, @@ -52,16 +52,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 d38e265d3812a4cbbbbfcf2d7df0276230d27bfb Mon Sep 17 00:00:00 2001 From: Ottomated Date: Thu, 20 Aug 2026 16:51:55 -0400 Subject: [PATCH 06/22] 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 1fd1949bbe0e22e987f0d371dccba65d307d8ee5 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Thu, 20 Aug 2026 17:24:49 -0400 Subject: [PATCH 07/22] 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 a9074a98c856..70aff33cffe9 100644 --- a/packages/kit/src/exports/vite/build/index.js +++ b/packages/kit/src/exports/vite/build/index.js @@ -836,6 +836,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) { @@ -845,12 +853,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 @@ -879,6 +885,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 4d5f2ee95351..3d15d6015ead 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 e0eaf4cae7bac7150f7df3991fcc74631772c27d Mon Sep 17 00:00:00 2001 From: Ottomated Date: Thu, 20 Aug 2026 17:42:31 -0400 Subject: [PATCH 08/22] 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 099fc9fa5ed1c0094181eb8fdc906f67a4bbaf53 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Thu, 20 Aug 2026 17:45:35 -0400 Subject: [PATCH 09/22] 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 1e0ef278feec5f4522be31f12aaa7afb5490657d Mon Sep 17 00:00:00 2001 From: Ottomated Date: Thu, 20 Aug 2026 20:20:15 -0400 Subject: [PATCH 10/22] 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 3d15d6015ead..92b8e18029fd 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 2aaecb35403fdfe69cd75055b6b0b6d474fba16c Mon Sep 17 00:00:00 2001 From: Ottomated Date: Fri, 21 Aug 2026 22:21:30 -0400 Subject: [PATCH 11/22] 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 4f6ccf666cf7..6b71d3531037 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` + @@ -179,7 +183,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 d20d2fbb0ed1..19fd3d4d59e2 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'; @@ -184,7 +184,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 2f08aad4d8ee73e72b729140af887565bc6f485b Mon Sep 17 00:00:00 2001 From: Ottomated Date: Thu, 20 Aug 2026 17:56:19 -0400 Subject: [PATCH 12/22] 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 481631c5b7d8..bf1042382811 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 92b8e18029fd..da574826b50f 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 ea622bfb258d9e4d3ce3fcf5b14b5449ac50c571 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Thu, 20 Aug 2026 18:07:50 -0400 Subject: [PATCH 13/22] 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 bf1042382811..ca1b10c82098 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 da574826b50f..73f056eb9191 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 2974f523f7ceb7dfd7ed89981bd770d8b4a15c0a Mon Sep 17 00:00:00 2001 From: Ottomated Date: Sun, 16 Aug 2026 22:30:28 -0400 Subject: [PATCH 14/22] 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 73f056eb9191..3c53fc71540c 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -4291,4 +4291,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 6a73e123227be577b31d9dc610c84f5c2e7ffc9c Mon Sep 17 00:00:00 2001 From: Ottomated Date: Tue, 11 Aug 2026 20:22:25 -0700 Subject: [PATCH 15/22] 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 c015202d5b722aa810fe924338de220dbed1af1a Mon Sep 17 00:00:00 2001 From: Ottomated Date: Tue, 11 Aug 2026 20:22:47 -0700 Subject: [PATCH 16/22] 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 a2e5b9c1d2bd2bea87df95cc32d212b5cd01f88d Mon Sep 17 00:00:00 2001 From: Ottomated Date: Tue, 11 Aug 2026 20:25:27 -0700 Subject: [PATCH 17/22] 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 3c53fc71540c..2390bf7da994 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 dd68bc4130a3ce57f5e225f78afeaad7e88087b9 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Tue, 11 Aug 2026 20:39:26 -0700 Subject: [PATCH 18/22] 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 8142106eb6c9cb5eee3a1fe1dfbc3e51aa3262c6 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Tue, 11 Aug 2026 21:59:17 -0700 Subject: [PATCH 19/22] 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 6642acc1c4c34bbcdb8709452fea4993f6998b0b Mon Sep 17 00:00:00 2001 From: Ottomated Date: Thu, 13 Aug 2026 14:36:42 -0700 Subject: [PATCH 20/22] 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 2390bf7da994..d9795d31d3ec 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'; @@ -4304,4 +4304,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 12323432798d5a3dba4856b3c1d24c42d52a9dff Mon Sep 17 00:00:00 2001 From: Ottomated Date: Mon, 24 Aug 2026 10:51:27 -0700 Subject: [PATCH 21/22] fix rebase stuff --- .changeset/smooth-flies-begin.md | 10 ---------- packages/kit/src/exports/public.d.ts | 6 ++++-- 2 files changed, 4 insertions(+), 12 deletions(-) delete mode 100644 .changeset/smooth-flies-begin.md diff --git a/.changeset/smooth-flies-begin.md b/.changeset/smooth-flies-begin.md deleted file mode 100644 index da2f9b58f3e1..000000000000 --- a/.changeset/smooth-flies-begin.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -"@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/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index 18c14bae05e0..076bc4568e0b 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -53,13 +53,15 @@ export interface Adapter { instrumentation?: () => boolean; }; /** - * This function overrides the default behavior to convert an `http.IncomingMessage` to a `Request` object. + * This function overrides the default behavior during dev and preview 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`. + * This function overrides the default behavior during dev and preview 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 ee50bdc9184e8bb38bbcd20ef88916ab98fce043 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Mon, 24 Aug 2026 10:53:07 -0700 Subject: [PATCH 22/22] types --- packages/kit/types/index.d.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index d9795d31d3ec..087ba98dd990 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -42,13 +42,15 @@ declare module '@sveltejs/kit' { instrumentation?: () => boolean; }; /** - * This function overrides the default behavior to convert an `http.IncomingMessage` to a `Request` object. + * This function overrides the default behavior during dev and preview 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`. + * This function overrides the default behavior during dev and preview 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 */