From 277389a25ae6b509f25b75a7adbc75ff6b63d269 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Wed, 22 Jul 2026 03:47:48 +0800 Subject: [PATCH 001/109] get it working --- .../99-writing-adapters.md | 41 ++ .../kit/src/core/generate_manifest/index.js | 1 + packages/kit/src/exports/public.d.ts | 1 + packages/kit/src/exports/vite/dev/context.js | 38 ++ packages/kit/src/exports/vite/dev/index.js | 479 ++---------------- packages/kit/src/exports/vite/dev/server.js | 52 ++ .../kit/src/exports/vite/dev/sourcemaps.js | 43 ++ .../kit/src/exports/vite/dev/ssr_entry.js | 39 ++ .../kit/src/exports/vite/dev/ssr_manifest.js | 232 +++++++++ packages/kit/src/exports/vite/dev/utils.js | 49 ++ packages/kit/src/exports/vite/index.js | 176 ++++++- packages/kit/src/exports/vite/module_ids.js | 1 + packages/kit/src/types/ambient-private.d.ts | 9 + packages/kit/src/types/ambient.d.ts | 53 ++ packages/kit/src/types/global-private.d.ts | 9 + packages/kit/src/types/internal.d.ts | 1 + packages/kit/src/utils/params.js | 5 +- packages/kit/types/index.d.ts | 54 ++ 18 files changed, 818 insertions(+), 465 deletions(-) create mode 100644 packages/kit/src/exports/vite/dev/context.js create mode 100644 packages/kit/src/exports/vite/dev/server.js create mode 100644 packages/kit/src/exports/vite/dev/sourcemaps.js create mode 100644 packages/kit/src/exports/vite/dev/ssr_entry.js create mode 100644 packages/kit/src/exports/vite/dev/ssr_manifest.js create mode 100644 packages/kit/src/exports/vite/dev/utils.js 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 204445e5e889..33c85d67a991 100644 --- a/documentation/docs/25-build-and-deploy/99-writing-adapters.md +++ b/documentation/docs/25-build-and-deploy/99-writing-adapters.md @@ -66,3 +66,44 @@ Within the `adapt` method, there are a number of things that an adapter should d - Put the user's static files and the generated JS/CSS in the correct location for the target platform Where possible, we recommend putting the adapter output under the `build/` directory with any intermediate output placed under `.svelte-kit/[adapter-name]`. + +## Configuring the development and preview experience + +By default, SvelteKit runs your server code through a Node.js runtime when running `vite dev` and `vite preview`. You can change this behaviour by adding a Vite plugin that has a `configureServer` and `configurePreviewServer` hook to route requests to [a different runtime](https://vite.dev/guide/api-environment-runtimes). + +The main Vite server environment SvelteKit uses is named `ssr`. You can change its settings by referencing it in the `config` hook of a Vite plugin. + +```js +// @errors: 2304 1005 1109 +config(userConfig) { + userConfig.environments.ssr = { ... } +} +``` + +You can also create your own server entry file by importing the `Server` class from `sveltekit:server`, the environment variables loaded by Vite through `env` from `sveltekit:env`, and your app-specific information as `manifest` from `sveltekit:server-manifest`. + +```js +import { env } from 'sveltekit:env'; +import { Server } from 'sveltekit:server'; +import { manifest } from 'sveltekit:server-manifest'; + +const server = new Server(manifest); + +await server.init({ env }); + +export default { + /** + * @param {Request} request + * @returns {Promise} + */ + async fetch(request) { + return await server.respond(request, { + getClientAddress: () => { + return request.headers.get('how-your-platform-exposes-the-remote-address') + } + }); + } +} + +import.meta.hot?.accept(); +``` diff --git a/packages/kit/src/core/generate_manifest/index.js b/packages/kit/src/core/generate_manifest/index.js index b83aa8fef7e4..ad9233e65527 100644 --- a/packages/kit/src/core/generate_manifest/index.js +++ b/packages/kit/src/core/generate_manifest/index.js @@ -102,6 +102,7 @@ export function generate_manifest({ appDir: ${s(build_data.app_dir)}, appPath: ${s(build_data.app_path)}, assets: new Set(${s(assets)}), + base: ${s(build_data.base)}, mimeTypes: ${s(mime_types)}, _: { client: ${uneval(build_data.client)}, diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index 8b493da6337a..6db957fa21d6 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -1775,6 +1775,7 @@ export interface SSRManifest { appPath: string; /** Static files from `config.files.assets` and the service worker (if any). */ assets: Set; + base: string; mimeTypes: Record; /** @internal private fields */ diff --git a/packages/kit/src/exports/vite/dev/context.js b/packages/kit/src/exports/vite/dev/context.js new file mode 100644 index 000000000000..6eb0b6635ab3 --- /dev/null +++ b/packages/kit/src/exports/vite/dev/context.js @@ -0,0 +1,38 @@ +/** @import { ValidatedConfig } from 'types' */ +/** @import { ViteDevServer } from 'vite' */ + +/** @returns {Array<{ hash: string, file: string }>} */ +export function get_remotes() { + return import.meta.hot?.data.remotes; +} + +/** @param {Array<{ hash: string, file: string }>} r */ +export function set_remotes(r) { + if (import.meta.hot) { + import.meta.hot.data.remotes = r; + } +} + +/** @returns {ValidatedConfig} */ +export function get_svelte_config() { + return import.meta.hot?.data.svelte_config; +} + +/** @param {ValidatedConfig} config */ +export function set_svelte_config(config) { + if (import.meta.hot) { + import.meta.hot.data.svelte_config = config; + } +} + +/** @returns {ViteDevServer} */ +export function get_dev_server() { + return import.meta.hot?.data.dev_server; +} + +/** @param {ViteDevServer} server */ +export function set_dev_server(server) { + if (import.meta.hot) { + import.meta.hot.data.dev_server = server; + } +} diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 7e3b161c7505..fc147a5cea78 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -1,149 +1,59 @@ -/** @import { RequestEvent } from '@sveltejs/kit' */ -/** @import { PrerenderOption, UniversalNode } from 'types' */ -import process from 'node:process'; +/** @import { ViteDevServer } from 'vite' */ import fs from 'node:fs'; import path from 'node:path'; import { URL } from 'node:url'; -import { AsyncLocalStorage } from 'node:async_hooks'; import { styleText } from 'node:util'; import sirv from 'sirv'; -import { isCSSRequest, loadEnv, buildErrorMessage } from 'vite'; -import { createReadableStream, getRequest, setResponse } from '../../../exports/node/index.js'; +import { isRunnableDevEnvironment } from 'vite'; +import { getRequest, setResponse } from '../../../exports/node/index.js'; import { coalesce_to_error } from '../../../utils/error.js'; import { resolve_entry } from '../../../utils/filesystem.js'; -import { load_and_validate_params } from '../../../utils/params.js'; -import { from_fs, to_fs } from '../../../utils/vite.js'; +import { to_fs } from '../../../utils/vite.js'; import { posixify } from '../../../utils/os.js'; import { load_error_page } from '../../../core/config/index.js'; -import { SRC_ROOT, SVELTE_KIT_ASSETS } from '../../../constants.js'; +import { SVELTE_KIT_ASSETS } from '../../../constants.js'; import * as sync from '../../../core/sync/sync.js'; -import { get_mime_lookup, get_runtime_base } from '../../../core/utils.js'; import '../../../utils/mime.js'; // extend mrmime with additional types (affects sirv too) -import { compact } from '../../../utils/array.js'; import { is_chrome_devtools_request, log_response, not_found, remote_module_pattern } from '../utils.js'; -import { SCHEME } from '../../../utils/url.js'; -import { check_feature } from '../../../utils/features.js'; import { escape_html } from '../../../utils/escape.js'; - -// vite-specifc queries that we should skip handling for css urls -const vite_css_query_regex = /(?:\?|&)(?:raw|url|inline)(?:&|$)/; +import { fix_stack_trace } from './sourcemaps.js'; /** * @param {import('vite').ViteDevServer} vite * @param {import('vite').ResolvedConfig} vite_config * @param {import('types').ValidatedConfig} svelte_config - * @param {() => Array<{ hash: string, file: string }>} get_remotes * @param {string} root The project root directory * @param {(manifest_data: import('types').ManifestData) => void} set_manifest_data - * @return {Promise void>>} + * @return {() => void} */ -export async function dev(vite, vite_config, svelte_config, get_remotes, root, set_manifest_data) { - /** @type {AsyncLocalStorage<{ event: RequestEvent, config: any, prerender: PrerenderOption }>} */ - const async_local_storage = new AsyncLocalStorage(); - - globalThis.__SVELTEKIT_TRACK__ = (label) => { - const context = async_local_storage.getStore(); - if (!context || context.prerender === true) return; - - check_feature( - /** @type {string} */ (context.event.route.id), - context.config, - label, - svelte_config.kit.adapter - ); - }; - - const fetch = globalThis.fetch; - globalThis.fetch = (info, init) => { - if (typeof info === 'string' && !SCHEME.test(info)) { - throw new Error( - `Cannot use relative URL (${info}) with global fetch — use \`event.fetch\` instead: https://svelte.dev/docs/kit/web-standards#fetch-apis` - ); - } - - return fetch(info, init); - }; - +export function dev(vite, vite_config, svelte_config, root, set_manifest_data) { sync.init(svelte_config, root); /** @type {import('types').ManifestData} */ let manifest_data; - /** @type {import('@sveltejs/kit').SSRManifest} */ - let manifest; /** @type {Error | null} */ let manifest_error = null; - /** @param {string} url */ - async function loud_ssr_load_module(url) { - try { - return await vite.ssrLoadModule(url, { fixStacktrace: true }); - } catch (/** @type {any} */ err) { - const msg = buildErrorMessage(err, [ - styleText('red', `Internal server error: ${err.message}`) - ]); - - if (!vite.config.logger.hasErrorLogged(err)) { - vite.config.logger.error(msg, { error: err }); - } - - // TODO this is inadequate — it doesn't reliably show the overlay on every page load, - // and when it does appear it may immediately vanish. `vite.ws.send` broadcasts - // to all connected clients, even ones that are unaffected by the error. - // we need a more considered approach - vite.ws.send({ - type: 'error', - err: /** @type {import('vite').ErrorPayload['err']} */ ({ - ...err, - // these properties are non-enumerable and will - // not be serialized unless we explicitly include them - message: err.message, - stack: err.stack ?? '' - }) - }); - - throw err; - } - } - - /** @param {string} id */ - async function resolve(id) { - const url = id.startsWith('..') ? to_fs(path.resolve(id)) : `/${id}`; - - const module = await loud_ssr_load_module(url); - - const module_node = await vite.moduleGraph.getModuleByUrl(url); - if (!module_node) throw new Error(`Could not find node for ${url}`); - - return { module, module_node, url }; - } - - async function update_manifest() { + function update_manifest() { try { ({ manifest_data } = sync.create(svelte_config, root)); set_manifest_data(manifest_data); - await load_and_validate_params({ - routes: manifest_data.routes, - params_path: manifest_data.params, - root, - load: (file) => loud_ssr_load_module(file) - }); - if (manifest_error) { manifest_error = null; - vite.ws.send({ type: 'full-reload' }); + vite.hot.send({ type: 'full-reload' }); } } catch (error) { manifest_error = /** @type {Error} */ (error); console.error(styleText(['bold', 'red'], manifest_error.message)); - vite.ws.send({ + vite.hot.send({ type: 'error', err: { message: manifest_error.message ?? 'Invalid routes', @@ -153,230 +63,9 @@ export async function dev(vite, vite_config, svelte_config, get_remotes, root, s return; } - - manifest = { - appDir: svelte_config.kit.appDir, - appPath: svelte_config.kit.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.kit.outDir)}/generated/client/app.js`, - imports: [], - stylesheets: [], - fonts: [], - uses_env_dynamic_public: true, - nodes: - svelte_config.kit.router.resolution === 'client' - ? undefined - : manifest_data.nodes.map((node, i) => { - if (node.component || node.universal) { - return `${svelte_config.kit.paths.base}${to_fs(svelte_config.kit.outDir)}/generated/client/nodes/${i}.js`; - } - }), - // `css` is not necessary in dev, as the JS file from `nodes` will reference the CSS file - routes: - svelte_config.kit.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 () => { - /** @type {import('types').SSRNode} */ - const result = {}; - 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 {import('vite').ModuleNode[]} */ - 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.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(); - - for (const module_node of module_nodes) { - await find_deps(vite, module_node, deps); - } - - /** @type {Record} */ - const styles = {}; - - for (const dep of deps) { - if (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 vite.ssrLoadModule(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; - }; - }), - prerendered_routes: new Set(), - get remotes() { - return Object.fromEntries( - get_remotes().map((remote) => [ - remote.hash, - () => vite.ssrLoadModule(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 vite.ssrLoadModule(url, { fixStacktrace: true }); - - if (!module.params) { - throw new Error( - `${manifest_data.params} does not export \`params\` from \`defineParams\`` - ); - } - - return module.params; - } - } - }; - } - - /** @param {Error} error */ - function fix_stack_trace(error) { - if (!error.stack) { - return; - } - - try { - vite.ssrFixStacktrace(error); - } catch { - // ssrFixStacktrace can fail on StackBlitz web containers and we don't know why - // by ignoring it the line numbers are wrong, but at least we can show the error - } - - if (error.stack) { - let end = 0; - - error.stack = error.stack - .replaceAll('\0', '') // remove null bytes from e.g. virtual module IDs, or the response will fail - .split('\n') - .map((line, i) => { - const match = /^ {4}at (?:[^ ]+ \((.+)\)|(.+))$/.exec(line); - if (!match) { - end = i + 1; - return line; - } - - const loc = match[1] ?? match[2]; - const file = loc.replace(/:\d+:\d+$/, ''); - - if (fs.existsSync(file)) { - if (!file.includes('node_modules') && !file.includes(SRC_ROOT)) { - end = i + 1; - } - - return line.replace(file, path.relative(process.cwd(), file)); - } - - return line; - }) - .slice(0, end) - .join('\n'); - - return error.stack; - } } - await update_manifest(); + update_manifest(); const params_file = resolve_entry(svelte_config.kit.files.params); @@ -420,6 +109,7 @@ export async function dev(vite, vite_config, svelte_config, get_remotes, root, s // Unless it's a file where the trailing slash page option might have changed if (timeout || !/\+(page|layout|server).*$/.test(file)) return; sync.update(svelte_config, manifest_data, file, root); + // invalidate_module(vite, sveltekit_manifest_data); }); const { appTemplate, errorTemplate, serviceWorker, hooks } = svelte_config.kit.files; @@ -477,9 +167,6 @@ export async function dev(vite, vite_config, svelte_config, get_remotes, root, s next(); }); - const env = loadEnv(vite_config.mode, svelte_config.kit.env.dir, ''); - const emulator = await svelte_config.kit.adapter?.emulate?.(); - return () => { const serve_static_middleware = vite.middlewares.stack.find( (middleware) => @@ -539,41 +226,6 @@ export async function dev(vite, vite_config, svelte_config, get_remotes, root, s return; } - const resolved_instrumentation = resolve_entry( - path.join(svelte_config.kit.files.src, 'instrumentation.server') - ); - - if (resolved_instrumentation) { - await vite.ssrLoadModule(resolved_instrumentation); - } - - // we have to import `Server` before calling `set_assets` - const { Server } = /** @type {import('types').ServerModule} */ ( - await vite.ssrLoadModule(`${get_runtime_base(root)}/server/index.js`, { - fixStacktrace: true - }) - ); - - const { set_fix_stack_trace } = await vite.ssrLoadModule( - `${get_runtime_base(root)}/server/internal.js` - ); - set_fix_stack_trace(fix_stack_trace); - - const { set_assets } = await vite.ssrLoadModule('$app/paths/internal/server'); - set_assets(assets); - - const server = new Server(manifest); - - await server.init({ - env, - read: (file) => createReadableStream(from_fs(file)) - }); - - const request = getRequest({ - base, - request: req - }); - if (manifest_error) { console.error(styleText(['bold', 'red'], manifest_error.message)); @@ -596,27 +248,22 @@ export async function dev(vite, vite_config, svelte_config, get_remotes, root, s return; } - const rendered = await server.respond(request, { - getClientAddress: () => { - const { remoteAddress } = req.socket; - if (remoteAddress) return remoteAddress; - throw new Error('Could not determine clientAddress'); - }, - read: (file) => { - if (file in manifest._.server_assets) { - return fs.readFileSync(from_fs(file)); - } - - return fs.readFileSync(path.join(svelte_config.kit.files.assets, file)); - }, - before_handle: async (event, config, prerender, handle) => { - // we need to use .run because .enterWith() is not supported in Cloudflare Workers - // see https://blog.cloudflare.com/workers-node-js-asynclocalstorage/ - return await async_local_storage.run({ event, config, prerender }, handle); - }, - emulator + if (!isRunnableDevEnvironment(vite.environments.ssr)) { + throw new Error('The configured Vite SSR environment must be a RunnableDevEnvironment'); + } + + const request = getRequest({ + base, + request: req }); + /** @type {import('./ssr_entry.js')} */ + const { respond } = await vite.environments.ssr.runner.import( + '__sveltekit/dev-server-entry.js' + ); + + const rendered = await respond(request, req.socket.remoteAddress); + if (rendered.status === 404) { // @ts-expect-error serve_static_middleware.handle(req, res, () => { @@ -650,62 +297,6 @@ function remove_static_middlewares(server) { } } -/** - * @param {import('vite').ViteDevServer} vite - * @param {import('vite').ModuleNode | import('vite').EnvironmentModuleNode} node - * @param {Set} deps - */ -async function find_deps(vite, node, deps) { - // since `ssrTransformResult.deps` contains URLs instead of `ModuleNode`s, this process is asynchronous. - // instead of using `await`, we resolve all branches in parallel. - /** @type {Promise[]} */ - const branches = []; - - /** @param {import('vite').ModuleNode | import('vite').EnvironmentModuleNode} node */ - async function add(node) { - if (!deps.has(node)) { - deps.add(node); - await find_deps(vite, node, deps); - } - } - - /** @param {string} url */ - async function add_by_url(url) { - const node = await get_server_module_by_url(vite, url); - - if (node) { - await add(node); - } - } - - const transform_result = - /** @type {import('vite').ModuleNode} */ (node).ssrTransformResult || node.transformResult; - - if (transform_result) { - if (transform_result.deps) { - transform_result.deps.forEach((url) => branches.push(add_by_url(url))); - } - - if (transform_result.dynamicDeps) { - transform_result.dynamicDeps.forEach((url) => branches.push(add_by_url(url))); - } - } else { - node.importedModules.forEach((node) => branches.push(add(node))); - } - - await Promise.all(branches); -} - -/** - * @param {import('vite').ViteDevServer} vite - * @param {string} url - */ -function get_server_module_by_url(vite, url) { - return vite.environments - ? vite.environments.ssr.moduleGraph.getModuleByUrl(url) - : vite.moduleGraph.getModuleByUrl(url, true); -} - /** * Determine if a file is being requested with the correct case, * to ensure consistent behaviour between dev and prod and across @@ -726,3 +317,19 @@ function has_correct_case(file, assets) { return false; } + +/** + * @param {ViteDevServer} server + * @param {string} id + * @returns {void} + */ +export function invalidate_module(server, id) { + for (const environment in server.environments) { + // Invalidate the manifest data module so it reloads with new routes/files + const module = server.environments[environment].moduleGraph.getModuleById(id); + if (module) { + server.environments[environment].moduleGraph.invalidateModule(module); + void server.environments[environment].reloadModule(module); + } + } +} diff --git a/packages/kit/src/exports/vite/dev/server.js b/packages/kit/src/exports/vite/dev/server.js new file mode 100644 index 000000000000..82cbfd9626cd --- /dev/null +++ b/packages/kit/src/exports/vite/dev/server.js @@ -0,0 +1,52 @@ +import { AsyncLocalStorage } from 'node:async_hooks'; +import { check_feature } from '../../../utils/features.js'; +import { SCHEME } from '../../../utils/url.js'; +import { fix_stack_trace } from './sourcemaps.js'; +import { get_svelte_config } from './context.js'; +import { set_fix_stack_trace } from '../../../runtime/server/internal.js'; +import { set_assets } from '$app/paths/internal/server'; + +set_fix_stack_trace(fix_stack_trace); +set_assets(__SVELTEKIT_PATHS_ASSETS__); + +const KitServer = ( + await import(/* @vite-ignore */ import.meta.resolve('../../../runtime/server/index.js')) +).Server; + +const async_local_storage = new AsyncLocalStorage(); + +/** @param {string} label */ +globalThis.__SVELTEKIT_TRACK__ = (label) => { + const context = async_local_storage.getStore(); + if (!context || context.prerender === true) return; + + check_feature(context.event.route.id, context.config, label, get_svelte_config().kit.adapter); +}; + +const fetch = globalThis.fetch; +/** @type {typeof fetch} */ +globalThis.fetch = (info, init) => { + if (typeof info === 'string' && !SCHEME.test(info)) { + throw new Error( + `Cannot use relative URL (\${info}) with global fetch — use \`event.fetch\` instead: https://svelte.dev/docs/kit/web-standards#fetch-apis` + ); + } + + return fetch(info, init); +}; + +const emulator = await get_svelte_config().kit.adapter?.emulate?.(); + +export class Server extends KitServer { + /** @type {import('types').InternalServer['respond']} */ + async respond(request, options) { + options.before_handle = async (event, config, prerender, handle) => { + // we need to use .run because .enterWith() is not supported in Cloudflare Workers + // see https://blog.cloudflare.com/workers-node-js-asynclocalstorage/ + return await async_local_storage.run({ event, config, prerender }, handle); + }; + options.emulator = emulator; + + return await super.respond(request, options); + } +} diff --git a/packages/kit/src/exports/vite/dev/sourcemaps.js b/packages/kit/src/exports/vite/dev/sourcemaps.js new file mode 100644 index 000000000000..0240f652ad38 --- /dev/null +++ b/packages/kit/src/exports/vite/dev/sourcemaps.js @@ -0,0 +1,43 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import process from 'node:process'; +import { SRC_ROOT } from '../../../constants.js'; + +const cwd = process.cwd(); + +/** @param {Error} error */ +export function fix_stack_trace(error) { + if (!error.stack) { + return ''; + } + + let end = 0; + + error.stack = error.stack + .replaceAll('\0', '') // remove null bytes from e.g. virtual module IDs, or the response will fail + .split('\n') + .map((line, i) => { + const match = /^ {4}at (?:[^ ]+ \((.+)\)|(.+))$/.exec(line); + if (!match) { + end = i + 1; + return line; + } + + const loc = match[1] ?? match[2]; + const file = loc.replace(/:\d+:\d+$/, ''); + + if (fs.existsSync(file)) { + if (!file.includes('node_modules') && !file.includes(SRC_ROOT)) { + end = i + 1; + } + + return line.replace(file, path.relative(cwd, file)); + } + + return line; + }) + .slice(0, end) + .join('\n'); + + return error.stack; +} diff --git a/packages/kit/src/exports/vite/dev/ssr_entry.js b/packages/kit/src/exports/vite/dev/ssr_entry.js new file mode 100644 index 000000000000..6ca4e80d556f --- /dev/null +++ b/packages/kit/src/exports/vite/dev/ssr_entry.js @@ -0,0 +1,39 @@ +/** @import { InternalServer } from 'types' */ +import fs from 'node:fs'; +import path from 'node:path'; +import { env } from 'sveltekit:env'; +import { Server } from 'sveltekit:server'; +import { manifest } from 'sveltekit:server-manifest'; +import { createReadableStream } from '@sveltejs/kit/node'; +import { from_fs } from '../../../utils/vite.js'; + +/** @type {InternalServer} */ +const server = new Server(manifest); + +await server.init({ + env, + read: (file) => createReadableStream(from_fs(file)) +}); + +/** + * @param {Request} request + * @param {string | undefined} remote_address + * @returns {Promise} + */ +export async function respond(request, remote_address) { + return await server.respond(request, { + getClientAddress: () => { + if (remote_address) return remote_address; + throw new Error('Could not determine clientAddress'); + }, + read: (file) => { + if (file in manifest._.server_assets) { + return fs.readFileSync(from_fs(file)); + } + + return fs.readFileSync(path.join(__SVELTEKIT_FILES_ASSETS__, file)); + } + }); +} + +import.meta.hot?.accept(); diff --git a/packages/kit/src/exports/vite/dev/ssr_manifest.js b/packages/kit/src/exports/vite/dev/ssr_manifest.js new file mode 100644 index 000000000000..1495c996e6f9 --- /dev/null +++ b/packages/kit/src/exports/vite/dev/ssr_manifest.js @@ -0,0 +1,232 @@ +/** @import { SSRManifest } from '@sveltejs/kit' */ +/** @import { EnvironmentModuleNode } from 'vite' */ +import fs from 'node:fs'; +import { isCSSRequest } from 'vite'; +import { manifest_data, mime_types } from '__sveltekit/manifest-data'; +import { resolve } from './utils.js'; +import { from_fs, to_fs } from '../../../utils/vite.js'; +import { compact } from '../../../utils/array.js'; +import { join } from '../../../utils/path.js'; +import { get_dev_server, get_remotes } from './context.js'; + +// vite-specifc queries that we should skip handling for css urls +const vite_css_query_regex = /(?:\?|&)(?:raw|url|inline)(?:&|$)/; + +/** @type {SSRManifest} */ +export const manifest = { + appDir: __SVELTEKIT_APP_DIR__, + appPath: `${__SVELTEKIT_PATHS_BASE__}/${__SVELTEKIT_APP_DIR__}`, + assets: new Set(manifest_data.assets.map((asset) => asset.file)), + base: __SVELTEKIT_PATHS_BASE__, + mimeTypes: mime_types, + _: { + client: { + start: `${__SVELTEKIT_RUNTIME__}/client/entry.js`, + app: `${to_fs(__SVELTEKIT_OUT_DIR__)}/generated/client/app.js`, + imports: [], + stylesheets: [], + fonts: [], + uses_env_dynamic_public: true, + nodes: __SVELTEKIT_CLIENT_ROUTING__ + ? undefined + : manifest_data.nodes.map((node, i) => { + if (node.component || node.universal) { + return `${__SVELTEKIT_PATHS_BASE__}${to_fs(__SVELTEKIT_OUT_DIR__)}/generated/client/nodes/${i}.js`; + } + }), + + // \`css\` is not necessary in dev, as the JS file from \`nodes\` will reference the CSS file + routes: __SVELTEKIT_CLIENT_ROUTING__ + ? 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, i) => { + return async () => { + /** @type {import('types').SSRNode} */ + const result = {}; + result.index = i; + 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, module_node } = await resolve( + join(__SVELTEKIT_ROOT__, /** @type {string} */ (node.component)) + ); + module_nodes.push(module_node); + return module.default; + }; + } + + if (node.universal) { + if (node.page_options?.ssr === false) { + result.universal = 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(join(__SVELTEKIT_ROOT__, node.universal)); + module_nodes.push(module_node); + result.universal = module; + } + } + + if (node.server) { + const { module } = await resolve(join(__SVELTEKIT_ROOT__, 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(); + + for (const module_node of module_nodes) { + await find_deps(module_node, deps); + } + + /** @type {Record} */ + const styles = {}; + + for (const dep of deps) { + if (isCSSRequest(dep.url) && !vite_css_query_regex.test(dep.url)) { + // if (isCSSRequest(dep.url) && !vite_css_query_regex.test(dep.url)) { + const inline_css_url = dep.url.includes('?') + ? dep.url.replace('?', '?inline&') + : dep.url + '?inline'; + try { + const mod = await import(/* @vite-ignore */ inline_css_url); + 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; + }; + }), + prerendered_routes: new Set(), + get remotes() { + return Object.fromEntries( + get_remotes().map((remote) => [ + remote.hash, + () => import(/* @vite-ignore */ 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 = join(__SVELTEKIT_ROOT__, endpoint.file); + const { module } = await resolve(url); + return module; + } + : null, + endpoint_id: endpoint?.file + }; + }) + ), + matchers: async () => { + if (!manifest_data.params) return {}; + + const url = join(__SVELTEKIT_ROOT__, manifest_data.params); + const { module } = await resolve(url); + + if (!module.params) { + throw new Error(`${manifest_data.params} does not export \`params\` from \`defineParams\``); + } + + return module.params; + } + } +}; + +/** + * @param {EnvironmentModuleNode} node + * @param {Set} deps + */ +async function find_deps(node, deps) { + const dev_server = get_dev_server(); + + // since `ssrTransformResult.deps` contains URLs instead of `ModuleNode`s, this process is asynchronous. + // instead of using `await`, we resolve all branches in parallel. + /** @type {Promise[]} */ + const branches = []; + + /** @param {EnvironmentModuleNode} node */ + async function add(node) { + if (!deps.has(node)) { + deps.add(node); + await find_deps(node, deps); + } + } + + /** @param {string} url */ + async function add_by_url(url) { + const node = await dev_server.environments.ssr.moduleGraph.getModuleByUrl(url); + + if (node) { + await add(node); + } + } + + if (node.transformResult) { + if (node.transformResult.deps) { + node.transformResult.deps.forEach((url) => branches.push(add_by_url(url))); + } + + if (node.transformResult.dynamicDeps) { + node.transformResult.dynamicDeps.forEach((url) => branches.push(add_by_url(url))); + } + } else { + node.importedModules.forEach((node) => branches.push(add(node))); + } + + await Promise.all(branches); +} diff --git a/packages/kit/src/exports/vite/dev/utils.js b/packages/kit/src/exports/vite/dev/utils.js new file mode 100644 index 000000000000..9c4789d02344 --- /dev/null +++ b/packages/kit/src/exports/vite/dev/utils.js @@ -0,0 +1,49 @@ +import path from 'node:path'; +import { to_fs } from '../../../utils/vite.js'; +import { styleText } from 'node:util'; +import { buildErrorMessage } from 'vite'; +import { get_dev_server } from './context.js'; + +/** @param {string} url */ +export async function loud_ssr_load_module(url) { + try { + return await import(/* @vite-ignore */ url); + } catch (/** @type {any} */ err) { + const msg = buildErrorMessage(err, [styleText('red', `Internal server error: ${err.message}`)]); + + const vite = get_dev_server(); + + if (!vite.config.logger.hasErrorLogged(err)) { + vite.config.logger.error(msg, { error: err }); + } + + // TODO this is inadequate — it doesn't reliably show the overlay on every page load, + // and when it does appear it may immediately vanish. `vite.hot.send` broadcasts + // to all connected clients, even ones that are unaffected by the error. + // we need a more considered approach + vite.hot.send({ + type: 'error', + err: /** @type {import('vite').ErrorPayload['err']} */ ({ + ...err, + // these properties are non-enumerable and will + // not be serialized unless we explicitly include them + message: err.message, + stack: err.stack ?? '' + }) + }); + + throw err; + } +} + +/** @param {string} id */ +export async function resolve(id) { + const url = id.startsWith('..') ? to_fs(path.resolve(id)) : `file://${id}`; + + const module = await loud_ssr_load_module(url); + + const module_node = await get_dev_server().environments.ssr.moduleGraph.getModuleByUrl(url); + if (!module_node) throw new Error(`Could not find node for ${url}`); + + return { module, module_node, url }; +} diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 7d1a654e27e7..b38fa0a58116 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -7,8 +7,9 @@ import fs from 'node:fs'; import path from 'node:path'; import process from 'node:process'; import { styleText } from 'node:util'; +import * as devalue from 'devalue'; import MagicString from 'magic-string'; -import { loadEnv } from 'vite'; +import { isRunnableDevEnvironment, loadEnv } from 'vite'; import { exactRegex, prefixRegex } from 'rolldown/filter'; import { copy, mkdirp, read, resolve_entry, rimraf } from '../../utils/filesystem.js'; @@ -26,11 +27,11 @@ import { import * as sync from '../../core/sync/sync.js'; import { create_assets } from '../../core/sync/create_manifest_data/index.js'; import { load_and_validate_params } from '../../utils/params.js'; -import { runtime_directory, logger } from '../../core/utils.js'; +import { runtime_directory, logger, get_runtime_base, get_mime_lookup } from '../../core/utils.js'; import { generate_manifest } from '../../core/generate_manifest/index.js'; import { build_server_nodes } from './build/build_server.js'; import { find_deps, resolve_symlinks } from './build/utils.js'; -import { dev } from './dev/index.js'; +import { dev, invalidate_module } from './dev/index.js'; import { preview } from './preview/index.js'; import { error_for_missing_config, @@ -59,13 +60,16 @@ import { sveltekit_manifest_data, sveltekit_server, sveltekit_env_public_client, - sveltekit_env_public_server + sveltekit_env_public_server, + sveltekit_traced } from './module_ids.js'; import { import_peer } from '../../utils/import.js'; import { compact } from '../../utils/array.js'; import { should_ignore, has_children } from './static_analysis/utils.js'; import { process_config, split_config, validate_config } from '../../core/config/index.js'; import { treeshake_prerendered_remotes } from './build/remote.js'; +import { SVELTE_KIT_ASSETS } from '../../constants.js'; +import { pathToFileURL } from 'node:url'; /** * The posix-ified root of the project based on the Vite configuration. @@ -308,6 +312,9 @@ function kit({ svelte_config }) { /** @type {import('node:path').ParsedPath} */ let parsed_service_worker; + /** @type {string | null} */ + let server_instrumentation; + /** @type {string} */ let normalized_cwd; /** @type {Array<{ alias: string, path: string }>} */ @@ -359,6 +366,7 @@ function kit({ svelte_config }) { : 'globalThis.__sveltekit_dev'; env = loadEnv(config_env.mode, kit.env.dir, ''); + server_instrumentation = resolve_entry(path.join(kit.files.src, 'instrumentation.server')); vite = await import_peer('vite', root); @@ -403,8 +411,6 @@ function kit({ svelte_config }) { const client_hooks = resolve_entry(kit.files.hooks.client); if (client_hooks) allow.add(path.dirname(client_hooks)); - const generated = path.posix.join(out_dir, 'generated'); - // dev and preview config can be shared /** @type {UserConfig} */ const new_config = { @@ -418,7 +424,8 @@ function kit({ svelte_config }) { }, resolve: { alias: [ - { find: '__SERVER__', replacement: `${generated}/server` }, + { find: '__SERVER__', replacement: `${out_dir}/generated/server` }, + { find: '__SVELTEKIT__', replacement: `${runtime_directory}/..` }, { find: '$app', replacement: `${runtime_directory}/app` }, { find: '$env', replacement: `${runtime_directory}/env` }, { @@ -457,7 +464,9 @@ function kit({ svelte_config }) { // this does not affect app code, just handling of imported libraries that use $app or $env '$app', '$env' - ] + ], + // avoid Vite dev server reloading the first time a page is requested + include: ['@sveltejs/kit > devalue', '@sveltejs/kit > esm-env'] }, ssr: { noExternal: [ @@ -501,6 +510,7 @@ function kit({ svelte_config }) { const define = { __SVELTEKIT_APP_DIR__: s(posixify(kit.appDir)), + __SVELTEKIT_OUT_DIR__: s(out_dir), __SVELTEKIT_APP_VERSION__: s(kit.version.name), __SVELTEKIT_EMBEDDED__: s(kit.embedded), __SVELTEKIT_FORK_PRELOADS__: s(kit.experimental.forkPreloads), @@ -513,6 +523,8 @@ function kit({ svelte_config }) { __SVELTEKIT_SUPPORTS_ASYNC__: s( svelte_config.compilerOptions?.experimental?.async ?? false ), + __SVELTEKIT_ROOT__: s(root), + __SVELTEKIT_RUNTIME__: s(get_runtime_base(root)), __SVELTEKIT_DEV__: s(!is_build) }; @@ -531,7 +543,9 @@ function kit({ svelte_config }) { __SVELTEKIT_APP_VERSION_POLL_INTERVAL__: '0', __SVELTEKIT_PAYLOAD__: kit_global, // only relevant when bundleStrategy !== 'split' __SVELTEKIT_HAS_SERVER_LOAD__: 'true', - __SVELTEKIT_HAS_UNIVERSAL_LOAD__: 'true' + __SVELTEKIT_HAS_UNIVERSAL_LOAD__: 'true', + __SVELTEKIT_PATHS_ASSETS__: kit.paths.assets ? s(SVELTE_KIT_ASSETS) : 'undefined', + __SVELTEKIT_FILES_ASSETS__: s(posixify(kit.files.assets)) }; // These Kit dependencies are packaged as CommonJS, which means they must always be externalized. @@ -588,6 +602,115 @@ function kit({ svelte_config }) { } }; + /** @type {Plugin} */ + const plugin_node_environment = { + name: 'vite-plugin-sveltekit-node-environment', + apply: 'serve', + resolveId: { + filter: { + id: exactRegex('__sveltekit/dev-server-entry.js') + }, + handler() { + return server_instrumentation + ? sveltekit_traced + : posixify(path.join(import.meta.dirname, 'dev/ssr_entry.js')); + } + } + }; + + /** @type {Plugin} */ + const plugin_dev_ssr = { + name: 'vite-plugin-sveltekit-dev-ssr', + apply: 'serve', + + applyToEnvironment(environment) { + return environment.config.consumer === 'server'; + }, + + resolveId: { + filter: { + id: [ + exactRegex('sveltekit:server-manifest'), + exactRegex('sveltekit:server'), + exactRegex('sveltekit:env') + ] + }, + handler(id) { + if (id === 'sveltekit:server-manifest') { + return path.join(import.meta.dirname, 'dev/ssr_manifest.js'); + } + + if (id === 'sveltekit:server') { + return path.join(import.meta.dirname, 'dev/server.js'); + } + + if (id === 'sveltekit:env') { + return id; + } + } + }, + load: { + filter: { + id: [ + exactRegex('sveltekit:env'), + exactRegex(sveltekit_manifest_data), + exactRegex(sveltekit_traced) + ] + }, + handler(id) { + switch (id) { + case sveltekit_manifest_data: { + return dedent` + import { load_and_validate_params } from '__SVELTEKIT__/utils/params.js'; + import { to_fs } from '__SVELTEKIT__/utils/vite.js'; + import { loud_ssr_load_module } from '__SVELTEKIT__/exports/vite/dev/utils.js'; + + export const manifest_data = { + assets: ${s(manifest_data.assets)}, + hooks: { + client: ${s(manifest_data.hooks.client)}, + server: ${s(manifest_data.hooks.server)}, + universal: ${s(manifest_data.hooks.universal)} + }, + nodes: ${devalue.uneval(manifest_data.nodes)}, + routes: ${devalue.uneval(manifest_data.routes)}, + params: ${s(manifest_data.params)} + }; + + await load_and_validate_params({ + routes: manifest_data.routes, + params_path: manifest_data.params, + root: to_fs(__SVELTEKIT_ROOT__), + load: (file) => loud_ssr_load_module(file) + }); + + export const mime_types = ${s(get_mime_lookup(manifest_data))}; + `; + } + + case sveltekit_traced: { + if (!server_instrumentation) { + throw new Error('Server instrumentation file not found. This should never happen'); + } + + return dedent` + import ${s(posixify(server_instrumentation))}; + + const { respond } = await import('${import.meta.resolve('./dev/ssr_entry.js')}'); + export { respond }; + + import.meta.hot?.accept(); + `; + } + + case 'sveltekit:env': { + return `export const env = ${s(env)};`; + } + } + } + } + }; + /** @type {string | null} */ let explicit_env_entry = null; @@ -1590,22 +1713,19 @@ function kit({ svelte_config }) { * @see https://vitejs.dev/guide/api-plugin.html#configureserver */ async configureServer(server) { - return await dev( - server, - vite_config, - svelte_config, - () => remotes, - root, - (data) => { - manifest_data = data; - // Invalidate the manifest data module so it reloads with new routes/files - const module = server.moduleGraph.getModuleById(sveltekit_manifest_data); - if (module) { - server.moduleGraph.invalidateModule(module); - void server.reloadModule(module); - } - } - ); + if (isRunnableDevEnvironment(server.environments.ssr)) { + /** @type {typeof import('./dev/context.js')} */ + const { set_dev_server, set_remotes, set_svelte_config } = + await server.environments.ssr.runner.import(import.meta.resolve('./dev/context.js')); + set_dev_server(server); + set_remotes(remotes); + set_svelte_config(svelte_config); + } + + return dev(server, vite_config, svelte_config, root, (data) => { + manifest_data = data; + invalidate_module(server, sveltekit_manifest_data); + }); }, /** @@ -1654,7 +1774,8 @@ function kit({ svelte_config }) { await load_and_validate_params({ routes: manifest_data.routes, params_path: manifest_data.params, - root + root, + load: (file) => import(pathToFileURL(file).href) }); const { output: server_chunks } = /** @type {Rolldown.RolldownOutput} */ ( @@ -1680,6 +1801,7 @@ function kit({ svelte_config }) { const build_data = { app_dir: kit.appDir, app_path: `${kit.paths.base.slice(1)}${kit.paths.base ? '/' : ''}${kit.appDir}`, + base: kit.paths.base, manifest_data, out_dir: out, service_worker: service_worker_entry_file ? 'service-worker.js' : null, // TODO make file configurable? @@ -2118,8 +2240,10 @@ function kit({ svelte_config }) { [ svelte_config.kit.adapter?.vite?.plugins, plugin_setup, + plugin_node_environment, plugin_remote_guard, plugin_remote, + plugin_dev_ssr, plugin_virtual_modules, process.env.TEST !== 'true' ? plugin_guard : undefined, plugin_service_worker, diff --git a/packages/kit/src/exports/vite/module_ids.js b/packages/kit/src/exports/vite/module_ids.js index 2afaa59b9229..8487a0032093 100644 --- a/packages/kit/src/exports/vite/module_ids.js +++ b/packages/kit/src/exports/vite/module_ids.js @@ -12,6 +12,7 @@ export const service_worker = '\0virtual:service-worker'; export const sveltekit_server = '\0virtual:__sveltekit/server'; export const sveltekit_manifest_data = '\0virtual:__sveltekit/manifest-data'; +export const sveltekit_traced = '\0virtual:__sveltekit/traced'; export const app_server = posixify( fileURLToPath(new URL('../../runtime/app/server/index.js', import.meta.url)) diff --git a/packages/kit/src/types/ambient-private.d.ts b/packages/kit/src/types/ambient-private.d.ts index a51cda0feb5d..eebc8ef43588 100644 --- a/packages/kit/src/types/ambient-private.d.ts +++ b/packages/kit/src/types/ambient-private.d.ts @@ -21,6 +21,15 @@ declare module '__sveltekit/server' { export function set_read_implementation(fn: (path: string) => ReadableStream): void; } +/** Used to construct the SSR manifest from within an environment during development */ +declare module '__sveltekit/manifest-data' { + import { ManifestData } from 'types'; + + export const env: Record; + export const mime_types: Record; + export const manifest_data: ManifestData; +} + declare module '__sveltekit/env' { // exported environment variables are defined in env.d.ts diff --git a/packages/kit/src/types/ambient.d.ts b/packages/kit/src/types/ambient.d.ts index 41dcb45ecd1a..10944dd6eaea 100644 --- a/packages/kit/src/types/ambient.d.ts +++ b/packages/kit/src/types/ambient.d.ts @@ -175,3 +175,56 @@ declare module '$app/types' { */ export type AssetPath = ReturnType; } + +/** + * Exports the `Server` class for creating custom server entry points. + * @example + * ```js + * import { env } from 'sveltekit:env'; + * import { Server } from 'sveltekit:server'; + * import { manifest } from 'sveltekit:server-manifest'; + * + * const server = new Server(manifest); + * + * await server.init({ env }); + * ``` + */ +declare module 'sveltekit:server' { + export { Server } from '@sveltejs/kit'; +} + +/** + * Exports the SSR manifest used to initialise the server. + * @example + * ```js + * import { env } from 'sveltekit:env'; + * import { Server } from 'sveltekit:server'; + * import { manifest } from 'sveltekit:server-manifest'; + * + * const server = new Server(manifest); + * + * await server.init({ env }); + * ``` + */ +declare module 'sveltekit:server-manifest' { + import { SSRManifest } from '@sveltejs/kit'; + + export const manifest: SSRManifest; +} + +/** + * Exports the environment variables loaded by Vite. Used when initialising the server. + * @example + * ```js + * import { env } from 'sveltekit:env'; + * import { Server } from 'sveltekit:server'; + * import { manifest } from 'sveltekit:server-manifest'; + * + * const server = new Server(manifest); + * + * await server.init({ env }); + * ``` + */ +declare module 'sveltekit:env' { + export const env: Record; +} diff --git a/packages/kit/src/types/global-private.d.ts b/packages/kit/src/types/global-private.d.ts index 1bb3e664d601..5fe70e2bc8e1 100644 --- a/packages/kit/src/types/global-private.d.ts +++ b/packages/kit/src/types/global-private.d.ts @@ -38,6 +38,15 @@ declare global { * Should only be used when bundleStrategy !== 'inline' to avoid SvelteKit runtime changing on every build, preventing cacheability. */ const __SVELTEKIT_PAYLOAD__: SvelteKitPayload; + /** + * The Vite `root` setting used to construct paths to nodes and components + * for the SSR manifest during development + */ + const __SVELTEKIT_ROOT__: string; + const __SVELTEKIT_OUT_DIR__: string; + const __SVELTEKIT_FILES_ASSETS__: string; + /** Path to the internal SvelteKit runtime directory */ + const __SVELTEKIT_RUNTIME__: string; /** * Whether the `experimental.async` flag is applied */ diff --git a/packages/kit/src/types/internal.d.ts b/packages/kit/src/types/internal.d.ts index 547a3f103758..f391b5dc924f 100644 --- a/packages/kit/src/types/internal.d.ts +++ b/packages/kit/src/types/internal.d.ts @@ -69,6 +69,7 @@ export interface AssetDependencies { export interface BuildData { app_dir: string; app_path: string; + base: string; manifest_data: ManifestData; out_dir: string; service_worker: string | null; diff --git a/packages/kit/src/utils/params.js b/packages/kit/src/utils/params.js index 70832b288c83..b60aa0a15450 100644 --- a/packages/kit/src/utils/params.js +++ b/packages/kit/src/utils/params.js @@ -1,5 +1,4 @@ import path from 'node:path'; -import { pathToFileURL } from 'node:url'; /** * @param {import('types').RouteData[]} routes @@ -36,7 +35,7 @@ export function validate_param_matchers(params, names, file) { * routes: import('types').RouteData[]; * params_path: string | null; * root: string; - * load?: (file: string) => Promise>; + * load: (file: string) => Promise>; * }} opts * @returns {Promise | null>} */ @@ -50,7 +49,7 @@ export async function load_and_validate_params({ routes, params_path, root, load } const file = path.resolve(root, params_path); - const module = load ? await load(file) : await import(pathToFileURL(file).href); + const module = await load(file); if (!module.params || typeof module.params !== 'object') { throw new Error(`${params_path} does not export \`params\` from \`defineParams\``); diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index b35569cdfc29..06ce9515472e 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -1745,6 +1745,7 @@ declare module '@sveltejs/kit' { appPath: string; /** Static files from `config.files.assets` and the service worker (if any). */ assets: Set; + base: string; mimeTypes: Record; } @@ -3826,4 +3827,57 @@ declare module '$app/types' { export type AssetPath = ReturnType; } +/** + * Exports the `Server` class for creating custom server entry points. + * @example + * ```js + * import { env } from 'sveltekit:env'; + * import { Server } from 'sveltekit:server'; + * import { manifest } from 'sveltekit:server-manifest'; + * + * const server = new Server(manifest); + * + * await server.init({ env }); + * ``` + */ +declare module 'sveltekit:server' { + export { Server } from '@sveltejs/kit'; +} + +/** + * Exports the SSR manifest used to initialise the server. + * @example + * ```js + * import { env } from 'sveltekit:env'; + * import { Server } from 'sveltekit:server'; + * import { manifest } from 'sveltekit:server-manifest'; + * + * const server = new Server(manifest); + * + * await server.init({ env }); + * ``` + */ +declare module 'sveltekit:server-manifest' { + import { SSRManifest } from '@sveltejs/kit'; + + export const manifest: SSRManifest; +} + +/** + * Exports the environment variables loaded by Vite. Used when initialising the server. + * @example + * ```js + * import { env } from 'sveltekit:env'; + * import { Server } from 'sveltekit:server'; + * import { manifest } from 'sveltekit:server-manifest'; + * + * const server = new Server(manifest); + * + * await server.init({ env }); + * ``` + */ +declare module 'sveltekit:env' { + export const env: Record; +} + //# sourceMappingURL=index.d.ts.map \ No newline at end of file From 9d8040fcd8a27c519354e487951ad6cacee50df5 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Wed, 22 Jul 2026 03:56:33 +0800 Subject: [PATCH 002/109] fix remote import --- packages/kit/src/exports/vite/dev/ssr_manifest.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/kit/src/exports/vite/dev/ssr_manifest.js b/packages/kit/src/exports/vite/dev/ssr_manifest.js index 1495c996e6f9..91a2faeceb94 100644 --- a/packages/kit/src/exports/vite/dev/ssr_manifest.js +++ b/packages/kit/src/exports/vite/dev/ssr_manifest.js @@ -146,7 +146,10 @@ export const manifest = { return Object.fromEntries( get_remotes().map((remote) => [ remote.hash, - () => import(/* @vite-ignore */ remote.file).then((module) => ({ default: module })) + () => + import(/* @vite-ignore */ join(__SVELTEKIT_ROOT__, remote.file)).then((module) => ({ + default: module + })) ]) ); }, From e97c6a1cf19383865262fc9c15d87e18bcd608e9 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Wed, 22 Jul 2026 05:47:51 +0800 Subject: [PATCH 003/109] replace ssrLoadModule and serialise assets path correctly --- packages/kit/src/core/env.js | 8 +++-- packages/kit/src/exports/vite/dev/index.js | 23 ++++++------- packages/kit/src/exports/vite/dev/server.js | 17 +++++----- .../kit/src/exports/vite/dev/ssr_manifest.js | 4 +-- packages/kit/src/exports/vite/dev/utils.js | 2 +- packages/kit/src/exports/vite/index.js | 34 ++++++++----------- 6 files changed, 44 insertions(+), 44 deletions(-) diff --git a/packages/kit/src/core/env.js b/packages/kit/src/core/env.js index dbb467e78eba..8f95b9b296f4 100644 --- a/packages/kit/src/core/env.js +++ b/packages/kit/src/core/env.js @@ -52,12 +52,16 @@ export async function load_explicit_env(kit, file, root, mode) { /** @type {Record>} */ let variables; + if (!vite.isRunnableDevEnvironment(server.environments.ssr)) { + throw new Error('The configured Vite SSR environment must be a RunnableDevEnvironment'); + } + /** @type {import('../runtime/app/env/internal.js')} */ ( - await server.ssrLoadModule(`${runtime_directory}/app/env/internal.js`) + await server.environments.ssr.runner.import(`${runtime_directory}/app/env/internal.js`) ).set_building(); try { - ({ variables } = await server.ssrLoadModule(file)); + ({ variables } = await server.environments.ssr.runner.import(file)); if (!variables || typeof variables !== 'object') { throw new Error(`${file} must export a variables object`); diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index fc147a5cea78..294b98eaf439 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -22,6 +22,7 @@ import { } from '../utils.js'; import { escape_html } from '../../../utils/escape.js'; import { fix_stack_trace } from './sourcemaps.js'; +import { sveltekit_manifest_data } from '../module_ids.js'; /** * @param {import('vite').ViteDevServer} vite @@ -29,9 +30,9 @@ import { fix_stack_trace } from './sourcemaps.js'; * @param {import('types').ValidatedConfig} svelte_config * @param {string} root The project root directory * @param {(manifest_data: import('types').ManifestData) => void} set_manifest_data - * @return {() => void} + * @return {Promise<() => void>} */ -export function dev(vite, vite_config, svelte_config, root, set_manifest_data) { +export async function dev(vite, vite_config, svelte_config, root, set_manifest_data) { sync.init(svelte_config, root); /** @type {import('types').ManifestData} */ @@ -109,7 +110,7 @@ export function dev(vite, vite_config, svelte_config, root, set_manifest_data) { // Unless it's a file where the trailing slash page option might have changed if (timeout || !/\+(page|layout|server).*$/.test(file)) return; sync.update(svelte_config, manifest_data, file, root); - // invalidate_module(vite, sveltekit_manifest_data); + invalidate_module(vite, sveltekit_manifest_data); }); const { appTemplate, errorTemplate, serviceWorker, hooks } = svelte_config.kit.files; @@ -167,6 +168,13 @@ export function dev(vite, vite_config, svelte_config, root, set_manifest_data) { next(); }); + if (!isRunnableDevEnvironment(vite.environments.ssr)) { + throw new Error('The configured Vite SSR environment must be a RunnableDevEnvironment'); + } + + /** @type {import('./ssr_entry.js')} */ + const { respond } = await vite.environments.ssr.runner.import('__sveltekit/dev-server-entry.js'); + return () => { const serve_static_middleware = vite.middlewares.stack.find( (middleware) => @@ -248,20 +256,11 @@ export function dev(vite, vite_config, svelte_config, root, set_manifest_data) { return; } - if (!isRunnableDevEnvironment(vite.environments.ssr)) { - throw new Error('The configured Vite SSR environment must be a RunnableDevEnvironment'); - } - const request = getRequest({ base, request: req }); - /** @type {import('./ssr_entry.js')} */ - const { respond } = await vite.environments.ssr.runner.import( - '__sveltekit/dev-server-entry.js' - ); - const rendered = await respond(request, req.socket.remoteAddress); if (rendered.status === 404) { diff --git a/packages/kit/src/exports/vite/dev/server.js b/packages/kit/src/exports/vite/dev/server.js index 82cbfd9626cd..b756338f34b1 100644 --- a/packages/kit/src/exports/vite/dev/server.js +++ b/packages/kit/src/exports/vite/dev/server.js @@ -1,17 +1,18 @@ import { AsyncLocalStorage } from 'node:async_hooks'; -import { check_feature } from '../../../utils/features.js'; -import { SCHEME } from '../../../utils/url.js'; +import { Server as KitServer } from '../../../runtime/server/index.js'; import { fix_stack_trace } from './sourcemaps.js'; import { get_svelte_config } from './context.js'; -import { set_fix_stack_trace } from '../../../runtime/server/internal.js'; -import { set_assets } from '$app/paths/internal/server'; +import { check_feature } from '../../../utils/features.js'; +import { SCHEME } from '../../../utils/url.js'; +const { set_fix_stack_trace } = await import(/* @vite-ignore */ '__sveltekit/server'); set_fix_stack_trace(fix_stack_trace); -set_assets(__SVELTEKIT_PATHS_ASSETS__); -const KitServer = ( - await import(/* @vite-ignore */ import.meta.resolve('../../../runtime/server/index.js')) -).Server; +/** @type {typeof import('../../../runtime/app/paths/internal/server.js')} */ +const { set_assets } = await import( + /* @vite-ignore */ import.meta.resolve('../../../runtime/app/paths/internal/server.js') +); +set_assets(__SVELTEKIT_PATHS_ASSETS__); const async_local_storage = new AsyncLocalStorage(); diff --git a/packages/kit/src/exports/vite/dev/ssr_manifest.js b/packages/kit/src/exports/vite/dev/ssr_manifest.js index 91a2faeceb94..1e26123c889a 100644 --- a/packages/kit/src/exports/vite/dev/ssr_manifest.js +++ b/packages/kit/src/exports/vite/dev/ssr_manifest.js @@ -3,11 +3,11 @@ import fs from 'node:fs'; import { isCSSRequest } from 'vite'; import { manifest_data, mime_types } from '__sveltekit/manifest-data'; +import { get_dev_server, get_remotes } from './context.js'; import { resolve } from './utils.js'; -import { from_fs, to_fs } from '../../../utils/vite.js'; import { compact } from '../../../utils/array.js'; import { join } from '../../../utils/path.js'; -import { get_dev_server, get_remotes } from './context.js'; +import { from_fs, to_fs } from '../../../utils/vite.js'; // vite-specifc queries that we should skip handling for css urls const vite_css_query_regex = /(?:\?|&)(?:raw|url|inline)(?:&|$)/; diff --git a/packages/kit/src/exports/vite/dev/utils.js b/packages/kit/src/exports/vite/dev/utils.js index 9c4789d02344..67fd1b904a16 100644 --- a/packages/kit/src/exports/vite/dev/utils.js +++ b/packages/kit/src/exports/vite/dev/utils.js @@ -1,8 +1,8 @@ import path from 'node:path'; -import { to_fs } from '../../../utils/vite.js'; import { styleText } from 'node:util'; import { buildErrorMessage } from 'vite'; import { get_dev_server } from './context.js'; +import { to_fs } from '../../../utils/vite.js'; /** @param {string} url */ export async function loud_ssr_load_module(url) { diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index b38fa0a58116..3c2dc2f51d8b 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -544,7 +544,9 @@ function kit({ svelte_config }) { __SVELTEKIT_PAYLOAD__: kit_global, // only relevant when bundleStrategy !== 'split' __SVELTEKIT_HAS_SERVER_LOAD__: 'true', __SVELTEKIT_HAS_UNIVERSAL_LOAD__: 'true', - __SVELTEKIT_PATHS_ASSETS__: kit.paths.assets ? s(SVELTE_KIT_ASSETS) : 'undefined', + __SVELTEKIT_PATHS_ASSETS__: kit.paths.assets + ? s(SVELTE_KIT_ASSETS) + : s(kit.paths.assets), __SVELTEKIT_FILES_ASSETS__: s(posixify(kit.files.assets)) }; @@ -602,22 +604,6 @@ function kit({ svelte_config }) { } }; - /** @type {Plugin} */ - const plugin_node_environment = { - name: 'vite-plugin-sveltekit-node-environment', - apply: 'serve', - resolveId: { - filter: { - id: exactRegex('__sveltekit/dev-server-entry.js') - }, - handler() { - return server_instrumentation - ? sveltekit_traced - : posixify(path.join(import.meta.dirname, 'dev/ssr_entry.js')); - } - } - }; - /** @type {Plugin} */ const plugin_dev_ssr = { name: 'vite-plugin-sveltekit-dev-ssr', @@ -630,12 +616,19 @@ function kit({ svelte_config }) { resolveId: { filter: { id: [ + exactRegex('__sveltekit/dev-server-entry.js'), exactRegex('sveltekit:server-manifest'), exactRegex('sveltekit:server'), exactRegex('sveltekit:env') ] }, handler(id) { + if (id === '__sveltekit/dev-server-entry.js') { + return server_instrumentation + ? sveltekit_traced + : posixify(path.join(import.meta.dirname, 'dev/ssr_entry.js')); + } + if (id === 'sveltekit:server-manifest') { return path.join(import.meta.dirname, 'dev/ssr_manifest.js'); } @@ -1107,7 +1100,11 @@ function kit({ svelte_config }) { // being called again with `opts.ssr === true` if the module isn't // already loaded) so we can determine what it exports if (dev_server) { - const module = await dev_server.ssrLoadModule(id); + if (!isRunnableDevEnvironment(dev_server.environments.ssr)) { + throw new Error('The configured Vite SSR environment must be a RunnableDevEnvironment'); + } + + const module = await dev_server.environments.ssr.runner.import(id); for (const [name, value] of Object.entries(module)) { const type = value?.__?.type; @@ -2240,7 +2237,6 @@ function kit({ svelte_config }) { [ svelte_config.kit.adapter?.vite?.plugins, plugin_setup, - plugin_node_environment, plugin_remote_guard, plugin_remote, plugin_dev_ssr, From 0c78c0e07c1c6d6b53ad466cec15f69182882dc1 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Wed, 22 Jul 2026 05:51:16 +0800 Subject: [PATCH 004/109] ensure it works with vitest --- packages/kit/src/exports/vite/dev/server.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/kit/src/exports/vite/dev/server.js b/packages/kit/src/exports/vite/dev/server.js index b756338f34b1..ea0e9b9d9c4e 100644 --- a/packages/kit/src/exports/vite/dev/server.js +++ b/packages/kit/src/exports/vite/dev/server.js @@ -8,10 +8,7 @@ import { SCHEME } from '../../../utils/url.js'; const { set_fix_stack_trace } = await import(/* @vite-ignore */ '__sveltekit/server'); set_fix_stack_trace(fix_stack_trace); -/** @type {typeof import('../../../runtime/app/paths/internal/server.js')} */ -const { set_assets } = await import( - /* @vite-ignore */ import.meta.resolve('../../../runtime/app/paths/internal/server.js') -); +const { set_assets } = await import(/* @vite-ignore */ '$app/paths/internal/server'); set_assets(__SVELTEKIT_PATHS_ASSETS__); const async_local_storage = new AsyncLocalStorage(); @@ -36,7 +33,7 @@ globalThis.fetch = (info, init) => { return fetch(info, init); }; -const emulator = await get_svelte_config().kit.adapter?.emulate?.(); +const emulator = await get_svelte_config()?.kit.adapter?.emulate?.(); export class Server extends KitServer { /** @type {import('types').InternalServer['respond']} */ From 0c197776c8e89f939498f73c8f9665a0faa20209 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Wed, 22 Jul 2026 06:12:12 +0800 Subject: [PATCH 005/109] doc fix --- .../docs/25-build-and-deploy/99-writing-adapters.md | 6 +++++- 1 file changed, 5 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 33c85d67a991..722dff9dbb4a 100644 --- a/documentation/docs/25-build-and-deploy/99-writing-adapters.md +++ b/documentation/docs/25-build-and-deploy/99-writing-adapters.md @@ -76,7 +76,11 @@ The main Vite server environment SvelteKit uses is named `ssr`. You can change i ```js // @errors: 2304 1005 1109 config(userConfig) { - userConfig.environments.ssr = { ... } + userConfig.environments = { + ssr: { + // ... + } + } } ``` From 3ff979bdc6524ad6c117b785c98b44a2568cbf1c Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Wed, 22 Jul 2026 23:50:13 +0800 Subject: [PATCH 006/109] import only after server has started --- packages/kit/src/exports/vite/dev/index.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 294b98eaf439..3022aebdc7c3 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -30,9 +30,9 @@ import { sveltekit_manifest_data } from '../module_ids.js'; * @param {import('types').ValidatedConfig} svelte_config * @param {string} root The project root directory * @param {(manifest_data: import('types').ManifestData) => void} set_manifest_data - * @return {Promise<() => void>} + * @return {() => void} */ -export async function dev(vite, vite_config, svelte_config, root, set_manifest_data) { +export function dev(vite, vite_config, svelte_config, root, set_manifest_data) { sync.init(svelte_config, root); /** @type {import('types').ManifestData} */ @@ -168,13 +168,6 @@ export async function dev(vite, vite_config, svelte_config, root, set_manifest_d next(); }); - if (!isRunnableDevEnvironment(vite.environments.ssr)) { - throw new Error('The configured Vite SSR environment must be a RunnableDevEnvironment'); - } - - /** @type {import('./ssr_entry.js')} */ - const { respond } = await vite.environments.ssr.runner.import('__sveltekit/dev-server-entry.js'); - return () => { const serve_static_middleware = vite.middlewares.stack.find( (middleware) => @@ -261,6 +254,14 @@ export async function dev(vite, vite_config, svelte_config, root, set_manifest_d request: req }); + if (!isRunnableDevEnvironment(vite.environments.ssr)) { + throw new Error('The configured Vite SSR environment must be a RunnableDevEnvironment'); + } + + /** @type {import('./ssr_entry.js')} */ + const { respond } = await vite.environments.ssr.runner.import( + '__sveltekit/dev-server-entry.js' + ); const rendered = await respond(request, req.socket.remoteAddress); if (rendered.status === 404) { From f8245f3ce130bf58eda7d37bc94a134ee4925195 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Wed, 22 Jul 2026 23:50:59 +0800 Subject: [PATCH 007/109] Update index.js --- packages/kit/src/exports/vite/index.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 3c2dc2f51d8b..7c5adff372ab 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -69,7 +69,7 @@ import { should_ignore, has_children } from './static_analysis/utils.js'; import { process_config, split_config, validate_config } from '../../core/config/index.js'; import { treeshake_prerendered_remotes } from './build/remote.js'; import { SVELTE_KIT_ASSETS } from '../../constants.js'; -import { pathToFileURL } from 'node:url'; +import { fileURLToPath, pathToFileURL } from 'node:url'; /** * The posix-ified root of the project based on the Vite configuration. @@ -689,7 +689,7 @@ function kit({ svelte_config }) { return dedent` import ${s(posixify(server_instrumentation))}; - const { respond } = await import('${import.meta.resolve('./dev/ssr_entry.js')}'); + const { respond } = await import(${s(import.meta.resolve('./dev/ssr_entry.js'))}); export { respond }; import.meta.hot?.accept(); @@ -1710,18 +1710,21 @@ function kit({ svelte_config }) { * @see https://vitejs.dev/guide/api-plugin.html#configureserver */ async configureServer(server) { + /** @type {typeof import('./dev/context.js')} */ + let context; + if (isRunnableDevEnvironment(server.environments.ssr)) { - /** @type {typeof import('./dev/context.js')} */ - const { set_dev_server, set_remotes, set_svelte_config } = - await server.environments.ssr.runner.import(import.meta.resolve('./dev/context.js')); - set_dev_server(server); - set_remotes(remotes); - set_svelte_config(svelte_config); + context = await server.environments.ssr.runner.import( + import.meta.resolve('./dev/context.js') + ); + context.set_dev_server(server); } return dev(server, vite_config, svelte_config, root, (data) => { manifest_data = data; invalidate_module(server, sveltekit_manifest_data); + context.set_svelte_config(svelte_config); + context.set_remotes(remotes); }); }, From 0ba765ade38e1ee0e56887d78386ff9b0409d660 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Wed, 22 Jul 2026 23:59:38 +0800 Subject: [PATCH 008/109] prettier --- packages/kit/src/exports/vite/dev/context.js | 44 +++++++++++--------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/packages/kit/src/exports/vite/dev/context.js b/packages/kit/src/exports/vite/dev/context.js index 6eb0b6635ab3..803aa8a81f32 100644 --- a/packages/kit/src/exports/vite/dev/context.js +++ b/packages/kit/src/exports/vite/dev/context.js @@ -1,38 +1,42 @@ /** @import { ValidatedConfig } from 'types' */ /** @import { ViteDevServer } from 'vite' */ -/** @returns {Array<{ hash: string, file: string }>} */ -export function get_remotes() { - return import.meta.hot?.data.remotes; +/** + * @typedef {object} Context + * @property {ViteDevServer} dev_server + * @property {Array<{ hash: string, file: string }>} remotes + * @property {ValidatedConfig} svelte_config + */ + +/** @type {Context} */ +const context = import.meta.hot?.data; + +/** @returns {ViteDevServer} */ +export function get_dev_server() { + return context.dev_server; } -/** @param {Array<{ hash: string, file: string }>} r */ -export function set_remotes(r) { - if (import.meta.hot) { - import.meta.hot.data.remotes = r; - } +/** @param {ViteDevServer} server */ +export function set_dev_server(server) { + context.dev_server = server; } /** @returns {ValidatedConfig} */ export function get_svelte_config() { - return import.meta.hot?.data.svelte_config; + return context.svelte_config; } /** @param {ValidatedConfig} config */ export function set_svelte_config(config) { - if (import.meta.hot) { - import.meta.hot.data.svelte_config = config; - } + context.svelte_config = config; } -/** @returns {ViteDevServer} */ -export function get_dev_server() { - return import.meta.hot?.data.dev_server; +/** @returns {Array<{ hash: string, file: string }>} */ +export function get_remotes() { + return context.remotes; } -/** @param {ViteDevServer} server */ -export function set_dev_server(server) { - if (import.meta.hot) { - import.meta.hot.data.dev_server = server; - } +/** @param {Array<{ hash: string, file: string }>} r */ +export function set_remotes(r) { + context.remotes = r; } From 9e0a2a965e2c6cb14c0f6aeca6723ec59cfa0f4f Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Thu, 23 Jul 2026 00:07:46 +0800 Subject: [PATCH 009/109] fix --- packages/kit/src/exports/vite/dev/context.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/src/exports/vite/dev/context.js b/packages/kit/src/exports/vite/dev/context.js index 803aa8a81f32..cf7fd9843d4f 100644 --- a/packages/kit/src/exports/vite/dev/context.js +++ b/packages/kit/src/exports/vite/dev/context.js @@ -9,7 +9,7 @@ */ /** @type {Context} */ -const context = import.meta.hot?.data; +const context = import.meta.hot?.data ?? {}; /** @returns {ViteDevServer} */ export function get_dev_server() { From 8fc3473f34df04b10998011b035bf1422554cc96 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Thu, 23 Jul 2026 00:08:30 +0800 Subject: [PATCH 010/109] format --- packages/kit/src/exports/vite/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 4036a1b526a7..08af0a8dac80 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -6,6 +6,7 @@ import fs from 'node:fs'; import path from 'node:path'; import process from 'node:process'; +import { pathToFileURL } from 'node:url'; import { styleText } from 'node:util'; import * as devalue from 'devalue'; import MagicString from 'magic-string'; @@ -68,7 +69,6 @@ import { should_ignore, has_children } from './static_analysis/utils.js'; import { process_config, split_config, validate_config } from '../../core/config/index.js'; import { treeshake_prerendered_remotes } from './build/remote.js'; import { SVELTE_KIT_ASSETS } from '../../constants.js'; -import { fileURLToPath, pathToFileURL } from 'node:url'; /** * The posix-ified root of the project based on the Vite configuration. From fd47988ddf914b5e9ddad31dd24a77477d882ab1 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Thu, 23 Jul 2026 01:58:56 +0800 Subject: [PATCH 011/109] standardise handler --- .../99-writing-adapters.md | 51 +++++++++++++------ .../vite/dev/{ssr_entry.js => handler.js} | 13 +++-- packages/kit/src/exports/vite/dev/index.js | 15 +++--- packages/kit/src/exports/vite/index.js | 21 +++++--- packages/kit/src/exports/vite/module_ids.js | 3 ++ packages/kit/src/runtime/app/server/index.js | 5 ++ 6 files changed, 76 insertions(+), 32 deletions(-) rename packages/kit/src/exports/vite/dev/{ssr_entry.js => handler.js} (72%) 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 722dff9dbb4a..2eb27d67b098 100644 --- a/documentation/docs/25-build-and-deploy/99-writing-adapters.md +++ b/documentation/docs/25-build-and-deploy/99-writing-adapters.md @@ -84,7 +84,26 @@ config(userConfig) { } ``` -You can also create your own server entry file by importing the `Server` class from `sveltekit:server`, the environment variables loaded by Vite through `env` from `sveltekit:env`, and your app-specific information as `manifest` from `sveltekit:server-manifest`. +You can also define a custom server entry during development by adding a Vite plugin which resolves the `sveltekit:server-entry` ID to your own module. + +```js +{ + name: 'vite-plugin-name-it-yourself', + applyToEnvironment(environment) { + return environment.name === 'ssr'; + }, + resolveId: { + filter: { + id: /^sveltekit:server-entry$/ + }, + handler(id) { + return import.meta.resolve('./path-to-your-server.js'); + } + } +} +``` + +This module should instantiate the server with your app's manifest, initialise environment variables and the `read` implementation, and export a `fetch` handler which receives a `Request` and returns a `Response`: ```js import { env } from 'sveltekit:env'; @@ -93,21 +112,23 @@ import { manifest } from 'sveltekit:server-manifest'; const server = new Server(manifest); -await server.init({ env }); - -export default { - /** - * @param {Request} request - * @returns {Promise} - */ - async fetch(request) { - return await server.respond(request, { - getClientAddress: () => { - return request.headers.get('how-your-platform-exposes-the-remote-address') - } - }); - } +await server.init({ + env, + read: (file) => { /* implement how your platform retrieves file contents */ } +}); + +/** + * @param {Request} request + * @returns {Promise} + */ +export async function fetch(request) { + return await server.respond(request, { + getClientAddress: () => { + return request.headers.get('how-your-platform-exposes-the-remote-address') + } + }); } +// Without this, server file changes will invalidate the entire Vite server module graph: import.meta.hot?.accept(); ``` diff --git a/packages/kit/src/exports/vite/dev/ssr_entry.js b/packages/kit/src/exports/vite/dev/handler.js similarity index 72% rename from packages/kit/src/exports/vite/dev/ssr_entry.js rename to packages/kit/src/exports/vite/dev/handler.js index 6ca4e80d556f..5ce0c024cc3a 100644 --- a/packages/kit/src/exports/vite/dev/ssr_entry.js +++ b/packages/kit/src/exports/vite/dev/handler.js @@ -12,15 +12,22 @@ const server = new Server(manifest); await server.init({ env, - read: (file) => createReadableStream(from_fs(file)) + read: (file) => createReadableStream(file) }); /** * @param {Request} request - * @param {string | undefined} remote_address * @returns {Promise} */ -export async function respond(request, remote_address) { +export async function fetch(request) { + /** @type {string | undefined} */ + let remote_address; + + if (request.headers.has('x-sveltekit-remote-address')) { + remote_address = request.headers.get('x-sveltekit-remote-address') ?? undefined; + request.headers.delete('x-sveltekit-remote-address'); + } + return await server.respond(request, { getClientAddress: () => { if (remote_address) return remote_address; diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 3022aebdc7c3..e68663616302 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -22,7 +22,7 @@ import { } from '../utils.js'; import { escape_html } from '../../../utils/escape.js'; import { fix_stack_trace } from './sourcemaps.js'; -import { sveltekit_manifest_data } from '../module_ids.js'; +import { sveltekit_dev_server_entry, sveltekit_manifest_data } from '../module_ids.js'; /** * @param {import('vite').ViteDevServer} vite @@ -258,11 +258,14 @@ export function dev(vite, vite_config, svelte_config, root, set_manifest_data) { throw new Error('The configured Vite SSR environment must be a RunnableDevEnvironment'); } - /** @type {import('./ssr_entry.js')} */ - const { respond } = await vite.environments.ssr.runner.import( - '__sveltekit/dev-server-entry.js' - ); - const rendered = await respond(request, req.socket.remoteAddress); + /** @type {{ fetch(request: Request): Promise }} */ + const { fetch } = await vite.environments.ssr.runner.import(sveltekit_dev_server_entry); + + if (req.socket.remoteAddress) { + request.headers.set('x-sveltekit-remote-address', req.socket.remoteAddress); + } + + const rendered = await fetch(request); if (rendered.status === 404) { // @ts-expect-error diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 08af0a8dac80..e35bdf055ff2 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -61,7 +61,9 @@ import { sveltekit_manifest_data, sveltekit_env_public_client, sveltekit_env_public_server, - sveltekit_traced + sveltekit_traced, + sveltekit_dev_server_entry, + sveltekit_dev_server_handler } from './module_ids.js'; import { import_peer } from '../../utils/import.js'; import { compact } from '../../utils/array.js'; @@ -615,17 +617,16 @@ function kit({ svelte_config }) { resolveId: { filter: { id: [ - exactRegex('__sveltekit/dev-server-entry.js'), + exactRegex(sveltekit_dev_server_entry), exactRegex('sveltekit:server-manifest'), exactRegex('sveltekit:server'), - exactRegex('sveltekit:env') + exactRegex('sveltekit:env'), + exactRegex(sveltekit_dev_server_handler) ] }, handler(id) { - if (id === '__sveltekit/dev-server-entry.js') { - return server_instrumentation - ? sveltekit_traced - : posixify(path.join(import.meta.dirname, 'dev/ssr_entry.js')); + if (id === sveltekit_dev_server_entry) { + return server_instrumentation ? sveltekit_traced : sveltekit_dev_server_handler; } if (id === 'sveltekit:server-manifest') { @@ -639,6 +640,10 @@ function kit({ svelte_config }) { if (id === 'sveltekit:env') { return id; } + + if (id === sveltekit_dev_server_handler) { + return posixify(path.join(import.meta.dirname, 'dev/ssr_entry.js')); + } } }, load: { @@ -688,7 +693,7 @@ function kit({ svelte_config }) { return dedent` import ${s(posixify(server_instrumentation))}; - const { respond } = await import(${s(import.meta.resolve('./dev/ssr_entry.js'))}); + const { respond } = await import(${s(sveltekit_dev_server_handler)}); export { respond }; import.meta.hot?.accept(); diff --git a/packages/kit/src/exports/vite/module_ids.js b/packages/kit/src/exports/vite/module_ids.js index 2d2b79c63eab..90fab6e37230 100644 --- a/packages/kit/src/exports/vite/module_ids.js +++ b/packages/kit/src/exports/vite/module_ids.js @@ -12,6 +12,9 @@ export const service_worker = '\0virtual:service-worker'; export const sveltekit_manifest_data = '\0virtual:__sveltekit/manifest-data'; export const sveltekit_traced = '\0virtual:__sveltekit/traced'; +export const sveltekit_dev_server_entry = '__sveltekit/dev-server-entry'; +export const sveltekit_dev_server_handler = 'sveltekit:server-entry'; + export const app_server = posixify( fileURLToPath(new URL('../../runtime/app/server/index.js', import.meta.url)) ); diff --git a/packages/kit/src/runtime/app/server/index.js b/packages/kit/src/runtime/app/server/index.js index ae79d3ce3dda..7af31db69b5f 100644 --- a/packages/kit/src/runtime/app/server/index.js +++ b/packages/kit/src/runtime/app/server/index.js @@ -1,6 +1,7 @@ import { read_implementation, manifest } from '../../server/internal.js'; import { assets } from '$app/paths/internal/server'; import { base64_decode } from '../../utils.js'; +import { from_fs } from '../../../utils/vite.js'; /** * Read the contents of an imported asset from the filesystem @@ -25,6 +26,10 @@ export function read(asset) { ); } + if (__SVELTEKIT_DEV__) { + asset = from_fs(asset); + } + // handle inline assets internally const match = /^data:([^;,]+)?(;base64)?,/.exec(asset); if (match) { From 930dfd55fece87ff616eb1d309bc21de866319aa Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Thu, 23 Jul 2026 02:30:20 +0800 Subject: [PATCH 012/109] resolve to ssr entry correctly --- packages/kit/src/exports/vite/dev/index.js | 4 ++-- .../vite/dev/{handler.js => ssr_entry.js} | 0 packages/kit/src/exports/vite/index.js | 20 ++++++++++--------- packages/kit/src/exports/vite/module_ids.js | 4 ++-- 4 files changed, 15 insertions(+), 13 deletions(-) rename packages/kit/src/exports/vite/dev/{handler.js => ssr_entry.js} (100%) diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index e68663616302..a0777d822fe5 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -22,7 +22,7 @@ import { } from '../utils.js'; import { escape_html } from '../../../utils/escape.js'; import { fix_stack_trace } from './sourcemaps.js'; -import { sveltekit_dev_server_entry, sveltekit_manifest_data } from '../module_ids.js'; +import { sveltekit_dev_internal_server_entry, sveltekit_manifest_data } from '../module_ids.js'; /** * @param {import('vite').ViteDevServer} vite @@ -259,7 +259,7 @@ export function dev(vite, vite_config, svelte_config, root, set_manifest_data) { } /** @type {{ fetch(request: Request): Promise }} */ - const { fetch } = await vite.environments.ssr.runner.import(sveltekit_dev_server_entry); + const { fetch } = await vite.environments.ssr.runner.import(sveltekit_dev_internal_server_entry); if (req.socket.remoteAddress) { request.headers.set('x-sveltekit-remote-address', req.socket.remoteAddress); diff --git a/packages/kit/src/exports/vite/dev/handler.js b/packages/kit/src/exports/vite/dev/ssr_entry.js similarity index 100% rename from packages/kit/src/exports/vite/dev/handler.js rename to packages/kit/src/exports/vite/dev/ssr_entry.js diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index e35bdf055ff2..1f5805a39264 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -62,8 +62,8 @@ import { sveltekit_env_public_client, sveltekit_env_public_server, sveltekit_traced, - sveltekit_dev_server_entry, - sveltekit_dev_server_handler + sveltekit_dev_internal_server_entry, + sveltekit_dev_server_entry } from './module_ids.js'; import { import_peer } from '../../utils/import.js'; import { compact } from '../../utils/array.js'; @@ -617,16 +617,18 @@ function kit({ svelte_config }) { resolveId: { filter: { id: [ - exactRegex(sveltekit_dev_server_entry), + exactRegex(sveltekit_dev_internal_server_entry), exactRegex('sveltekit:server-manifest'), exactRegex('sveltekit:server'), exactRegex('sveltekit:env'), - exactRegex(sveltekit_dev_server_handler) + exactRegex(sveltekit_dev_server_entry) ] }, handler(id) { - if (id === sveltekit_dev_server_entry) { - return server_instrumentation ? sveltekit_traced : sveltekit_dev_server_handler; + if (id === sveltekit_dev_internal_server_entry) { + return server_instrumentation + ? sveltekit_traced + : this.resolve(sveltekit_dev_server_entry); } if (id === 'sveltekit:server-manifest') { @@ -641,7 +643,7 @@ function kit({ svelte_config }) { return id; } - if (id === sveltekit_dev_server_handler) { + if (id === sveltekit_dev_server_entry) { return posixify(path.join(import.meta.dirname, 'dev/ssr_entry.js')); } } @@ -693,7 +695,7 @@ function kit({ svelte_config }) { return dedent` import ${s(posixify(server_instrumentation))}; - const { respond } = await import(${s(sveltekit_dev_server_handler)}); + const { respond } = await import(${s(sveltekit_dev_server_entry)}); export { respond }; import.meta.hot?.accept(); @@ -2243,9 +2245,9 @@ function kit({ svelte_config }) { [ svelte_config.kit.adapter?.vite?.plugins, plugin_setup, + plugin_dev_ssr, plugin_remote_guard, plugin_remote, - plugin_dev_ssr, plugin_virtual_modules, process.env.TEST !== 'true' ? plugin_guard : undefined, plugin_service_worker, diff --git a/packages/kit/src/exports/vite/module_ids.js b/packages/kit/src/exports/vite/module_ids.js index 90fab6e37230..8a5b2ff48996 100644 --- a/packages/kit/src/exports/vite/module_ids.js +++ b/packages/kit/src/exports/vite/module_ids.js @@ -12,8 +12,8 @@ export const service_worker = '\0virtual:service-worker'; export const sveltekit_manifest_data = '\0virtual:__sveltekit/manifest-data'; export const sveltekit_traced = '\0virtual:__sveltekit/traced'; -export const sveltekit_dev_server_entry = '__sveltekit/dev-server-entry'; -export const sveltekit_dev_server_handler = 'sveltekit:server-entry'; +export const sveltekit_dev_internal_server_entry = '__sveltekit/dev-server-entry'; +export const sveltekit_dev_server_entry = 'sveltekit:server-entry'; export const app_server = posixify( fileURLToPath(new URL('../../runtime/app/server/index.js', import.meta.url)) From 68e7364c427a0ddb64017e3626548e2af5318597 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Thu, 23 Jul 2026 02:44:29 +0800 Subject: [PATCH 013/109] format --- packages/kit/src/exports/vite/dev/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index a0777d822fe5..aebecf7aabb7 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -259,7 +259,9 @@ export function dev(vite, vite_config, svelte_config, root, set_manifest_data) { } /** @type {{ fetch(request: Request): Promise }} */ - const { fetch } = await vite.environments.ssr.runner.import(sveltekit_dev_internal_server_entry); + const { fetch } = await vite.environments.ssr.runner.import( + sveltekit_dev_internal_server_entry + ); if (req.socket.remoteAddress) { request.headers.set('x-sveltekit-remote-address', req.socket.remoteAddress); From a4c64f921a3c7c0baab918abe595d4fc7bb29bbb Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Thu, 23 Jul 2026 02:51:48 +0800 Subject: [PATCH 014/109] doc fix --- documentation/docs/25-build-and-deploy/99-writing-adapters.md | 1 + 1 file changed, 1 insertion(+) 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 2eb27d67b098..466129f0cba2 100644 --- a/documentation/docs/25-build-and-deploy/99-writing-adapters.md +++ b/documentation/docs/25-build-and-deploy/99-writing-adapters.md @@ -87,6 +87,7 @@ config(userConfig) { You can also define a custom server entry during development by adding a Vite plugin which resolves the `sveltekit:server-entry` ID to your own module. ```js +// @errors: 1005 1128 { name: 'vite-plugin-name-it-yourself', applyToEnvironment(environment) { From 88fb3fbbfb9d8216560109eca23ddad3694c67a2 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Thu, 23 Jul 2026 11:20:24 +0800 Subject: [PATCH 015/109] fix trace facade not exporting fetch handler --- packages/kit/src/exports/vite/dev/index.js | 4 ++-- packages/kit/src/exports/vite/index.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index aebecf7aabb7..094872aa71ae 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -259,7 +259,7 @@ export function dev(vite, vite_config, svelte_config, root, set_manifest_data) { } /** @type {{ fetch(request: Request): Promise }} */ - const { fetch } = await vite.environments.ssr.runner.import( + const server_entry = await vite.environments.ssr.runner.import( sveltekit_dev_internal_server_entry ); @@ -267,7 +267,7 @@ export function dev(vite, vite_config, svelte_config, root, set_manifest_data) { request.headers.set('x-sveltekit-remote-address', req.socket.remoteAddress); } - const rendered = await fetch(request); + const rendered = await server_entry.fetch(request); if (rendered.status === 404) { // @ts-expect-error diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 1f5805a39264..538768d5a819 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -695,8 +695,8 @@ function kit({ svelte_config }) { return dedent` import ${s(posixify(server_instrumentation))}; - const { respond } = await import(${s(sveltekit_dev_server_entry)}); - export { respond }; + const { fetch } = await import(${s(sveltekit_dev_server_entry)}); + export { fetch }; import.meta.hot?.accept(); `; From dd97b188113bb9df42fe6a7bea1789f32dcbc530 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Thu, 23 Jul 2026 11:46:31 +0800 Subject: [PATCH 016/109] remove Vite fs prefix just before reading --- packages/kit/src/runtime/app/server/index.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/kit/src/runtime/app/server/index.js b/packages/kit/src/runtime/app/server/index.js index 7af31db69b5f..adc119f758dd 100644 --- a/packages/kit/src/runtime/app/server/index.js +++ b/packages/kit/src/runtime/app/server/index.js @@ -26,10 +26,6 @@ export function read(asset) { ); } - if (__SVELTEKIT_DEV__) { - asset = from_fs(asset); - } - // handle inline assets internally const match = /^data:([^;,]+)?(;base64)?,/.exec(asset); if (match) { @@ -68,7 +64,7 @@ export function read(asset) { const length = manifest._.server_assets[file]; const type = manifest.mimeTypes[file.slice(file.lastIndexOf('.'))]; - return new Response(read_implementation(file), { + return new Response(read_implementation(__SVELTEKIT_DEV__ ? from_fs(file) : file), { headers: { 'Content-Length': '' + length, 'Content-Type': type From 35ac374584c20af42c7d0cc33cfaa2001dbf8ad7 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Thu, 23 Jul 2026 15:19:43 +0800 Subject: [PATCH 017/109] avoid collision --- packages/kit/src/exports/vite/dev/index.js | 6 ++---- .../kit/src/exports/vite/dev/ssr_manifest.js | 2 +- packages/kit/src/exports/vite/index.js | 21 ++++++++++--------- packages/kit/src/exports/vite/module_ids.js | 8 +++++-- packages/kit/src/types/ambient-private.d.ts | 2 +- 5 files changed, 21 insertions(+), 18 deletions(-) diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 094872aa71ae..85b0686c0a18 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -22,7 +22,7 @@ import { } from '../utils.js'; import { escape_html } from '../../../utils/escape.js'; import { fix_stack_trace } from './sourcemaps.js'; -import { sveltekit_dev_internal_server_entry, sveltekit_manifest_data } from '../module_ids.js'; +import { sveltekit_dev_server, sveltekit_manifest_data } from '../module_ids.js'; /** * @param {import('vite').ViteDevServer} vite @@ -259,9 +259,7 @@ export function dev(vite, vite_config, svelte_config, root, set_manifest_data) { } /** @type {{ fetch(request: Request): Promise }} */ - const server_entry = await vite.environments.ssr.runner.import( - sveltekit_dev_internal_server_entry - ); + const server_entry = await vite.environments.ssr.runner.import(sveltekit_dev_server); if (req.socket.remoteAddress) { request.headers.set('x-sveltekit-remote-address', req.socket.remoteAddress); diff --git a/packages/kit/src/exports/vite/dev/ssr_manifest.js b/packages/kit/src/exports/vite/dev/ssr_manifest.js index 1e26123c889a..a9bec127e402 100644 --- a/packages/kit/src/exports/vite/dev/ssr_manifest.js +++ b/packages/kit/src/exports/vite/dev/ssr_manifest.js @@ -2,7 +2,7 @@ /** @import { EnvironmentModuleNode } from 'vite' */ import fs from 'node:fs'; import { isCSSRequest } from 'vite'; -import { manifest_data, mime_types } from '__sveltekit/manifest-data'; +import { manifest_data, mime_types } from '__sveltekit/dev-manifest-data'; import { get_dev_server, get_remotes } from './context.js'; import { resolve } from './utils.js'; import { compact } from '../../../utils/array.js'; diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 538768d5a819..416d573ad9f9 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -61,9 +61,10 @@ import { sveltekit_manifest_data, sveltekit_env_public_client, sveltekit_env_public_server, - sveltekit_traced, - sveltekit_dev_internal_server_entry, - sveltekit_dev_server_entry + sveltekit_dev_traced, + sveltekit_dev_server_entry, + sveltekit_dev_manifest_data, + sveltekit_dev_server } from './module_ids.js'; import { import_peer } from '../../utils/import.js'; import { compact } from '../../utils/array.js'; @@ -617,7 +618,7 @@ function kit({ svelte_config }) { resolveId: { filter: { id: [ - exactRegex(sveltekit_dev_internal_server_entry), + exactRegex(sveltekit_dev_server), exactRegex('sveltekit:server-manifest'), exactRegex('sveltekit:server'), exactRegex('sveltekit:env'), @@ -625,9 +626,9 @@ function kit({ svelte_config }) { ] }, handler(id) { - if (id === sveltekit_dev_internal_server_entry) { + if (id === sveltekit_dev_server) { return server_instrumentation - ? sveltekit_traced + ? sveltekit_dev_traced : this.resolve(sveltekit_dev_server_entry); } @@ -652,13 +653,13 @@ function kit({ svelte_config }) { filter: { id: [ exactRegex('sveltekit:env'), - exactRegex(sveltekit_manifest_data), - exactRegex(sveltekit_traced) + exactRegex(sveltekit_dev_manifest_data), + exactRegex(sveltekit_dev_traced) ] }, handler(id) { switch (id) { - case sveltekit_manifest_data: { + case sveltekit_dev_manifest_data: { return dedent` import { load_and_validate_params } from '__SVELTEKIT__/utils/params.js'; import { to_fs } from '__SVELTEKIT__/utils/vite.js'; @@ -687,7 +688,7 @@ function kit({ svelte_config }) { `; } - case sveltekit_traced: { + case sveltekit_dev_traced: { if (!server_instrumentation) { throw new Error('Server instrumentation file not found. This should never happen'); } diff --git a/packages/kit/src/exports/vite/module_ids.js b/packages/kit/src/exports/vite/module_ids.js index 8a5b2ff48996..d8c3d344423e 100644 --- a/packages/kit/src/exports/vite/module_ids.js +++ b/packages/kit/src/exports/vite/module_ids.js @@ -9,11 +9,15 @@ export const sveltekit_env_service_worker = '\0virtual:__sveltekit/env/service-w export const service_worker = '\0virtual:service-worker'; +/** Used to construct `$app/manifest` */ export const sveltekit_manifest_data = '\0virtual:__sveltekit/manifest-data'; -export const sveltekit_traced = '\0virtual:__sveltekit/traced'; -export const sveltekit_dev_internal_server_entry = '__sveltekit/dev-server-entry'; +export const sveltekit_dev_server = '__sveltekit/dev-server'; +/** A facade that initialises tracing during development */ +export const sveltekit_dev_traced = '\0virtual:__sveltekit/traced'; export const sveltekit_dev_server_entry = 'sveltekit:server-entry'; +/** Used to construct the SSR manifest from within an environment during development */ +export const sveltekit_dev_manifest_data = '\0virtual:__sveltekit/dev-manifest-data'; export const app_server = posixify( fileURLToPath(new URL('../../runtime/app/server/index.js', import.meta.url)) diff --git a/packages/kit/src/types/ambient-private.d.ts b/packages/kit/src/types/ambient-private.d.ts index eebc8ef43588..92f034d78288 100644 --- a/packages/kit/src/types/ambient-private.d.ts +++ b/packages/kit/src/types/ambient-private.d.ts @@ -22,7 +22,7 @@ declare module '__sveltekit/server' { } /** Used to construct the SSR manifest from within an environment during development */ -declare module '__sveltekit/manifest-data' { +declare module '__sveltekit/dev-manifest-data' { import { ManifestData } from 'types'; export const env: Record; From 7391962105b059e49b7472fbca2ca35a2300709e Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Thu, 23 Jul 2026 19:25:48 +0800 Subject: [PATCH 018/109] Update 99-writing-adapters.md --- documentation/docs/25-build-and-deploy/99-writing-adapters.md | 2 +- 1 file changed, 1 insertion(+), 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 466129f0cba2..4a5aa417ea64 100644 --- a/documentation/docs/25-build-and-deploy/99-writing-adapters.md +++ b/documentation/docs/25-build-and-deploy/99-writing-adapters.md @@ -98,7 +98,7 @@ You can also define a custom server entry during development by adding a Vite pl id: /^sveltekit:server-entry$/ }, handler(id) { - return import.meta.resolve('./path-to-your-server.js'); + return this.resolve(import.meta.resolve('./path-to-your-server.js')); } } } From 0bd98133c4cff0d0b3b3eeb9363ac38ea34c4dbc Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Thu, 23 Jul 2026 23:01:00 +0800 Subject: [PATCH 019/109] invalidate dev manifest data --- packages/kit/src/exports/vite/dev/index.js | 3 ++- packages/kit/src/exports/vite/index.js | 1 + packages/kit/src/exports/vite/module_ids.js | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 85b0686c0a18..7facdd85cda7 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -22,7 +22,7 @@ import { } from '../utils.js'; import { escape_html } from '../../../utils/escape.js'; import { fix_stack_trace } from './sourcemaps.js'; -import { sveltekit_dev_server, sveltekit_manifest_data } from '../module_ids.js'; +import { sveltekit_dev_manifest_data, sveltekit_dev_server, sveltekit_manifest_data } from '../module_ids.js'; /** * @param {import('vite').ViteDevServer} vite @@ -111,6 +111,7 @@ export function dev(vite, vite_config, svelte_config, root, set_manifest_data) { if (timeout || !/\+(page|layout|server).*$/.test(file)) return; sync.update(svelte_config, manifest_data, file, root); invalidate_module(vite, sveltekit_manifest_data); + invalidate_module(vite, sveltekit_dev_manifest_data); }); const { appTemplate, errorTemplate, serviceWorker, hooks } = svelte_config.kit.files; diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 416d573ad9f9..53c5123f77c7 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -1729,6 +1729,7 @@ function kit({ svelte_config }) { return dev(server, vite_config, svelte_config, root, (data) => { manifest_data = data; invalidate_module(server, sveltekit_manifest_data); + invalidate_module(server, sveltekit_dev_manifest_data); context.set_svelte_config(svelte_config); context.set_remotes(remotes); }); diff --git a/packages/kit/src/exports/vite/module_ids.js b/packages/kit/src/exports/vite/module_ids.js index d8c3d344423e..3b7fb9758e80 100644 --- a/packages/kit/src/exports/vite/module_ids.js +++ b/packages/kit/src/exports/vite/module_ids.js @@ -12,7 +12,7 @@ export const service_worker = '\0virtual:service-worker'; /** Used to construct `$app/manifest` */ export const sveltekit_manifest_data = '\0virtual:__sveltekit/manifest-data'; -export const sveltekit_dev_server = '__sveltekit/dev-server'; +export const sveltekit_dev_server = '__sveltekit/dev-server.js'; /** A facade that initialises tracing during development */ export const sveltekit_dev_traced = '\0virtual:__sveltekit/traced'; export const sveltekit_dev_server_entry = 'sveltekit:server-entry'; From 42eac098e42006a52b82102715d2da21206fd113 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Thu, 23 Jul 2026 23:34:46 +0800 Subject: [PATCH 020/109] format --- packages/kit/src/exports/vite/dev/index.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 7facdd85cda7..441b0f330012 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -22,7 +22,11 @@ import { } from '../utils.js'; import { escape_html } from '../../../utils/escape.js'; import { fix_stack_trace } from './sourcemaps.js'; -import { sveltekit_dev_manifest_data, sveltekit_dev_server, sveltekit_manifest_data } from '../module_ids.js'; +import { + sveltekit_dev_manifest_data, + sveltekit_dev_server, + sveltekit_manifest_data +} from '../module_ids.js'; /** * @param {import('vite').ViteDevServer} vite From 9b751f6948417b60737c8d3873d382327eb78329 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Thu, 23 Jul 2026 23:42:59 +0800 Subject: [PATCH 021/109] windows --- packages/kit/src/exports/vite/index.js | 4 ++-- packages/kit/src/utils/params.js | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 53c5123f77c7..23b8c85f5111 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -680,8 +680,8 @@ function kit({ svelte_config }) { await load_and_validate_params({ routes: manifest_data.routes, params_path: manifest_data.params, - root: to_fs(__SVELTEKIT_ROOT__), - load: (file) => loud_ssr_load_module(file) + root: __SVELTEKIT_ROOT__, + load: (file) => loud_ssr_load_module(to_fs(file)) }); export const mime_types = ${s(get_mime_lookup(manifest_data))}; diff --git a/packages/kit/src/utils/params.js b/packages/kit/src/utils/params.js index b60aa0a15450..2b92ead2d167 100644 --- a/packages/kit/src/utils/params.js +++ b/packages/kit/src/utils/params.js @@ -1,4 +1,5 @@ import path from 'node:path'; +import { posixify } from './os.js'; /** * @param {import('types').RouteData[]} routes @@ -48,7 +49,7 @@ export async function load_and_validate_params({ routes, params_path, root, load throw new Error(`No matcher found for parameter '${names.values().next().value}'`); } - const file = path.resolve(root, params_path); + const file = posixify(path.resolve(root, params_path)); const module = await load(file); if (!module.params || typeof module.params !== 'object') { From b403f30bb40b1823d5c57a297f353f5449961303 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Thu, 23 Jul 2026 23:53:21 +0800 Subject: [PATCH 022/109] revert --- packages/kit/src/exports/vite/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 23b8c85f5111..1965d64e3d05 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -548,7 +548,7 @@ function kit({ svelte_config }) { __SVELTEKIT_HAS_UNIVERSAL_LOAD__: 'true', __SVELTEKIT_PATHS_ASSETS__: kit.paths.assets ? s(SVELTE_KIT_ASSETS) - : s(kit.paths.assets), + : s(kit.paths.base), __SVELTEKIT_FILES_ASSETS__: s(posixify(kit.files.assets)) }; From f2f90c008cb96ad6ace0a5397256bd938b63a16d Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Fri, 24 Jul 2026 00:06:18 +0800 Subject: [PATCH 023/109] format --- packages/kit/src/exports/vite/index.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 1965d64e3d05..9652ce2bd2d5 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -546,9 +546,7 @@ function kit({ svelte_config }) { __SVELTEKIT_PAYLOAD__: kit_global, // only relevant when bundleStrategy !== 'split' __SVELTEKIT_HAS_SERVER_LOAD__: 'true', __SVELTEKIT_HAS_UNIVERSAL_LOAD__: 'true', - __SVELTEKIT_PATHS_ASSETS__: kit.paths.assets - ? s(SVELTE_KIT_ASSETS) - : s(kit.paths.base), + __SVELTEKIT_PATHS_ASSETS__: kit.paths.assets ? s(SVELTE_KIT_ASSETS) : s(kit.paths.base), __SVELTEKIT_FILES_ASSETS__: s(posixify(kit.files.assets)) }; From 14ee428cea14594eab9215bbadf83be93fd3cafb Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Fri, 24 Jul 2026 00:24:16 +0800 Subject: [PATCH 024/109] this doesn't belong here --- packages/kit/src/exports/vite/dev/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 441b0f330012..7bc7011cfda4 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -114,7 +114,6 @@ export function dev(vite, vite_config, svelte_config, root, set_manifest_data) { // Unless it's a file where the trailing slash page option might have changed if (timeout || !/\+(page|layout|server).*$/.test(file)) return; sync.update(svelte_config, manifest_data, file, root); - invalidate_module(vite, sveltekit_manifest_data); invalidate_module(vite, sveltekit_dev_manifest_data); }); From a4f48bc585ec3eff9d1a8dca81c007f043ea196d Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Fri, 24 Jul 2026 00:26:17 +0800 Subject: [PATCH 025/109] format --- packages/kit/src/exports/vite/dev/index.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 7bc7011cfda4..1bc750bf9120 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -22,11 +22,7 @@ import { } from '../utils.js'; import { escape_html } from '../../../utils/escape.js'; import { fix_stack_trace } from './sourcemaps.js'; -import { - sveltekit_dev_manifest_data, - sveltekit_dev_server, - sveltekit_manifest_data -} from '../module_ids.js'; +import { sveltekit_dev_manifest_data, sveltekit_dev_server } from '../module_ids.js'; /** * @param {import('vite').ViteDevServer} vite From 483fff63f1f2b3f41e47828e082595de45878606 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Fri, 24 Jul 2026 00:32:12 +0800 Subject: [PATCH 026/109] base path in manifest is not needed yet --- packages/kit/src/exports/public.d.ts | 1 - packages/kit/src/exports/vite/dev/ssr_manifest.js | 1 - packages/kit/src/exports/vite/index.js | 1 - packages/kit/src/types/internal.d.ts | 1 - 4 files changed, 4 deletions(-) diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index bcfd66cffc12..c1f28309fa6f 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -1756,7 +1756,6 @@ export interface SSRManifest { appPath: string; /** Static files from `config.files.assets` and the service worker (if any). */ assets: Set; - base: string; mimeTypes: Record; /** @internal private fields */ diff --git a/packages/kit/src/exports/vite/dev/ssr_manifest.js b/packages/kit/src/exports/vite/dev/ssr_manifest.js index a9bec127e402..b6ff21def5be 100644 --- a/packages/kit/src/exports/vite/dev/ssr_manifest.js +++ b/packages/kit/src/exports/vite/dev/ssr_manifest.js @@ -17,7 +17,6 @@ export const manifest = { appDir: __SVELTEKIT_APP_DIR__, appPath: `${__SVELTEKIT_PATHS_BASE__}/${__SVELTEKIT_APP_DIR__}`, assets: new Set(manifest_data.assets.map((asset) => asset.file)), - base: __SVELTEKIT_PATHS_BASE__, mimeTypes: mime_types, _: { client: { diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 9652ce2bd2d5..3f38ed431db5 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -1806,7 +1806,6 @@ function kit({ svelte_config }) { const build_data = { app_dir: kit.appDir, app_path: `${kit.paths.base.slice(1)}${kit.paths.base ? '/' : ''}${kit.appDir}`, - base: kit.paths.base, manifest_data, out_dir: out, service_worker: service_worker_entry_file ? 'service-worker.js' : null, // TODO make file configurable? diff --git a/packages/kit/src/types/internal.d.ts b/packages/kit/src/types/internal.d.ts index a526540bb4cf..8a2bb1bf211e 100644 --- a/packages/kit/src/types/internal.d.ts +++ b/packages/kit/src/types/internal.d.ts @@ -69,7 +69,6 @@ export interface AssetDependencies { export interface BuildData { app_dir: string; app_path: string; - base: string; manifest_data: ManifestData; out_dir: string; service_worker: string | null; From 90ecc9978fb57391386795cc5fc90a70ad771cb1 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Fri, 24 Jul 2026 01:09:32 +0800 Subject: [PATCH 027/109] replace ssrLoadModule --- packages/kit/src/core/env.js | 8 +- packages/kit/src/exports/vite/dev/index.js | 163 +++++++++++---------- packages/kit/src/exports/vite/index.js | 24 ++- 3 files changed, 103 insertions(+), 92 deletions(-) diff --git a/packages/kit/src/core/env.js b/packages/kit/src/core/env.js index dbb467e78eba..8f95b9b296f4 100644 --- a/packages/kit/src/core/env.js +++ b/packages/kit/src/core/env.js @@ -52,12 +52,16 @@ export async function load_explicit_env(kit, file, root, mode) { /** @type {Record>} */ let variables; + if (!vite.isRunnableDevEnvironment(server.environments.ssr)) { + throw new Error('The configured Vite SSR environment must be a RunnableDevEnvironment'); + } + /** @type {import('../runtime/app/env/internal.js')} */ ( - await server.ssrLoadModule(`${runtime_directory}/app/env/internal.js`) + await server.environments.ssr.runner.import(`${runtime_directory}/app/env/internal.js`) ).set_building(); try { - ({ variables } = await server.ssrLoadModule(file)); + ({ variables } = await server.environments.ssr.runner.import(file)); if (!variables || typeof variables !== 'object') { throw new Error(`${file} must export a variables object`); diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 7e3b161c7505..0ddcbb01f15a 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -7,7 +7,7 @@ import { URL } from 'node:url'; import { AsyncLocalStorage } from 'node:async_hooks'; import { styleText } from 'node:util'; import sirv from 'sirv'; -import { isCSSRequest, loadEnv, buildErrorMessage } from 'vite'; +import { isCSSRequest, loadEnv, buildErrorMessage, isRunnableDevEnvironment } from 'vite'; import { createReadableStream, getRequest, setResponse } from '../../../exports/node/index.js'; import { coalesce_to_error } from '../../../utils/error.js'; import { resolve_entry } from '../../../utils/filesystem.js'; @@ -79,10 +79,19 @@ export async function dev(vite, vite_config, svelte_config, get_remotes, root, s /** @type {Error | null} */ let manifest_error = null; - /** @param {string} url */ + if (!isRunnableDevEnvironment(vite.environments.ssr)) { + throw new Error('The configured Vite SSR environment must be a RunnableDevEnvironment'); + } + + const { runner } = vite.environments.ssr; + + /** + * @param {string} url + * @returns {Promise>} + */ async function loud_ssr_load_module(url) { try { - return await vite.ssrLoadModule(url, { fixStacktrace: true }); + return await runner.import(url); } catch (/** @type {any} */ err) { const msg = buildErrorMessage(err, [ styleText('red', `Internal server error: ${err.message}`) @@ -93,10 +102,10 @@ export async function dev(vite, vite_config, svelte_config, get_remotes, root, s } // TODO this is inadequate — it doesn't reliably show the overlay on every page load, - // and when it does appear it may immediately vanish. `vite.ws.send` broadcasts + // and when it does appear it may immediately vanish. `vite.hot.send` broadcasts // to all connected clients, even ones that are unaffected by the error. // we need a more considered approach - vite.ws.send({ + vite.hot.send({ type: 'error', err: /** @type {import('vite').ErrorPayload['err']} */ ({ ...err, @@ -117,7 +126,7 @@ export async function dev(vite, vite_config, svelte_config, get_remotes, root, s const module = await loud_ssr_load_module(url); - const module_node = await vite.moduleGraph.getModuleByUrl(url); + const module_node = await vite.environments.ssr.moduleGraph.getModuleByUrl(url); if (!module_node) throw new Error(`Could not find node for ${url}`); return { module, module_node, url }; @@ -137,13 +146,13 @@ export async function dev(vite, vite_config, svelte_config, get_remotes, root, s if (manifest_error) { manifest_error = null; - vite.ws.send({ type: 'full-reload' }); + vite.hot.send({ type: 'full-reload' }); } } catch (error) { manifest_error = /** @type {Error} */ (error); console.error(styleText(['bold', 'red'], manifest_error.message)); - vite.ws.send({ + vite.hot.send({ type: 'error', err: { message: manifest_error.message ?? 'Invalid routes', @@ -216,7 +225,7 @@ export async function dev(vite, vite_config, svelte_config, get_remotes, root, s result.stylesheets = []; result.fonts = []; - /** @type {import('vite').ModuleNode[]} */ + /** @type {import('vite').EnvironmentModuleNode[]} */ const module_nodes = []; if (node.component) { @@ -250,7 +259,7 @@ export async function dev(vite, vite_config, svelte_config, get_remotes, root, s // 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} */ + /** @type {Set} */ const deps = new Set(); for (const module_node of module_nodes) { @@ -266,7 +275,7 @@ export async function dev(vite, vite_config, svelte_config, get_remotes, root, s ? dep.url.replace('?', '?inline&') : dep.url + '?inline'; try { - const mod = await vite.ssrLoadModule(inlineCssUrl); + const mod = await runner.import(inlineCssUrl); styles[dep.url] = mod.default; } catch { // this can happen with dynamically imported modules, I think @@ -287,7 +296,7 @@ export async function dev(vite, vite_config, svelte_config, get_remotes, root, s return Object.fromEntries( get_remotes().map((remote) => [ remote.hash, - () => vite.ssrLoadModule(remote.file).then((module) => ({ default: module })) + () => runner.import(remote.file).then((module) => ({ default: module })) ]) ); }, @@ -316,7 +325,7 @@ export async function dev(vite, vite_config, svelte_config, get_remotes, root, s if (!manifest_data.params) return {}; const url = path.resolve(root, manifest_data.params); - const module = await vite.ssrLoadModule(url, { fixStacktrace: true }); + const module = await runner.import(url); if (!module.params) { throw new Error( @@ -336,48 +345,37 @@ export async function dev(vite, vite_config, svelte_config, get_remotes, root, s return; } - try { - vite.ssrFixStacktrace(error); - } catch { - // ssrFixStacktrace can fail on StackBlitz web containers and we don't know why - // by ignoring it the line numbers are wrong, but at least we can show the error - } + let end = 0; + + error.stack = error.stack + .replaceAll('\0', '') // remove null bytes from e.g. virtual module IDs, or the response will fail + .split('\n') + .map((line, i) => { + const match = /^ {4}at (?:[^ ]+ \((.+)\)|(.+))$/.exec(line); + if (!match) { + end = i + 1; + return line; + } - if (error.stack) { - let end = 0; + const loc = match[1] ?? match[2]; + const file = loc.replace(/:\d+:\d+$/, ''); - error.stack = error.stack - .replaceAll('\0', '') // remove null bytes from e.g. virtual module IDs, or the response will fail - .split('\n') - .map((line, i) => { - const match = /^ {4}at (?:[^ ]+ \((.+)\)|(.+))$/.exec(line); - if (!match) { + if (fs.existsSync(file)) { + if (!file.includes('node_modules') && !file.includes(SRC_ROOT)) { end = i + 1; - return line; } - const loc = match[1] ?? match[2]; - const file = loc.replace(/:\d+:\d+$/, ''); - - if (fs.existsSync(file)) { - if (!file.includes('node_modules') && !file.includes(SRC_ROOT)) { - end = i + 1; - } - - return line.replace(file, path.relative(process.cwd(), file)); - } + return line.replace(file, path.relative(process.cwd(), file)); + } - return line; - }) - .slice(0, end) - .join('\n'); + return line; + }) + .slice(0, end) + .join('\n'); - return error.stack; - } + return error.stack; } - await update_manifest(); - const params_file = resolve_entry(svelte_config.kit.files.params); /** @@ -430,7 +428,7 @@ export async function dev(vite, vite_config, svelte_config, get_remotes, root, s if (appTemplate !== 'index.html') { vite.watcher.on('change', (file) => { if (file === appTemplate) { - vite.ws.send({ type: 'full-reload' }); + vite.hot.send({ type: 'full-reload' }); } }); } @@ -480,6 +478,9 @@ export async function dev(vite, vite_config, svelte_config, get_remotes, root, s const env = loadEnv(vite_config.mode, svelte_config.kit.env.dir, ''); const emulator = await svelte_config.kit.adapter?.emulate?.(); + /** @type {boolean | void} */ + let manifest_created; + return () => { const serve_static_middleware = vite.middlewares.stack.find( (middleware) => @@ -491,6 +492,14 @@ export async function dev(vite, vite_config, svelte_config, get_remotes, root, s remove_static_middlewares(vite.middlewares); vite.middlewares.use(async (req, res) => { + // Vite throws a Cannot read properties of undefined (reading 'wrapDynamicImport') + // if you try to run ssr.runner.import before the server has started so + // we do it inside here to avoid that + if (!manifest_created) { + await update_manifest(); + manifest_created = true; + } + // Vite's base middleware strips out the base path. Restore it const original_url = req.url; req.url = req.originalUrl; @@ -544,22 +553,20 @@ export async function dev(vite, vite_config, svelte_config, get_remotes, root, s ); if (resolved_instrumentation) { - await vite.ssrLoadModule(resolved_instrumentation); + await runner.import(resolved_instrumentation); } // we have to import `Server` before calling `set_assets` const { Server } = /** @type {import('types').ServerModule} */ ( - await vite.ssrLoadModule(`${get_runtime_base(root)}/server/index.js`, { - fixStacktrace: true - }) + await runner.import(`${get_runtime_base(root)}/server/index.js`) ); - const { set_fix_stack_trace } = await vite.ssrLoadModule( + const { set_fix_stack_trace } = await runner.import( `${get_runtime_base(root)}/server/internal.js` ); set_fix_stack_trace(fix_stack_trace); - const { set_assets } = await vite.ssrLoadModule('$app/paths/internal/server'); + const { set_assets } = await runner.import('$app/paths/internal/server'); set_assets(assets); const server = new Server(manifest); @@ -652,16 +659,16 @@ function remove_static_middlewares(server) { /** * @param {import('vite').ViteDevServer} vite - * @param {import('vite').ModuleNode | import('vite').EnvironmentModuleNode} node - * @param {Set} deps + * @param {import('vite').EnvironmentModuleNode} node + * @param {Set} deps */ async function find_deps(vite, node, deps) { - // since `ssrTransformResult.deps` contains URLs instead of `ModuleNode`s, this process is asynchronous. + // since `transformResult.deps` contains URLs instead of `ModuleNode`s, this process is asynchronous. // instead of using `await`, we resolve all branches in parallel. /** @type {Promise[]} */ const branches = []; - /** @param {import('vite').ModuleNode | import('vite').EnvironmentModuleNode} node */ + /** @param {import('vite').EnvironmentModuleNode} node */ async function add(node) { if (!deps.has(node)) { deps.add(node); @@ -671,23 +678,20 @@ async function find_deps(vite, node, deps) { /** @param {string} url */ async function add_by_url(url) { - const node = await get_server_module_by_url(vite, url); + const node = await vite.environments.ssr.moduleGraph.getModuleByUrl(url); if (node) { await add(node); } } - const transform_result = - /** @type {import('vite').ModuleNode} */ (node).ssrTransformResult || node.transformResult; - - if (transform_result) { - if (transform_result.deps) { - transform_result.deps.forEach((url) => branches.push(add_by_url(url))); + if (node.transformResult) { + if (node.transformResult.deps) { + node.transformResult.deps.forEach((url) => branches.push(add_by_url(url))); } - if (transform_result.dynamicDeps) { - transform_result.dynamicDeps.forEach((url) => branches.push(add_by_url(url))); + if (node.transformResult.dynamicDeps) { + node.transformResult.dynamicDeps.forEach((url) => branches.push(add_by_url(url))); } } else { node.importedModules.forEach((node) => branches.push(add(node))); @@ -696,16 +700,6 @@ async function find_deps(vite, node, deps) { await Promise.all(branches); } -/** - * @param {import('vite').ViteDevServer} vite - * @param {string} url - */ -function get_server_module_by_url(vite, url) { - return vite.environments - ? vite.environments.ssr.moduleGraph.getModuleByUrl(url) - : vite.moduleGraph.getModuleByUrl(url, true); -} - /** * Determine if a file is being requested with the correct case, * to ensure consistent behaviour between dev and prod and across @@ -726,3 +720,20 @@ function has_correct_case(file, assets) { return false; } + +/** + * Invalidates a module in all environments. + * @param {import('vite').ViteDevServer} server + * @param {string} id + * @returns {void} + */ +export function invalidate_module(server, id) { + for (const environment in server.environments) { + // Invalidate the manifest data module so it reloads with new routes/files + const module = server.environments[environment].moduleGraph.getModuleById(id); + if (module) { + server.environments[environment].moduleGraph.invalidateModule(module); + void server.environments[environment].reloadModule(module); + } + } +} diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 726833c3cdb0..a2edb1490b5b 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -30,7 +30,7 @@ import { runtime_directory, logger } from '../../core/utils.js'; import { generate_manifest } from '../../core/generate_manifest/index.js'; import { build_server_nodes } from './build/build_server.js'; import { find_deps, resolve_symlinks } from './build/utils.js'; -import { dev } from './dev/index.js'; +import { dev, invalidate_module } from './dev/index.js'; import { preview } from './preview/index.js'; import { error_for_missing_config, @@ -620,14 +620,10 @@ function kit({ svelte_config }) { ); for (const id of [sveltekit_env, sveltekit_env_public_client]) { - const module = server.moduleGraph.getModuleById(id); - - if (module) { - server.moduleGraph.invalidateModule(module); - } + invalidate_module(server, id); } - server.ws.send({ type: 'full-reload' }); + server.hot.send({ type: 'full-reload' }); } }); }, @@ -931,7 +927,7 @@ function kit({ svelte_config }) { if (this.environment.config.consumer !== 'client') { // we need to add an `await Promise.resolve()` because if the user imports this function // on the client AND in a load function when loading the client module we will trigger - // an ssrLoadModule during dev. During a link preload, the module can be mistakenly + // an import during dev. During a link preload, the module can be mistakenly // loaded and transformed twice and the first time all its exports would be undefined // triggering a dev server error. By adding a microtask we ensure that the module is fully loaded const ms = new MagicString(code); @@ -982,7 +978,11 @@ function kit({ svelte_config }) { // being called again with `opts.ssr === true` if the module isn't // already loaded) so we can determine what it exports if (dev_server) { - const module = await dev_server.ssrLoadModule(id); + if (!vite.isRunnableDevEnvironment(dev_server.environments.ssr)) { + throw new Error('The configured Vite SSR environment must be a RunnableDevEnvironment'); + } + + const module = await dev_server.environments.ssr.runner.import(id); for (const [name, value] of Object.entries(module)) { const type = value?.__?.type; @@ -1597,11 +1597,7 @@ function kit({ svelte_config }) { (data) => { manifest_data = data; // Invalidate the manifest data module so it reloads with new routes/files - const module = server.moduleGraph.getModuleById(sveltekit_manifest_data); - if (module) { - server.moduleGraph.invalidateModule(module); - void server.reloadModule(module); - } + invalidate_module(server, sveltekit_manifest_data); } ); }, From a5beefdb98fe726935f5aa182afb585906d8d537 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Fri, 24 Jul 2026 01:14:39 +0800 Subject: [PATCH 028/109] remove comment --- packages/kit/src/exports/vite/dev/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 0ddcbb01f15a..55a56645402f 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -729,7 +729,6 @@ function has_correct_case(file, assets) { */ export function invalidate_module(server, id) { for (const environment in server.environments) { - // Invalidate the manifest data module so it reloads with new routes/files const module = server.environments[environment].moduleGraph.getModuleById(id); if (module) { server.environments[environment].moduleGraph.invalidateModule(module); From f203fc67e07ab56fd53913353db41346d27e3640 Mon Sep 17 00:00:00 2001 From: "vercel[bot]" <35613825+vercel[bot]@users.noreply.github.com> Date: Thu, 23 Jul 2026 17:38:03 +0000 Subject: [PATCH 029/109] Fix: The `change` file watcher dereferences `manifest_data.nodes` before `manifest_data` is assigned, crashing with a TypeError if a route file is edited before the dev server serves its first HTTP request. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit fixes the issue reported at packages/kit/src/exports/vite/dev/index.js:416 ## Bug In `packages/kit/src/exports/vite/dev/index.js`, commit `90ecc99` ("replace ssrLoadModule") removed the eager `await update_manifest()` that previously ran during `dev()` setup, and instead runs it lazily on the first HTTP request: ```js vite.middlewares.use(async (req, res) => { if (!manifest_created) { await update_manifest(); // this assigns `manifest_data` manifest_created = true; } ... ``` `update_manifest()` is the only thing that assigns the module-scoped `let manifest_data;` (via `({ manifest_data } = sync.create(...))`). However, the file watchers are registered during setup — *before* the middleware installer is returned and therefore before any request is served. The `change` watcher dereferences `manifest_data`: ```js watch('change', (file) => { if (timeout || !/+(page|layout|server).*$/.test(file)) return; sync.update(svelte_config, manifest_data, file, root); }); ``` and `sync.update` (`packages/kit/src/core/sync/sync.js`) immediately iterates it: ```js export function update(config, manifest_data, file, root) { const node_analyser = create_node_analyser(root); for (const node of manifest_data.nodes) { ... } // TypeError if undefined ... } ``` ### Concrete trigger 1. Start the dev server. 2. Before making any HTTP request (e.g. before opening the browser), edit a `+page.svelte`, `+layout.svelte`, or `+server.js` file. 3. The `change` watcher fires, `timeout` is `null`, the regex matches, so `sync.update(svelte_config, undefined, file, root)` is called. 4. `sync.update` throws `TypeError: Cannot read properties of undefined (reading 'nodes')`. Prior to this PR the eager `await update_manifest()` guaranteed `manifest_data` was assigned before any watcher could fire, so this window did not exist. ## Fix Guard the `change` handler with an early return when `manifest_data` is still undefined. In that case there is nothing to incrementally update — the manifest will be created from scratch (`sync.create`) on the first request via the deferred `update_manifest()` call. This preserves the intended deferral of the `runner.import`-dependent work (which is why the eager call was removed) while avoiding the crash. ```js watch('change', (file) => { if (!manifest_data) return; if (timeout || !/+(page|layout|server).*$/.test(file)) return; sync.update(svelte_config, manifest_data, file, root); }); ``` Note the `add`/`unlink` watchers call `update_manifest` (which assigns `manifest_data` itself and wraps the `runner.import` work in a try/catch), so they are not affected by this window. Co-authored-by: Vercel Co-authored-by: teemingc --- packages/kit/src/exports/vite/dev/index.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 55a56645402f..6517c72ee06b 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -414,6 +414,11 @@ export async function dev(vite, vite_config, svelte_config, get_remotes, root, s watch('add', () => debounce(update_manifest)); watch('unlink', () => debounce(update_manifest)); watch('change', (file) => { + // `manifest_data` is populated lazily on the first request (see `update_manifest` + // call in the middleware below), so it may still be undefined if a file changes + // before the dev server has served a request. In that case there's nothing to + // update — the manifest will be created from scratch on the first request. + if (!manifest_data) return; // Don't run for a single file if the whole manifest is about to get updated // Unless it's a file where the trailing slash page option might have changed if (timeout || !/\+(page|layout|server).*$/.test(file)) return; From b8bd2bd8a6be6888d1492a1aa11f6df3dadf78dc Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Fri, 24 Jul 2026 01:39:01 +0800 Subject: [PATCH 030/109] regenerate types --- packages/kit/types/index.d.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 9d4946750f87..9a184b42ee35 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -1726,7 +1726,6 @@ declare module '@sveltejs/kit' { appPath: string; /** Static files from `config.files.assets` and the service worker (if any). */ assets: Set; - base: string; mimeTypes: Record; } From b29f33976f7022c3838e8c724648a7bc4e04c80f Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Fri, 24 Jul 2026 01:49:32 +0800 Subject: [PATCH 031/109] missed this --- packages/kit/src/core/generate_manifest/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/kit/src/core/generate_manifest/index.js b/packages/kit/src/core/generate_manifest/index.js index ad9233e65527..b83aa8fef7e4 100644 --- a/packages/kit/src/core/generate_manifest/index.js +++ b/packages/kit/src/core/generate_manifest/index.js @@ -102,7 +102,6 @@ export function generate_manifest({ appDir: ${s(build_data.app_dir)}, appPath: ${s(build_data.app_path)}, assets: new Set(${s(assets)}), - base: ${s(build_data.base)}, mimeTypes: ${s(mime_types)}, _: { client: ${uneval(build_data.client)}, From 79b8d5f6847dff4f2ab7ca9bfc88e4c44486f068 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Fri, 24 Jul 2026 01:54:06 +0800 Subject: [PATCH 032/109] Update packages/kit/src/exports/vite/dev/server.js Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> --- packages/kit/src/exports/vite/dev/server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/src/exports/vite/dev/server.js b/packages/kit/src/exports/vite/dev/server.js index ea0e9b9d9c4e..a0c41309421b 100644 --- a/packages/kit/src/exports/vite/dev/server.js +++ b/packages/kit/src/exports/vite/dev/server.js @@ -26,7 +26,7 @@ const fetch = globalThis.fetch; globalThis.fetch = (info, init) => { if (typeof info === 'string' && !SCHEME.test(info)) { throw new Error( - `Cannot use relative URL (\${info}) with global fetch — use \`event.fetch\` instead: https://svelte.dev/docs/kit/web-standards#fetch-apis` + `Cannot use relative URL (${info}) with global fetch — use \`event.fetch\` instead: https://svelte.dev/docs/kit/web-standards#fetch-apis` ); } From da976b808efa509e896fbcde64267479cdcde6ac Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Fri, 24 Jul 2026 01:57:50 +0800 Subject: [PATCH 033/109] add again --- packages/kit/src/exports/vite/dev/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index bb57a6ddd2ef..12af786f780a 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -327,6 +327,7 @@ function has_correct_case(file, assets) { } /** + * Invalidates a module in all environments. * @param {ViteDevServer} server * @param {string} id * @returns {void} From fc5d32f6ae0fdd436b2b4adb59d028a01151edef Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Fri, 24 Jul 2026 02:01:01 +0800 Subject: [PATCH 034/109] Update packages/kit/src/exports/vite/dev/index.js Co-authored-by: Nic Polumeyv --- packages/kit/src/exports/vite/dev/index.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 6517c72ee06b..5b4a5dfd162f 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -500,10 +500,7 @@ export async function dev(vite, vite_config, svelte_config, get_remotes, root, s // Vite throws a Cannot read properties of undefined (reading 'wrapDynamicImport') // if you try to run ssr.runner.import before the server has started so // we do it inside here to avoid that - if (!manifest_created) { - await update_manifest(); - manifest_created = true; - } + await (init_manifest ??= update_manifest()); // Vite's base middleware strips out the base path. Restore it const original_url = req.url; From 9bfd90399a85fa41048e8e4f984bf81002169381 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Fri, 24 Jul 2026 02:01:16 +0800 Subject: [PATCH 035/109] Update packages/kit/src/exports/vite/dev/index.js Co-authored-by: Nic Polumeyv --- packages/kit/src/exports/vite/dev/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 5b4a5dfd162f..3b12fe62daa0 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -483,8 +483,8 @@ export async function dev(vite, vite_config, svelte_config, get_remotes, root, s const env = loadEnv(vite_config.mode, svelte_config.kit.env.dir, ''); const emulator = await svelte_config.kit.adapter?.emulate?.(); - /** @type {boolean | void} */ - let manifest_created; + /** @type {Promise | undefined} */ + let init_manifest; return () => { const serve_static_middleware = vite.middlewares.stack.find( From 28a0e7390e431710de831dda718cb702450a5cba Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 24 Jul 2026 09:41:14 -0400 Subject: [PATCH 036/109] DRY --- packages/kit/src/core/env.js | 9 ++++----- packages/kit/src/exports/vite/dev/index.js | 7 ++----- packages/kit/src/exports/vite/index.js | 7 ++----- packages/kit/src/runner.js | 13 +++++++++++++ 4 files changed, 21 insertions(+), 15 deletions(-) create mode 100644 packages/kit/src/runner.js diff --git a/packages/kit/src/core/env.js b/packages/kit/src/core/env.js index 8f95b9b296f4..12c3b437bcd0 100644 --- a/packages/kit/src/core/env.js +++ b/packages/kit/src/core/env.js @@ -10,6 +10,7 @@ import { runtime_directory } from './utils.js'; import { resolve_entry } from '../utils/filesystem.js'; import { handle_issues, validate } from '../exports/internal/env.js'; import { get_config_aliases } from '../exports/vite/utils.js'; +import { get_runner } from '../runner.js'; /** * @typedef {'public' | 'private'} EnvType @@ -52,16 +53,14 @@ export async function load_explicit_env(kit, file, root, mode) { /** @type {Record>} */ let variables; - if (!vite.isRunnableDevEnvironment(server.environments.ssr)) { - throw new Error('The configured Vite SSR environment must be a RunnableDevEnvironment'); - } + const runner = get_runner(server); /** @type {import('../runtime/app/env/internal.js')} */ ( - await server.environments.ssr.runner.import(`${runtime_directory}/app/env/internal.js`) + await runner.import(`${runtime_directory}/app/env/internal.js`) ).set_building(); try { - ({ variables } = await server.environments.ssr.runner.import(file)); + ({ variables } = await runner.import(file)); if (!variables || typeof variables !== 'object') { throw new Error(`${file} must export a variables object`); diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 3b12fe62daa0..0e3deb8cfb01 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -29,6 +29,7 @@ import { import { SCHEME } from '../../../utils/url.js'; import { check_feature } from '../../../utils/features.js'; import { escape_html } from '../../../utils/escape.js'; +import { get_runner } from '../../../runner.js'; // vite-specifc queries that we should skip handling for css urls const vite_css_query_regex = /(?:\?|&)(?:raw|url|inline)(?:&|$)/; @@ -79,11 +80,7 @@ export async function dev(vite, vite_config, svelte_config, get_remotes, root, s /** @type {Error | null} */ let manifest_error = null; - if (!isRunnableDevEnvironment(vite.environments.ssr)) { - throw new Error('The configured Vite SSR environment must be a RunnableDevEnvironment'); - } - - const { runner } = vite.environments.ssr; + const runner = get_runner(vite); /** * @param {string} url diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index a2edb1490b5b..179264fec0cb 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -65,6 +65,7 @@ import { compact } from '../../utils/array.js'; import { should_ignore, has_children } from './static_analysis/utils.js'; import { process_config, split_config, validate_config } from '../../core/config/index.js'; import { treeshake_prerendered_remotes } from './build/remote.js'; +import { get_runner } from '../../runner.js'; /** * The posix-ified root of the project based on the Vite configuration. @@ -978,11 +979,7 @@ function kit({ svelte_config }) { // being called again with `opts.ssr === true` if the module isn't // already loaded) so we can determine what it exports if (dev_server) { - if (!vite.isRunnableDevEnvironment(dev_server.environments.ssr)) { - throw new Error('The configured Vite SSR environment must be a RunnableDevEnvironment'); - } - - const module = await dev_server.environments.ssr.runner.import(id); + const module = await get_runner(dev_server).import(id); for (const [name, value] of Object.entries(module)) { const type = value?.__?.type; diff --git a/packages/kit/src/runner.js b/packages/kit/src/runner.js new file mode 100644 index 000000000000..59f27bd4398c --- /dev/null +++ b/packages/kit/src/runner.js @@ -0,0 +1,13 @@ +/** @import { ViteDevServer } from 'vite' */ +import * as vite from 'vite'; + +/** + * @param {ViteDevServer} server + */ +export function get_runner(server) { + if (!vite.isRunnableDevEnvironment(server.environments.ssr)) { + throw new Error('The configured Vite SSR environment must be a RunnableDevEnvironment'); + } + + return server.environments.ssr.runner; +} From 77883dc418786b14d3c0f431a42fc8a2b5cb7f7d Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Fri, 24 Jul 2026 21:46:30 +0800 Subject: [PATCH 037/109] Update packages/kit/src/exports/vite/dev/index.js Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> --- packages/kit/src/exports/vite/dev/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 0e3deb8cfb01..eae0fa71a655 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -7,7 +7,7 @@ import { URL } from 'node:url'; import { AsyncLocalStorage } from 'node:async_hooks'; import { styleText } from 'node:util'; import sirv from 'sirv'; -import { isCSSRequest, loadEnv, buildErrorMessage, isRunnableDevEnvironment } from 'vite'; +import { isCSSRequest, loadEnv, buildErrorMessage } from 'vite'; import { createReadableStream, getRequest, setResponse } from '../../../exports/node/index.js'; import { coalesce_to_error } from '../../../utils/error.js'; import { resolve_entry } from '../../../utils/filesystem.js'; From 391049ce49fc16cbda38c643250ce44e68e4dc96 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Sat, 25 Jul 2026 00:18:12 +0800 Subject: [PATCH 038/109] remove merge conflict markers --- packages/kit/src/exports/vite/index.js | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 9d5b95606743..c788400aa2e8 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -71,10 +71,7 @@ import { compact } from '../../utils/array.js'; import { should_ignore, has_children } from './static_analysis/utils.js'; import { process_config, split_config, validate_config } from '../../core/config/index.js'; import { treeshake_prerendered_remotes } from './build/remote.js'; -<<<<<<< HEAD import { SVELTE_KIT_ASSETS } from '../../constants.js'; -======= ->>>>>>> version-3 import { get_runner } from '../../runner.js'; /** @@ -1710,7 +1707,6 @@ function kit({ svelte_config }) { * @see https://vitejs.dev/guide/api-plugin.html#configureserver */ async configureServer(server) { -<<<<<<< HEAD /** @type {typeof import('./dev/context.js')} */ let context; @@ -1728,20 +1724,6 @@ function kit({ svelte_config }) { context.set_svelte_config(svelte_config); context.set_remotes(remotes); }); -======= - return await dev( - server, - vite_config, - svelte_config, - () => remotes, - root, - (data) => { - manifest_data = data; - // Invalidate the manifest data module so it reloads with new routes/files - invalidate_module(server, sveltekit_manifest_data); - } - ); ->>>>>>> version-3 }, /** From dbc8def9322058ab3951fc52d2fa4bd8fcf952b7 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Sat, 25 Jul 2026 01:06:36 +0800 Subject: [PATCH 039/109] remove manifest data virtual module --- packages/kit/src/exports/vite/dev/context.js | 69 ++++++++++--------- packages/kit/src/exports/vite/dev/index.js | 15 +--- .../kit/src/exports/vite/dev/ssr_manifest.js | 47 ++++++++----- packages/kit/src/exports/vite/index.js | 61 ++++------------ packages/kit/src/exports/vite/module_ids.js | 4 +- 5 files changed, 80 insertions(+), 116 deletions(-) diff --git a/packages/kit/src/exports/vite/dev/context.js b/packages/kit/src/exports/vite/dev/context.js index cf7fd9843d4f..98a8336c05ab 100644 --- a/packages/kit/src/exports/vite/dev/context.js +++ b/packages/kit/src/exports/vite/dev/context.js @@ -1,42 +1,45 @@ -/** @import { ValidatedConfig } from 'types' */ +/** @import { ManifestData, ValidatedConfig } from 'types' */ /** @import { ViteDevServer } from 'vite' */ /** * @typedef {object} Context - * @property {ViteDevServer} dev_server - * @property {Array<{ hash: string, file: string }>} remotes * @property {ValidatedConfig} svelte_config + * @property {ViteDevServer} vite + * @property {ManifestData} manifest_data + * @property {Array<{ hash: string, file: string }>} remotes */ -/** @type {Context} */ -const context = import.meta.hot?.data ?? {}; - -/** @returns {ViteDevServer} */ -export function get_dev_server() { - return context.dev_server; -} - -/** @param {ViteDevServer} server */ -export function set_dev_server(server) { - context.dev_server = server; -} - -/** @returns {ValidatedConfig} */ -export function get_svelte_config() { - return context.svelte_config; -} - -/** @param {ValidatedConfig} config */ -export function set_svelte_config(config) { - context.svelte_config = config; +class DevContext { + /** @type {Context} */ + context = import.meta.hot?.data ?? {}; + + get svelte_config() { + return this.context.svelte_config; + } + set svelte_config(config) { + this.context.svelte_config = config; + } + + get vite() { + return this.context.vite; + } + set vite(vite) { + this.context.vite = vite; + } + + get manifest_data() { + return this.context.manifest_data; + } + set manifest_data(manifest_data) { + this.context.manifest_data = manifest_data; + } + + get remotes() { + return this.context.remotes; + } + set remotes(remotes) { + this.context.remotes = remotes; + } } -/** @returns {Array<{ hash: string, file: string }>} */ -export function get_remotes() { - return context.remotes; -} - -/** @param {Array<{ hash: string, file: string }>} r */ -export function set_remotes(r) { - context.remotes = r; -} +export const dev = new DevContext(); diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 16596c5cb303..3fc3c8824ebc 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -21,7 +21,7 @@ import { } from '../utils.js'; import { escape_html } from '../../../utils/escape.js'; import { fix_stack_trace } from './sourcemaps.js'; -import { sveltekit_dev_manifest_data, sveltekit_dev_server } from '../module_ids.js'; +import { sveltekit_dev_init } from '../module_ids.js'; import { get_runner } from '../../../runner.js'; /** @@ -115,7 +115,6 @@ export function dev(vite, vite_config, svelte_config, root, set_manifest_data) { // Unless it's a file where the trailing slash page option might have changed if (timeout || !/\+(page|layout|server).*$/.test(file)) return; sync.update(svelte_config, manifest_data, file, root); - invalidate_module(vite, sveltekit_dev_manifest_data); }); const { appTemplate, errorTemplate, serviceWorker, hooks } = svelte_config.kit.files; @@ -173,8 +172,6 @@ export function dev(vite, vite_config, svelte_config, root, set_manifest_data) { next(); }); - let inited_manifest = false; - return () => { const serve_static_middleware = vite.middlewares.stack.find( (middleware) => @@ -186,14 +183,6 @@ export function dev(vite, vite_config, svelte_config, root, set_manifest_data) { remove_static_middlewares(vite.middlewares); vite.middlewares.use(async (req, res) => { - // Vite throws a Cannot read properties of undefined (reading 'wrapDynamicImport') - // if you try to run ssr.runner.import before the server has started so - // we do it inside here to avoid that - if (!inited_manifest) { - inited_manifest = true; - update_manifest(); - } - // Vite's base middleware strips out the base path. Restore it const original_url = req.url; req.url = req.originalUrl; @@ -272,7 +261,7 @@ export function dev(vite, vite_config, svelte_config, root, set_manifest_data) { const runner = get_runner(vite); /** @type {{ fetch(request: Request): Promise }} */ - const server_entry = await runner.import(sveltekit_dev_server); + const server_entry = await runner.import(sveltekit_dev_init); if (req.socket.remoteAddress) { request.headers.set('x-sveltekit-remote-address', req.socket.remoteAddress); diff --git a/packages/kit/src/exports/vite/dev/ssr_manifest.js b/packages/kit/src/exports/vite/dev/ssr_manifest.js index b6ff21def5be..8d00ec06e8a4 100644 --- a/packages/kit/src/exports/vite/dev/ssr_manifest.js +++ b/packages/kit/src/exports/vite/dev/ssr_manifest.js @@ -1,23 +1,31 @@ -/** @import { SSRManifest } from '@sveltejs/kit' */ +/** @import { ParamMatcher, SSRManifest } from '@sveltejs/kit' */ /** @import { EnvironmentModuleNode } from 'vite' */ import fs from 'node:fs'; import { isCSSRequest } from 'vite'; -import { manifest_data, mime_types } from '__sveltekit/dev-manifest-data'; -import { get_dev_server, get_remotes } from './context.js'; -import { resolve } from './utils.js'; +import { dev } from './context.js'; +import { loud_ssr_load_module, resolve } from './utils.js'; +import { get_mime_lookup } from '../../../core/utils.js'; import { compact } from '../../../utils/array.js'; +import { load_and_validate_params } from '../../../utils/params.js'; import { join } from '../../../utils/path.js'; import { from_fs, to_fs } from '../../../utils/vite.js'; // vite-specifc queries that we should skip handling for css urls const vite_css_query_regex = /(?:\?|&)(?:raw|url|inline)(?:&|$)/; +await load_and_validate_params({ + routes: dev.manifest_data.routes, + params_path: dev.manifest_data.params, + root: __SVELTEKIT_ROOT__, + load: (file) => loud_ssr_load_module(to_fs(file)) +}); + /** @type {SSRManifest} */ export const manifest = { appDir: __SVELTEKIT_APP_DIR__, appPath: `${__SVELTEKIT_PATHS_BASE__}/${__SVELTEKIT_APP_DIR__}`, - assets: new Set(manifest_data.assets.map((asset) => asset.file)), - mimeTypes: mime_types, + assets: new Set(dev.manifest_data.assets.map((asset) => asset.file)), + mimeTypes: get_mime_lookup(dev.manifest_data), _: { client: { start: `${__SVELTEKIT_RUNTIME__}/client/entry.js`, @@ -28,7 +36,7 @@ export const manifest = { uses_env_dynamic_public: true, nodes: __SVELTEKIT_CLIENT_ROUTING__ ? undefined - : manifest_data.nodes.map((node, i) => { + : dev.manifest_data.nodes.map((node, i) => { if (node.component || node.universal) { return `${__SVELTEKIT_PATHS_BASE__}${to_fs(__SVELTEKIT_OUT_DIR__)}/generated/client/nodes/${i}.js`; } @@ -38,7 +46,7 @@ export const manifest = { routes: __SVELTEKIT_CLIENT_ROUTING__ ? undefined : compact( - manifest_data.routes.map((route) => { + dev.manifest_data.routes.map((route) => { if (!route.page) return; return { @@ -46,10 +54,10 @@ export const manifest = { pattern: route.pattern, params: route.params, layouts: route.page.layouts.map((l) => - l !== undefined ? [!!manifest_data.nodes[l].server, l] : undefined + l !== undefined ? [!!dev.manifest_data.nodes[l].server, l] : undefined ), errors: route.page.errors, - leaf: [!!manifest_data.nodes[route.page.leaf].server, route.page.leaf] + leaf: [!!dev.manifest_data.nodes[route.page.leaf].server, route.page.leaf] }; }) ) @@ -61,7 +69,7 @@ export const manifest = { get: (_, /** @type {string} */ file) => fs.statSync(from_fs(file)).size } ), - nodes: manifest_data.nodes.map((node, i) => { + nodes: dev.manifest_data.nodes.map((node, i) => { return async () => { /** @type {import('types').SSRNode} */ const result = {}; @@ -143,7 +151,7 @@ export const manifest = { prerendered_routes: new Set(), get remotes() { return Object.fromEntries( - get_remotes().map((remote) => [ + dev.remotes.map((remote) => [ remote.hash, () => import(/* @vite-ignore */ join(__SVELTEKIT_ROOT__, remote.file)).then((module) => ({ @@ -153,7 +161,7 @@ export const manifest = { ); }, routes: compact( - manifest_data.routes.map((route) => { + dev.manifest_data.routes.map((route) => { if (!route.page && !route.endpoint) return null; const endpoint = route.endpoint; @@ -175,13 +183,16 @@ export const manifest = { }) ), matchers: async () => { - if (!manifest_data.params) return {}; + if (!dev.manifest_data.params) return {}; - const url = join(__SVELTEKIT_ROOT__, manifest_data.params); + const url = join(__SVELTEKIT_ROOT__, dev.manifest_data.params); + /** @type {{ module: { params?: Record> } }} */ const { module } = await resolve(url); if (!module.params) { - throw new Error(`${manifest_data.params} does not export \`params\` from \`defineParams\``); + throw new Error( + `${dev.manifest_data.params} does not export \`params\` from \`defineParams\`` + ); } return module.params; @@ -194,8 +205,6 @@ export const manifest = { * @param {Set} deps */ async function find_deps(node, deps) { - const dev_server = get_dev_server(); - // since `ssrTransformResult.deps` contains URLs instead of `ModuleNode`s, this process is asynchronous. // instead of using `await`, we resolve all branches in parallel. /** @type {Promise[]} */ @@ -211,7 +220,7 @@ async function find_deps(node, deps) { /** @param {string} url */ async function add_by_url(url) { - const node = await dev_server.environments.ssr.moduleGraph.getModuleByUrl(url); + const node = await dev.vite.environments.ssr.moduleGraph.getModuleByUrl(url); if (node) { await add(node); diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index c788400aa2e8..f1f69676cc8e 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -8,7 +8,6 @@ import path from 'node:path'; import process from 'node:process'; import { pathToFileURL } from 'node:url'; import { styleText } from 'node:util'; -import * as devalue from 'devalue'; import MagicString from 'magic-string'; import { isRunnableDevEnvironment, loadEnv } from 'vite'; import { exactRegex, prefixRegex } from 'rolldown/filter'; @@ -28,7 +27,7 @@ import { import * as sync from '../../core/sync/sync.js'; import { create_assets } from '../../core/sync/create_manifest_data/index.js'; import { load_and_validate_params } from '../../utils/params.js'; -import { runtime_directory, logger, get_runtime_base, get_mime_lookup } from '../../core/utils.js'; +import { runtime_directory, logger, get_runtime_base } from '../../core/utils.js'; import { generate_manifest } from '../../core/generate_manifest/index.js'; import { build_server_nodes } from './build/build_server.js'; import { find_deps, resolve_symlinks } from './build/utils.js'; @@ -63,8 +62,7 @@ import { sveltekit_env_public_server, sveltekit_dev_traced, sveltekit_dev_server_entry, - sveltekit_dev_manifest_data, - sveltekit_dev_server + sveltekit_dev_init } from './module_ids.js'; import { import_peer } from '../../utils/import.js'; import { compact } from '../../utils/array.js'; @@ -617,7 +615,7 @@ function kit({ svelte_config }) { resolveId: { filter: { id: [ - exactRegex(sveltekit_dev_server), + exactRegex(sveltekit_dev_init), exactRegex('sveltekit:server-manifest'), exactRegex('sveltekit:server'), exactRegex('sveltekit:env'), @@ -625,7 +623,7 @@ function kit({ svelte_config }) { ] }, handler(id) { - if (id === sveltekit_dev_server) { + if (id === sveltekit_dev_init) { return server_instrumentation ? sveltekit_dev_traced : this.resolve(sveltekit_dev_server_entry); @@ -650,43 +648,10 @@ function kit({ svelte_config }) { }, load: { filter: { - id: [ - exactRegex('sveltekit:env'), - exactRegex(sveltekit_dev_manifest_data), - exactRegex(sveltekit_dev_traced) - ] + id: [exactRegex('sveltekit:env'), exactRegex(sveltekit_dev_traced)] }, handler(id) { switch (id) { - case sveltekit_dev_manifest_data: { - return dedent` - import { load_and_validate_params } from '__SVELTEKIT__/utils/params.js'; - import { to_fs } from '__SVELTEKIT__/utils/vite.js'; - import { loud_ssr_load_module } from '__SVELTEKIT__/exports/vite/dev/utils.js'; - - export const manifest_data = { - assets: ${s(manifest_data.assets)}, - hooks: { - client: ${s(manifest_data.hooks.client)}, - server: ${s(manifest_data.hooks.server)}, - universal: ${s(manifest_data.hooks.universal)} - }, - nodes: ${devalue.uneval(manifest_data.nodes)}, - routes: ${devalue.uneval(manifest_data.routes)}, - params: ${s(manifest_data.params)} - }; - - await load_and_validate_params({ - routes: manifest_data.routes, - params_path: manifest_data.params, - root: __SVELTEKIT_ROOT__, - load: (file) => loud_ssr_load_module(to_fs(file)) - }); - - export const mime_types = ${s(get_mime_lookup(manifest_data))}; - `; - } - case sveltekit_dev_traced: { if (!server_instrumentation) { throw new Error('Server instrumentation file not found. This should never happen'); @@ -1707,22 +1672,22 @@ function kit({ svelte_config }) { * @see https://vitejs.dev/guide/api-plugin.html#configureserver */ async configureServer(server) { - /** @type {typeof import('./dev/context.js')} */ + /** @type {import('./dev/context.js').Context} */ let context; if (isRunnableDevEnvironment(server.environments.ssr)) { - context = await server.environments.ssr.runner.import( - import.meta.resolve('./dev/context.js') - ); - context.set_dev_server(server); + context = /** @type {typeof import('./dev/context.js')} */ ( + await server.environments.ssr.runner.import(import.meta.resolve('./dev/context.js')) + ).dev; + context.vite = server; } return dev(server, vite_config, svelte_config, root, (data) => { manifest_data = data; + context.manifest_data = data; invalidate_module(server, sveltekit_manifest_data); - invalidate_module(server, sveltekit_dev_manifest_data); - context.set_svelte_config(svelte_config); - context.set_remotes(remotes); + context.svelte_config = svelte_config; + context.remotes = remotes; }); }, diff --git a/packages/kit/src/exports/vite/module_ids.js b/packages/kit/src/exports/vite/module_ids.js index 3b7fb9758e80..34819390596d 100644 --- a/packages/kit/src/exports/vite/module_ids.js +++ b/packages/kit/src/exports/vite/module_ids.js @@ -12,12 +12,10 @@ export const service_worker = '\0virtual:service-worker'; /** Used to construct `$app/manifest` */ export const sveltekit_manifest_data = '\0virtual:__sveltekit/manifest-data'; -export const sveltekit_dev_server = '__sveltekit/dev-server.js'; +export const sveltekit_dev_init = '__sveltekit/dev-server.js'; /** A facade that initialises tracing during development */ export const sveltekit_dev_traced = '\0virtual:__sveltekit/traced'; export const sveltekit_dev_server_entry = 'sveltekit:server-entry'; -/** Used to construct the SSR manifest from within an environment during development */ -export const sveltekit_dev_manifest_data = '\0virtual:__sveltekit/dev-manifest-data'; export const app_server = posixify( fileURLToPath(new URL('../../runtime/app/server/index.js', import.meta.url)) From 71416d3ab6e5fa422f660f9c50a4c278be4200ed Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Sat, 25 Jul 2026 01:27:10 +0800 Subject: [PATCH 040/109] didnt get them all the first try --- packages/kit/src/exports/vite/dev/server.js | 6 +++--- packages/kit/src/exports/vite/dev/utils.js | 12 +++++------- packages/kit/src/exports/vite/index.js | 1 - 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/packages/kit/src/exports/vite/dev/server.js b/packages/kit/src/exports/vite/dev/server.js index a0c41309421b..8bafb56a96b9 100644 --- a/packages/kit/src/exports/vite/dev/server.js +++ b/packages/kit/src/exports/vite/dev/server.js @@ -1,9 +1,9 @@ import { AsyncLocalStorage } from 'node:async_hooks'; import { Server as KitServer } from '../../../runtime/server/index.js'; import { fix_stack_trace } from './sourcemaps.js'; -import { get_svelte_config } from './context.js'; import { check_feature } from '../../../utils/features.js'; import { SCHEME } from '../../../utils/url.js'; +import { dev } from './context.js'; const { set_fix_stack_trace } = await import(/* @vite-ignore */ '__sveltekit/server'); set_fix_stack_trace(fix_stack_trace); @@ -18,7 +18,7 @@ globalThis.__SVELTEKIT_TRACK__ = (label) => { const context = async_local_storage.getStore(); if (!context || context.prerender === true) return; - check_feature(context.event.route.id, context.config, label, get_svelte_config().kit.adapter); + check_feature(context.event.route.id, context.config, label, dev.svelte_config.kit.adapter); }; const fetch = globalThis.fetch; @@ -33,7 +33,7 @@ globalThis.fetch = (info, init) => { return fetch(info, init); }; -const emulator = await get_svelte_config()?.kit.adapter?.emulate?.(); +const emulator = await dev.svelte_config.kit.adapter?.emulate?.(); export class Server extends KitServer { /** @type {import('types').InternalServer['respond']} */ diff --git a/packages/kit/src/exports/vite/dev/utils.js b/packages/kit/src/exports/vite/dev/utils.js index 67fd1b904a16..5db7c42c7ff5 100644 --- a/packages/kit/src/exports/vite/dev/utils.js +++ b/packages/kit/src/exports/vite/dev/utils.js @@ -1,7 +1,7 @@ import path from 'node:path'; import { styleText } from 'node:util'; import { buildErrorMessage } from 'vite'; -import { get_dev_server } from './context.js'; +import { dev } from './context.js'; import { to_fs } from '../../../utils/vite.js'; /** @param {string} url */ @@ -11,17 +11,15 @@ export async function loud_ssr_load_module(url) { } catch (/** @type {any} */ err) { const msg = buildErrorMessage(err, [styleText('red', `Internal server error: ${err.message}`)]); - const vite = get_dev_server(); - - if (!vite.config.logger.hasErrorLogged(err)) { - vite.config.logger.error(msg, { error: err }); + if (!dev.vite.config.logger.hasErrorLogged(err)) { + dev.vite.config.logger.error(msg, { error: err }); } // TODO this is inadequate — it doesn't reliably show the overlay on every page load, // and when it does appear it may immediately vanish. `vite.hot.send` broadcasts // to all connected clients, even ones that are unaffected by the error. // we need a more considered approach - vite.hot.send({ + dev.vite.hot.send({ type: 'error', err: /** @type {import('vite').ErrorPayload['err']} */ ({ ...err, @@ -42,7 +40,7 @@ export async function resolve(id) { const module = await loud_ssr_load_module(url); - const module_node = await get_dev_server().environments.ssr.moduleGraph.getModuleByUrl(url); + const module_node = await dev.vite.environments.ssr.moduleGraph.getModuleByUrl(url); if (!module_node) throw new Error(`Could not find node for ${url}`); return { module, module_node, url }; diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index f1f69676cc8e..0b78c8a55df8 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -426,7 +426,6 @@ function kit({ svelte_config }) { resolve: { alias: [ { find: '__SERVER__', replacement: `${out_dir}/generated/server` }, - { find: '__SVELTEKIT__', replacement: `${runtime_directory}/..` }, { find: '$app', replacement: `${runtime_directory}/app` }, { find: '$env', replacement: `${runtime_directory}/env` }, { From f2e57ad6a18e7867227b0318985267e5914c39aa Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Sat, 25 Jul 2026 01:31:23 +0800 Subject: [PATCH 041/109] invalidate ssr manifest module --- packages/kit/src/exports/vite/index.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 0b78c8a55df8..c65c39621ea8 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -72,6 +72,8 @@ import { treeshake_prerendered_remotes } from './build/remote.js'; import { SVELTE_KIT_ASSETS } from '../../constants.js'; import { get_runner } from '../../runner.js'; +const dev_context = import.meta.resolve('./dev/context.js'); + /** * The posix-ified root of the project based on the Vite configuration. * Populated after Vite plugins' `config` hooks run @@ -1676,15 +1678,15 @@ function kit({ svelte_config }) { if (isRunnableDevEnvironment(server.environments.ssr)) { context = /** @type {typeof import('./dev/context.js')} */ ( - await server.environments.ssr.runner.import(import.meta.resolve('./dev/context.js')) + await server.environments.ssr.runner.import(dev_context) ).dev; context.vite = server; } return dev(server, vite_config, svelte_config, root, (data) => { - manifest_data = data; - context.manifest_data = data; + manifest_data = context.manifest_data = data; invalidate_module(server, sveltekit_manifest_data); + invalidate_module(server, dev_context); context.svelte_config = svelte_config; context.remotes = remotes; }); From 42e0ef8238b63af9ff314bcf509cca958378bd29 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Sat, 25 Jul 2026 02:11:20 +0800 Subject: [PATCH 042/109] no longer true --- packages/kit/src/exports/vite/dev/index.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 3fc3c8824ebc..daafd7531309 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -106,11 +106,6 @@ export function dev(vite, vite_config, svelte_config, root, set_manifest_data) { watch('add', () => debounce(update_manifest)); watch('unlink', () => debounce(update_manifest)); watch('change', (file) => { - // `manifest_data` is populated lazily on the first request (see `update_manifest` - // call in the middleware below), so it may still be undefined if a file changes - // before the dev server has served a request. In that case there's nothing to - // update — the manifest will be created from scratch on the first request. - if (!manifest_data) return; // Don't run for a single file if the whole manifest is about to get updated // Unless it's a file where the trailing slash page option might have changed if (timeout || !/\+(page|layout|server).*$/.test(file)) return; From 7db1c35f19f1953b675132653eab31e497dbb523 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Sat, 25 Jul 2026 02:33:43 +0800 Subject: [PATCH 043/109] vercel bot fix --- packages/kit/src/exports/vite/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index c65c39621ea8..7aaafd970265 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -6,7 +6,7 @@ import fs from 'node:fs'; import path from 'node:path'; import process from 'node:process'; -import { pathToFileURL } from 'node:url'; +import { fileURLToPath, pathToFileURL } from 'node:url'; import { styleText } from 'node:util'; import MagicString from 'magic-string'; import { isRunnableDevEnvironment, loadEnv } from 'vite'; @@ -72,7 +72,7 @@ import { treeshake_prerendered_remotes } from './build/remote.js'; import { SVELTE_KIT_ASSETS } from '../../constants.js'; import { get_runner } from '../../runner.js'; -const dev_context = import.meta.resolve('./dev/context.js'); +const dev_context = posixify(fileURLToPath(import.meta.resolve('./dev/context.js'))); /** * The posix-ified root of the project based on the Vite configuration. From 49c05565c08a5741a67ad7e8fa39a9c0d3f1b9d8 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Sat, 25 Jul 2026 02:51:41 +0800 Subject: [PATCH 044/109] only reload for $app/manifest --- packages/kit/src/exports/vite/dev/index.js | 5 +++-- packages/kit/src/exports/vite/index.js | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index daafd7531309..f02a24f76de1 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -322,14 +322,15 @@ function has_correct_case(file, assets) { * Invalidates a module in all environments. * @param {ViteDevServer} server * @param {string} id + * @param {{ reload?: boolean }=} opts * @returns {void} */ -export function invalidate_module(server, id) { +export function invalidate_module(server, id, opts = { reload: false }) { for (const environment in server.environments) { const module = server.environments[environment].moduleGraph.getModuleById(id); if (module) { server.environments[environment].moduleGraph.invalidateModule(module); - void server.environments[environment].reloadModule(module); + if (opts.reload) void server.environments[environment].reloadModule(module); } } } diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 7aaafd970265..ac7995225c7b 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -1685,7 +1685,7 @@ function kit({ svelte_config }) { return dev(server, vite_config, svelte_config, root, (data) => { manifest_data = context.manifest_data = data; - invalidate_module(server, sveltekit_manifest_data); + invalidate_module(server, sveltekit_manifest_data, { reload: true }); invalidate_module(server, dev_context); context.svelte_config = svelte_config; context.remotes = remotes; From 36e2c2176e1163d194271d8e550756da7c6529c9 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Sat, 25 Jul 2026 03:28:05 +0800 Subject: [PATCH 045/109] not needed --- packages/kit/src/exports/vite/dev/ssr_entry.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/kit/src/exports/vite/dev/ssr_entry.js b/packages/kit/src/exports/vite/dev/ssr_entry.js index 5ce0c024cc3a..c56ddfa4e29b 100644 --- a/packages/kit/src/exports/vite/dev/ssr_entry.js +++ b/packages/kit/src/exports/vite/dev/ssr_entry.js @@ -42,5 +42,3 @@ export async function fetch(request) { } }); } - -import.meta.hot?.accept(); From c97be4db5a030caba5279327708c4c61c58af4c7 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Sat, 25 Jul 2026 03:37:06 +0800 Subject: [PATCH 046/109] clean up --- packages/kit/src/exports/vite/index.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index ac7995225c7b..30add305dd35 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -6,7 +6,7 @@ import fs from 'node:fs'; import path from 'node:path'; import process from 'node:process'; -import { fileURLToPath, pathToFileURL } from 'node:url'; +import { pathToFileURL } from 'node:url'; import { styleText } from 'node:util'; import MagicString from 'magic-string'; import { isRunnableDevEnvironment, loadEnv } from 'vite'; @@ -72,7 +72,7 @@ import { treeshake_prerendered_remotes } from './build/remote.js'; import { SVELTE_KIT_ASSETS } from '../../constants.js'; import { get_runner } from '../../runner.js'; -const dev_context = posixify(fileURLToPath(import.meta.resolve('./dev/context.js'))); +const dev_context = posixify(path.join(import.meta.dirname, 'dev/context.js')); /** * The posix-ified root of the project based on the Vite configuration. @@ -631,11 +631,11 @@ function kit({ svelte_config }) { } if (id === 'sveltekit:server-manifest') { - return path.join(import.meta.dirname, 'dev/ssr_manifest.js'); + return posixify(path.join(import.meta.dirname, 'dev/ssr_manifest.js')); } if (id === 'sveltekit:server') { - return path.join(import.meta.dirname, 'dev/server.js'); + return posixify(path.join(import.meta.dirname, 'dev/server.js')); } if (id === 'sveltekit:env') { @@ -663,8 +663,6 @@ function kit({ svelte_config }) { const { fetch } = await import(${s(sveltekit_dev_server_entry)}); export { fetch }; - - import.meta.hot?.accept(); `; } From 87d28dfb71e7cc3ca8d69554b19a9ca40bb37024 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Sat, 25 Jul 2026 03:56:55 +0800 Subject: [PATCH 047/109] not needed anymore --- packages/kit/src/types/ambient-private.d.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/packages/kit/src/types/ambient-private.d.ts b/packages/kit/src/types/ambient-private.d.ts index 92f034d78288..a51cda0feb5d 100644 --- a/packages/kit/src/types/ambient-private.d.ts +++ b/packages/kit/src/types/ambient-private.d.ts @@ -21,15 +21,6 @@ declare module '__sveltekit/server' { export function set_read_implementation(fn: (path: string) => ReadableStream): void; } -/** Used to construct the SSR manifest from within an environment during development */ -declare module '__sveltekit/dev-manifest-data' { - import { ManifestData } from 'types'; - - export const env: Record; - export const mime_types: Record; - export const manifest_data: ManifestData; -} - declare module '__sveltekit/env' { // exported environment variables are defined in env.d.ts From a0828b5f9405830b751d3539d8e9be32557412ad Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Sat, 25 Jul 2026 05:33:02 +0800 Subject: [PATCH 048/109] log environment errors --- packages/kit/src/runtime/server/index.js | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js index 0b1522391ef0..65836c9b3a34 100644 --- a/packages/kit/src/runtime/server/index.js +++ b/packages/kit/src/runtime/server/index.js @@ -113,16 +113,34 @@ export class Server { return; } + /** @type {string} */ + let message = ''; + let stack = ''; + let e = error; while (e instanceof Error) { if (e.stack) { + message ||= e.message; + stack += e.stack; console.error(e.stack); } e = e.cause; } if (e) { - console.error(String(e)); + const original_message = String(e); + console.error(original_message); + if (import.meta.hot && original_message) message = original_message; + } + + if (import.meta.hot && message) { + import.meta.hot.send('vite:error', { + type: 'error', + err: { + message, + stack: stack.replace(`Error: ${message}\n`, '') + } + }); } }), handleFetch: module.handleFetch || (({ request, fetch }) => fetch(request)), From 9108ed451ee8b61d38bf437f7ae52904a071f3fc Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Sat, 25 Jul 2026 05:33:08 +0800 Subject: [PATCH 049/109] Update utils.js --- packages/kit/src/exports/vite/dev/utils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/kit/src/exports/vite/dev/utils.js b/packages/kit/src/exports/vite/dev/utils.js index 5db7c42c7ff5..8b711bfb234c 100644 --- a/packages/kit/src/exports/vite/dev/utils.js +++ b/packages/kit/src/exports/vite/dev/utils.js @@ -21,13 +21,13 @@ export async function loud_ssr_load_module(url) { // we need a more considered approach dev.vite.hot.send({ type: 'error', - err: /** @type {import('vite').ErrorPayload['err']} */ ({ + err: { ...err, // these properties are non-enumerable and will // not be serialized unless we explicitly include them message: err.message, stack: err.stack ?? '' - }) + } }); throw err; From ba380f7afd734f117c85a76e4034dd5decec8721 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Sat, 25 Jul 2026 05:33:24 +0800 Subject: [PATCH 050/109] forward server errors --- packages/kit/src/exports/vite/dev/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index f02a24f76de1..d99a2fb0eb78 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -167,6 +167,8 @@ export function dev(vite, vite_config, svelte_config, root, set_manifest_data) { next(); }); + vite.environments.ssr.hot.on('vite:error', vite.hot.send); + return () => { const serve_static_middleware = vite.middlewares.stack.find( (middleware) => From 7ab3973044fae2de0ed1225f2d743e908e2004ae Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Sat, 25 Jul 2026 05:34:08 +0800 Subject: [PATCH 051/109] add types --- packages/kit/src/exports/vite/dev/server.js | 38 +++++++++++++++------ 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/packages/kit/src/exports/vite/dev/server.js b/packages/kit/src/exports/vite/dev/server.js index 8bafb56a96b9..841f224bc4e7 100644 --- a/packages/kit/src/exports/vite/dev/server.js +++ b/packages/kit/src/exports/vite/dev/server.js @@ -1,3 +1,5 @@ +/** @import { RequestEvent } from '@sveltejs/kit'; */ +/** @import { InternalServer, PrerenderOption } from 'types'; */ import { AsyncLocalStorage } from 'node:async_hooks'; import { Server as KitServer } from '../../../runtime/server/index.js'; import { fix_stack_trace } from './sourcemaps.js'; @@ -11,6 +13,14 @@ set_fix_stack_trace(fix_stack_trace); const { set_assets } = await import(/* @vite-ignore */ '$app/paths/internal/server'); set_assets(__SVELTEKIT_PATHS_ASSETS__); +/** + * @typedef {object} FeatureContext + * @property {RequestEvent} event + * @property {Record} config + * @property {PrerenderOption} prerender + */ + +/** @type {AsyncLocalStorage} */ const async_local_storage = new AsyncLocalStorage(); /** @param {string} label */ @@ -18,7 +28,12 @@ globalThis.__SVELTEKIT_TRACK__ = (label) => { const context = async_local_storage.getStore(); if (!context || context.prerender === true) return; - check_feature(context.event.route.id, context.config, label, dev.svelte_config.kit.adapter); + check_feature( + /** @type {string} */ (context.event.route.id), + context.config, + label, + dev.svelte_config.kit.adapter + ); }; const fetch = globalThis.fetch; @@ -36,15 +51,18 @@ globalThis.fetch = (info, init) => { const emulator = await dev.svelte_config.kit.adapter?.emulate?.(); export class Server extends KitServer { - /** @type {import('types').InternalServer['respond']} */ + /** @type {InternalServer['respond']} */ async respond(request, options) { - options.before_handle = async (event, config, prerender, handle) => { - // we need to use .run because .enterWith() is not supported in Cloudflare Workers - // see https://blog.cloudflare.com/workers-node-js-asynclocalstorage/ - return await async_local_storage.run({ event, config, prerender }, handle); - }; - options.emulator = emulator; - - return await super.respond(request, options); + return await /** @type {InternalServer['respond']} */ (super.respond)(request, { + ...options, + before_handle: async (event, config, prerender, handle) => { + // we need to use .run because .enterWith() is not supported in Cloudflare Workers + // see https://blog.cloudflare.com/workers-node-js-asynclocalstorage/ + return await async_local_storage.run({ event, config, prerender }, () => { + return options.before_handle?.(event, config, prerender, handle) ?? handle(); + }); + }, + emulator + }); } } From 8649cf72d29de628abf073d1b4c2c84a3360ed5c Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Tue, 28 Jul 2026 03:50:07 +0800 Subject: [PATCH 052/109] initial rework --- .changeset/custom-server-handler.md | 5 + .../99-writing-adapters.md | 86 +++++-------- packages/kit/src/exports/public.d.ts | 44 ++++++- packages/kit/src/exports/vite/dev/handler.js | 22 ++++ packages/kit/src/exports/vite/dev/index.js | 61 ++++++--- packages/kit/src/exports/vite/dev/server.js | 27 ++-- .../kit/src/exports/vite/dev/ssr_entry.js | 44 +------ .../kit/src/exports/vite/dev/ssr_manifest.js | 3 +- packages/kit/src/exports/vite/index.js | 97 +++++--------- packages/kit/src/exports/vite/module_ids.js | 9 +- packages/kit/src/runtime/server/index.js | 54 ++++---- packages/kit/src/types/ambient-private.d.ts | 16 ++- packages/kit/src/types/ambient.d.ts | 53 -------- .../kit/test/apps/basics/test/server.test.js | 4 + packages/kit/types/index.d.ts | 118 +++++++++--------- playgrounds/basic/tsconfig.json | 2 +- playgrounds/basic/vite.config.js | 5 +- 17 files changed, 322 insertions(+), 328 deletions(-) create mode 100644 .changeset/custom-server-handler.md create mode 100644 packages/kit/src/exports/vite/dev/handler.js diff --git a/.changeset/custom-server-handler.md b/.changeset/custom-server-handler.md new file mode 100644 index 000000000000..2416c7f7b824 --- /dev/null +++ b/.changeset/custom-server-handler.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': minor +--- + +feat: allow adapters to provide a `customHandler` that handles requests to the server 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 4a5aa417ea64..83134de9490a 100644 --- a/documentation/docs/25-build-and-deploy/99-writing-adapters.md +++ b/documentation/docs/25-build-and-deploy/99-writing-adapters.md @@ -40,6 +40,8 @@ export default function (options) { // Return `false if it can't, or throw a descriptive error. } }, + // TODO + customHandler: import.meta.resolve('./my-handler.js'), vite: { plugins: [ // add plugins here to integrate with Vite @@ -51,7 +53,7 @@ export default function (options) { } ``` -Of these, `name` and `adapt` are required. `emulate`, `vite.plugins` and `supports` are optional. +Of these, `name` and `adapt` are required. `emulate`, `customHandler`, `vite.plugins` and `supports` are optional. Within the `adapt` method, there are a number of things that an adapter should do: @@ -67,69 +69,41 @@ Within the `adapt` method, there are a number of things that an adapter should d Where possible, we recommend putting the adapter output under the `build/` directory with any intermediate output placed under `.svelte-kit/[adapter-name]`. -## Configuring the development and preview experience +## Custom request handler -By default, SvelteKit runs your server code through a Node.js runtime when running `vite dev` and `vite preview`. You can change this behaviour by adding a Vite plugin that has a `configureServer` and `configurePreviewServer` hook to route requests to [a different runtime](https://vite.dev/guide/api-environment-runtimes). - -The main Vite server environment SvelteKit uses is named `ssr`. You can change its settings by referencing it in the `config` hook of a Vite plugin. +You can add a `customHandler` property set to the path of a module that initialises the server and handles requests: ```js -// @errors: 2304 1005 1109 -config(userConfig) { - userConfig.environments = { - ssr: { - // ... - } - } -} +/** @type {import('@sveltejs/kit').Adapter} */ +const adapter = { + name: 'adapter-package-name', + adapt() {}, + customHandler: import.meta.resolve('./handler.js') +}; ``` -You can also define a custom server entry during development by adding a Vite plugin which resolves the `sveltekit:server-entry` ID to your own module. +The default export of the module is given an instance of `Server`. TODO ```js -// @errors: 1005 1128 -{ - name: 'vite-plugin-name-it-yourself', - applyToEnvironment(environment) { - return environment.name === 'ssr'; - }, - resolveId: { - filter: { - id: /^sveltekit:server-entry$/ - }, - handler(id) { - return this.resolve(import.meta.resolve('./path-to-your-server.js')); - } - } -} -``` - -This module should instantiate the server with your app's manifest, initialise environment variables and the `read` implementation, and export a `fetch` handler which receives a `Request` and returns a `Response`: - -```js -import { env } from 'sveltekit:env'; -import { Server } from 'sveltekit:server'; -import { manifest } from 'sveltekit:server-manifest'; - -const server = new Server(manifest); - -await server.init({ - env, - read: (file) => { /* implement how your platform retrieves file contents */ } -}); - -/** - * @param {Request} request - * @returns {Promise} - */ -export async function fetch(request) { - return await server.respond(request, { - getClientAddress: () => { - return request.headers.get('how-your-platform-exposes-the-remote-address') +/// file: handler.js +/** @type {import('@sveltejs/kit').CustomHandler} */ +export default async (server, env) => { + await server.init({ + env, + read: () => { + // how your platform reads files } }); -} -// Without this, server file changes will invalidate the entire Vite server module graph: -import.meta.hot?.accept(); + return (request, address) => { + return server.respond(request, { + getClientAddress() { + return address; + }, + platform: { + // the shape of `App.Platform` + } + }); + }; +}; ``` diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index f061fc082486..7742d9067b76 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -71,6 +71,19 @@ export interface Adapter { * during dev, build and prerendering. */ emulate?: () => MaybePromise; + /** + * The path to a module whose default export is a [custom handler](https://svelte.dev/docs/kit/writing-adapters#Providing-a-custom-handler). + * ```js + * /** @type {import('@sveltejs/kit').Adapter} *\/ + * const adapter = { + * name: 'my-adapter', + * adapt() {}, + * customHandler: import.meta.resolve('./handler.js') + * }; + * ``` + * @since 3.0.0 + */ + customHandler?: string; vite?: { /** * Plugins provided by the adapter are placed before any of SvelteKit's own plugins. @@ -80,6 +93,33 @@ export interface Adapter { }; } +/** + * Typically you would intercept platform-specific requests and add platform-specific data: + * + * ```js + * export default async function (server, env) { + * await server.init({ env }); + * + * return (request) => { + * return server.respond(request, { + * getClientAddress() { + * throw new Error('Could not determine clientAddress'); + * }, + * platform: { + * // the shape of `App.Platform` + * } + * }); + * }; + * }; + * ``` + * @since 3.0.0 + */ +export type SSRHandler = ( + server: Server, + /** Environment variables loaded by Vite */ + env: Record +) => Promise<(request: Request, address: string | undefined) => Promise>; + export type LoadProperties | void> = input extends void ? undefined // needs to be undefined, because void will break intellisense : input extends Record @@ -1791,7 +1831,7 @@ export class Server { export interface ServerInitOptions { /** A map of environment variables. */ - env: Record; + env: Record; /** A function that turns an asset filename into a `ReadableStream`. Required for the `read` export from `$app/server` to work. */ read?: (file: string) => MaybePromise; } @@ -1802,7 +1842,7 @@ export interface ServerInitOptions { 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. */ + /** 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; diff --git a/packages/kit/src/exports/vite/dev/handler.js b/packages/kit/src/exports/vite/dev/handler.js new file mode 100644 index 000000000000..cfc51f6e012a --- /dev/null +++ b/packages/kit/src/exports/vite/dev/handler.js @@ -0,0 +1,22 @@ +/** @import { SSRHandler } from '@sveltejs/kit' */ +import { createReadableStream } from '@sveltejs/kit/node'; + +/** + * The handler used when the adapter doesn't provide a `customHandler`. + * @type {SSRHandler} + */ +export default async function (server, env) { + await server.init({ + env, + read: (file) => createReadableStream(file) + }); + + return (request, address) => { + return server.respond(request, { + getClientAddress() { + if (!address) throw new Error('Could not determine clientAddress'); + return address; + } + }); + }; +} diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index d99a2fb0eb78..c1f632e505ff 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -21,21 +21,24 @@ import { } from '../utils.js'; import { escape_html } from '../../../utils/escape.js'; import { fix_stack_trace } from './sourcemaps.js'; -import { sveltekit_dev_init } from '../module_ids.js'; +import { sveltekit_dev_server } from '../module_ids.js'; import { get_runner } from '../../../runner.js'; +export const dev_context = posixify(path.join(import.meta.dirname, 'context.js')); + /** * @param {import('vite').ViteDevServer} vite * @param {import('vite').ResolvedConfig} vite_config * @param {import('types').ValidatedConfig} svelte_config + * @param {import('types').RemoteChunk[]} remotes * @param {string} root The project root directory * @param {(manifest_data: import('types').ManifestData) => void} set_manifest_data * @return {() => void} */ -export function dev(vite, vite_config, svelte_config, root, set_manifest_data) { +export function dev(vite, vite_config, svelte_config, remotes, root, set_manifest_data) { sync.init(svelte_config, root); - /** @type {import('types').ManifestData} */ + /** @type {import('types').ManifestData | undefined} */ let manifest_data; /** @type {Error | null} */ @@ -106,6 +109,7 @@ export function dev(vite, vite_config, svelte_config, root, set_manifest_data) { watch('add', () => debounce(update_manifest)); watch('unlink', () => debounce(update_manifest)); watch('change', (file) => { + if (!manifest_data) return; // Don't run for a single file if the whole manifest is about to get updated // Unless it's a file where the trailing slash page option might have changed if (timeout || !/\+(page|layout|server).*$/.test(file)) return; @@ -169,6 +173,8 @@ export function dev(vite, vite_config, svelte_config, root, set_manifest_data) { vite.environments.ssr.hot.on('vite:error', vite.hot.send); + const adapter_supports_instrumentation = svelte_config.kit.adapter?.supports?.instrumentation?.(); + return () => { const serve_static_middleware = vite.middlewares.stack.find( (middleware) => @@ -228,6 +234,30 @@ export function dev(vite, vite_config, svelte_config, root, set_manifest_data) { return; } + const runner = get_runner(vite); + + // resolve the instrumentation file per request so that changes to it + // are picked up on new requests + /** @type {string | null} */ + const resolved_instrumentation = resolve_entry( + path.join(svelte_config.kit.files.src, 'instrumentation.server') + ); + + if (resolved_instrumentation) { + if (svelte_config.kit.adapter && adapter_supports_instrumentation) { + throw new Error( + `${resolved_instrumentation} is unsupported in ${svelte_config.kit.adapter.name}.` + ); + } + + await runner.import(resolved_instrumentation); + } + + const request = getRequest({ + base, + request: req + }); + if (manifest_error) { console.error(styleText(['bold', 'red'], manifest_error.message)); @@ -250,21 +280,22 @@ export function dev(vite, vite_config, svelte_config, root, set_manifest_data) { return; } - const request = getRequest({ - base, - request: req - }); + if (!manifest_data) throw new Error('Failed to update the manifest'); - const runner = get_runner(vite); + // set the context on every request in case the user has hmr disabled + const context = /** @type {typeof import('./context.js')} */ ( + await runner.import(dev_context) + ).dev.context; + context.vite = vite; + context.svelte_config = svelte_config; + context.remotes = remotes; + context.manifest_data = manifest_data; - /** @type {{ fetch(request: Request): Promise }} */ - const server_entry = await runner.import(sveltekit_dev_init); - - if (req.socket.remoteAddress) { - request.headers.set('x-sveltekit-remote-address', req.socket.remoteAddress); - } + const handle = /** @type {typeof import('./ssr_entry.js')} */ ( + await runner.import(sveltekit_dev_server) + ).default; - const rendered = await server_entry.fetch(request); + const rendered = await handle(request, req.socket.remoteAddress); if (rendered.status === 404) { // @ts-expect-error diff --git a/packages/kit/src/exports/vite/dev/server.js b/packages/kit/src/exports/vite/dev/server.js index 841f224bc4e7..fd8060e75886 100644 --- a/packages/kit/src/exports/vite/dev/server.js +++ b/packages/kit/src/exports/vite/dev/server.js @@ -1,11 +1,15 @@ /** @import { RequestEvent } from '@sveltejs/kit'; */ -/** @import { InternalServer, PrerenderOption } from 'types'; */ +/** @import { InternalServer, PrerenderOption, RequestOptions } from 'types'; */ +import fs from 'node:fs'; +import path from 'node:path'; import { AsyncLocalStorage } from 'node:async_hooks'; -import { Server as KitServer } from '../../../runtime/server/index.js'; +import { dev } from './context.js'; import { fix_stack_trace } from './sourcemaps.js'; +import { manifest } from './ssr_manifest.js'; +import { Server as KitServer } from '../../../runtime/server/index.js'; import { check_feature } from '../../../utils/features.js'; import { SCHEME } from '../../../utils/url.js'; -import { dev } from './context.js'; +import { from_fs } from '../../../utils/vite.js'; const { set_fix_stack_trace } = await import(/* @vite-ignore */ '__sveltekit/server'); set_fix_stack_trace(fix_stack_trace); @@ -51,16 +55,25 @@ globalThis.fetch = (info, init) => { const emulator = await dev.svelte_config.kit.adapter?.emulate?.(); export class Server extends KitServer { - /** @type {InternalServer['respond']} */ + /** + * @param {Request} request + * @param {RequestOptions} options + * @returns {Promise} + */ async respond(request, options) { return await /** @type {InternalServer['respond']} */ (super.respond)(request, { ...options, + read: (file) => { + if (file in manifest._.server_assets) { + return fs.readFileSync(from_fs(file)); + } + + return fs.readFileSync(path.join(__SVELTEKIT_FILES_ASSETS__, file)); + }, before_handle: async (event, config, prerender, handle) => { // we need to use .run because .enterWith() is not supported in Cloudflare Workers // see https://blog.cloudflare.com/workers-node-js-asynclocalstorage/ - return await async_local_storage.run({ event, config, prerender }, () => { - return options.before_handle?.(event, config, prerender, handle) ?? handle(); - }); + return await async_local_storage.run({ event, config, prerender }, handle); }, emulator }); diff --git a/packages/kit/src/exports/vite/dev/ssr_entry.js b/packages/kit/src/exports/vite/dev/ssr_entry.js index c56ddfa4e29b..acaa3a7b6d98 100644 --- a/packages/kit/src/exports/vite/dev/ssr_entry.js +++ b/packages/kit/src/exports/vite/dev/ssr_entry.js @@ -1,44 +1,12 @@ /** @import { InternalServer } from 'types' */ -import fs from 'node:fs'; -import path from 'node:path'; -import { env } from 'sveltekit:env'; -import { Server } from 'sveltekit:server'; -import { manifest } from 'sveltekit:server-manifest'; -import { createReadableStream } from '@sveltejs/kit/node'; -import { from_fs } from '../../../utils/vite.js'; +import init_server from '__sveltekit/dev-handler'; +import { env } from '__sveltekit/dev-env'; +import { Server } from './server.js'; +import { manifest } from './ssr_manifest.js'; /** @type {InternalServer} */ const server = new Server(manifest); -await server.init({ - env, - read: (file) => createReadableStream(file) -}); +const handler = await init_server(server, env); -/** - * @param {Request} request - * @returns {Promise} - */ -export async function fetch(request) { - /** @type {string | undefined} */ - let remote_address; - - if (request.headers.has('x-sveltekit-remote-address')) { - remote_address = request.headers.get('x-sveltekit-remote-address') ?? undefined; - request.headers.delete('x-sveltekit-remote-address'); - } - - return await server.respond(request, { - getClientAddress: () => { - if (remote_address) return remote_address; - throw new Error('Could not determine clientAddress'); - }, - read: (file) => { - if (file in manifest._.server_assets) { - return fs.readFileSync(from_fs(file)); - } - - return fs.readFileSync(path.join(__SVELTEKIT_FILES_ASSETS__, file)); - } - }); -} +export default handler; diff --git a/packages/kit/src/exports/vite/dev/ssr_manifest.js b/packages/kit/src/exports/vite/dev/ssr_manifest.js index 8d00ec06e8a4..c9d56b466556 100644 --- a/packages/kit/src/exports/vite/dev/ssr_manifest.js +++ b/packages/kit/src/exports/vite/dev/ssr_manifest.js @@ -23,7 +23,7 @@ await load_and_validate_params({ /** @type {SSRManifest} */ export const manifest = { appDir: __SVELTEKIT_APP_DIR__, - appPath: `${__SVELTEKIT_PATHS_BASE__}/${__SVELTEKIT_APP_DIR__}`, + appPath: `${__SVELTEKIT_PATHS_BASE__.slice(1)}${__SVELTEKIT_PATHS_BASE__ ? '/' : ''}${__SVELTEKIT_APP_DIR__}`, assets: new Set(dev.manifest_data.assets.map((asset) => asset.file)), mimeTypes: get_mime_lookup(dev.manifest_data), _: { @@ -127,7 +127,6 @@ export const manifest = { for (const dep of deps) { if (isCSSRequest(dep.url) && !vite_css_query_regex.test(dep.url)) { - // if (isCSSRequest(dep.url) && !vite_css_query_regex.test(dep.url)) { const inline_css_url = dep.url.includes('?') ? dep.url.replace('?', '?inline&') : dep.url + '?inline'; diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index a4ae2a3c511a..cc1dcbbce086 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -1,15 +1,15 @@ /** @import { EnvVarConfig, KitConfig } from '@sveltejs/kit' */ /** @import { Options, SvelteConfig } from '@sveltejs/vite-plugin-svelte' */ /** @import { PreprocessorGroup } from 'svelte/compiler' */ -/** @import { BuildData, ManifestData, Prerendered, ServerMetadata, RemoteInternals, ValidatedConfig, ValidatedKitConfig } from 'types' */ +/** @import { BuildData, ManifestData, Prerendered, ServerMetadata, RemoteInternals, ValidatedConfig, ValidatedKitConfig, RemoteChunk } from 'types' */ /** @import { Manifest, Plugin, ResolvedConfig, Rolldown, UserConfig, ViteDevServer } from 'vite' */ import fs from 'node:fs'; import path from 'node:path'; import process from 'node:process'; -import { pathToFileURL } from 'node:url'; +import { fileURLToPath, pathToFileURL } from 'node:url'; import { styleText } from 'node:util'; import MagicString from 'magic-string'; -import { isRunnableDevEnvironment, loadEnv } from 'vite'; +import { loadEnv } from 'vite'; import { exactRegex, prefixRegex } from 'rolldown/filter'; import { copy, mkdirp, read, resolve_entry, rimraf } from '../../utils/filesystem.js'; @@ -30,7 +30,7 @@ import { runtime_directory, logger, get_runtime_base } from '../../core/utils.js import { generate_manifest } from '../../core/generate_manifest/index.js'; import { build_server_nodes } from './build/build_server.js'; import { find_deps, resolve_symlinks } from './build/utils.js'; -import { dev, invalidate_module } from './dev/index.js'; +import { dev, dev_context, invalidate_module } from './dev/index.js'; import { preview } from './preview/index.js'; import { error_for_missing_config, @@ -57,9 +57,9 @@ import { sveltekit_manifest_data, sveltekit_env_public_client, sveltekit_env_public_server, - sveltekit_dev_traced, - sveltekit_dev_server_entry, - sveltekit_dev_init + sveltekit_dev_env, + sveltekit_dev_server, + sveltekit_dev_handler } from './module_ids.js'; import { import_peer } from '../../utils/import.js'; import { compact } from '../../utils/array.js'; @@ -69,6 +69,9 @@ import { treeshake_prerendered_remotes } from './build/remote.js'; import { SVELTE_KIT_ASSETS } from '../../constants.js'; import { get_runner } from '../../runner.js'; +const dev_ssr_entry = posixify(path.join(import.meta.dirname, 'dev/ssr_entry.js')); +const dev_default_handler = posixify(path.join(import.meta.dirname, 'dev/handler.js')); + /** @type {import('./types.js').EnforcedConfig} */ const enforced_config = { appType: true, @@ -266,8 +269,7 @@ function kit({ svelte_config }) { /** @type {string | null} */ let service_worker_entry_file; - /** @type {string | null} */ - let server_instrumentation; + /** @type {string} */ let normalized_cwd; /** @type {Array<{ alias: string, path: string }>} */ @@ -340,7 +342,6 @@ function kit({ svelte_config }) { : 'globalThis.__sveltekit_dev'; env = loadEnv(config_env.mode, kit.env.dir, ''); - server_instrumentation = resolve_entry(path.join(kit.files.src, 'instrumentation.server')); vite = await import_peer('vite', root); @@ -587,61 +588,31 @@ function kit({ svelte_config }) { resolveId: { filter: { - id: [ - exactRegex(sveltekit_dev_init), - exactRegex('sveltekit:server-manifest'), - exactRegex('sveltekit:server'), - exactRegex('sveltekit:env'), - exactRegex(sveltekit_dev_server_entry) - ] + id: [exactRegex(sveltekit_dev_server), exactRegex(sveltekit_dev_handler)] }, handler(id) { - if (id === sveltekit_dev_init) { - return server_instrumentation - ? sveltekit_dev_traced - : this.resolve(sveltekit_dev_server_entry); + if (id === sveltekit_dev_server) { + return dev_ssr_entry; } - if (id === 'sveltekit:server-manifest') { - return posixify(path.join(import.meta.dirname, 'dev/ssr_manifest.js')); - } + if (id === sveltekit_dev_handler) { + const custom_handler = kit.adapter?.customHandler; + if (!custom_handler) return dev_default_handler; - if (id === 'sveltekit:server') { - return posixify(path.join(import.meta.dirname, 'dev/server.js')); - } - - if (id === 'sveltekit:env') { - return id; - } + const filepath = custom_handler.startsWith('file://') + ? fileURLToPath(custom_handler) + : path.resolve(root, custom_handler); - if (id === sveltekit_dev_server_entry) { - return posixify(path.join(import.meta.dirname, 'dev/ssr_entry.js')); + return posixify(filepath); } } }, load: { filter: { - id: [exactRegex('sveltekit:env'), exactRegex(sveltekit_dev_traced)] + id: [exactRegex(sveltekit_dev_env)] }, - handler(id) { - switch (id) { - case sveltekit_dev_traced: { - if (!server_instrumentation) { - throw new Error('Server instrumentation file not found. This should never happen'); - } - - return dedent` - import ${s(posixify(server_instrumentation))}; - - const { fetch } = await import(${s(sveltekit_dev_server_entry)}); - export { fetch }; - `; - } - - case 'sveltekit:env': { - return `export const env = ${s(env)};`; - } - } + handler() { + return `export const env = ${s(env)};`; } } }; @@ -918,7 +889,7 @@ function kit({ svelte_config }) { /** @type {ViteDevServer} */ let dev_server; - /** @type {Array<{ hash: string, file: string }>} */ + /** @type {RemoteChunk[]} */ const remotes = []; /** @type {Map} Maps remote hash -> original module id */ @@ -1594,23 +1565,11 @@ function kit({ svelte_config }) { * Adds the SvelteKit middleware to do SSR in dev mode. * @see https://vitejs.dev/guide/api-plugin.html#configureserver */ - async configureServer(server) { - /** @type {import('./dev/context.js').Context} */ - let context; - - if (isRunnableDevEnvironment(server.environments.ssr)) { - context = /** @type {typeof import('./dev/context.js')} */ ( - await server.environments.ssr.runner.import(dev_context) - ).dev; - context.vite = server; - } - - return dev(server, vite_config, svelte_config, root, (data) => { - manifest_data = context.manifest_data = data; + configureServer(server) { + return dev(server, vite_config, svelte_config, remotes, root, (data) => { + manifest_data = data; invalidate_module(server, sveltekit_manifest_data, { reload: true }); invalidate_module(server, dev_context); - context.svelte_config = svelte_config; - context.remotes = remotes; }); }, diff --git a/packages/kit/src/exports/vite/module_ids.js b/packages/kit/src/exports/vite/module_ids.js index 447312abf9da..b26b15aa18a7 100644 --- a/packages/kit/src/exports/vite/module_ids.js +++ b/packages/kit/src/exports/vite/module_ids.js @@ -11,11 +11,12 @@ export const service_worker = '\0virtual:service-worker'; /** Used to construct `$app/manifest` */ export const sveltekit_manifest_data = '\0virtual:__sveltekit/manifest-data'; +/** The environment variables loaded by Vite, used to initialise the dev server instance */ +export const sveltekit_dev_env = '\0virtual:__sveltekit/dev-env'; -export const sveltekit_dev_init = '__sveltekit/dev-server.js'; -/** A facade that initialises tracing during development */ -export const sveltekit_dev_traced = '\0virtual:__sveltekit/traced'; -export const sveltekit_dev_server_entry = 'sveltekit:server-entry'; +/** Resolves to the adapter's `customHandler`, falling back to SvelteKit's default handler */ +export const sveltekit_dev_server = '__sveltekit/dev-server.js'; +export const sveltekit_dev_handler = '__sveltekit/dev-handler'; export const app_server = posixify( fileURLToPath(new URL('../../runtime/app/server/index.js', import.meta.url)) diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js index 65836c9b3a34..911ec07ed280 100644 --- a/packages/kit/src/runtime/server/index.js +++ b/packages/kit/src/runtime/server/index.js @@ -105,23 +105,25 @@ export class Server { this.#options.hooks = { handle: module.handle || (({ event, resolve }) => resolve(event)), - handleError: - module.handleError || - (({ error }) => { - if (error instanceof SvelteKitError) { + handleError: (args) => { + /** @type {string} */ + let message = ''; + let stack = ''; + + if (!module.handleError) { + if (args.error instanceof SvelteKitError) { // don't log stack traces for 404s etc, it's all internal gubbins return; } - /** @type {string} */ - let message = ''; - let stack = ''; - - let e = error; + let e = args.error; while (e instanceof Error) { - if (e.stack) { + if (__SVELTEKIT_DEV__) { message ||= e.message; - stack += e.stack; + if (e.stack) stack += e.stack; + } + + if (e.stack) { console.error(e.stack); } e = e.cause; @@ -130,19 +132,27 @@ export class Server { if (e) { const original_message = String(e); console.error(original_message); - if (import.meta.hot && original_message) message = original_message; + if (__SVELTEKIT_DEV__ && original_message) message = original_message; } + } else if (args.error instanceof Error) { + message = args.error.message; + stack = args.error.stack ?? ''; + } - if (import.meta.hot && message) { - import.meta.hot.send('vite:error', { - type: 'error', - err: { - message, - stack: stack.replace(`Error: ${message}\n`, '') - } - }); - } - }), + if (__SVELTEKIT_DEV__ && message) { + import.meta.hot?.send('vite:error', { + type: 'error', + err: { + message, + stack: stack.replace(`Error: ${message}\n`, '') + } + }); + } + + if (module.handleError) { + return module.handleError(args); + } + }, handleFetch: module.handleFetch || (({ request, fetch }) => fetch(request)), handleValidationError: module.handleValidationError || diff --git a/packages/kit/src/types/ambient-private.d.ts b/packages/kit/src/types/ambient-private.d.ts index a51cda0feb5d..c5d7c15d74ed 100644 --- a/packages/kit/src/types/ambient-private.d.ts +++ b/packages/kit/src/types/ambient-private.d.ts @@ -25,7 +25,7 @@ declare module '__sveltekit/env' { // exported environment variables are defined in env.d.ts /** Populate exported environment variables */ - export function set_env(environment: Record): void; + export function set_env(environment: Record): void; /** public env vars */ export const explicit_public_env: Record; @@ -53,3 +53,17 @@ declare module '__sveltekit/manifest-data' { export const prerendered: Array<{ path: string }>; export const routes: Array<{ id: string }>; } + +/** The environment variables loaded by Vite, used to initialise the development server */ +declare module '__sveltekit/dev-env' { + export const env: Record; +} + +/** Resolves to the adapter's `customHandler`, falling back to SvelteKit's default handler */ +declare module '__sveltekit/dev-handler' { + // eslint-disable-next-line no-duplicate-imports + import { SSRHandler } from '@sveltejs/kit'; + + const create_handler: SSRHandler; + export default create_handler; +} diff --git a/packages/kit/src/types/ambient.d.ts b/packages/kit/src/types/ambient.d.ts index 82bfdf30a4d4..288495ddc974 100644 --- a/packages/kit/src/types/ambient.d.ts +++ b/packages/kit/src/types/ambient.d.ts @@ -146,56 +146,3 @@ declare module '$app/types' { */ export type AssetPath = ReturnType; } - -/** - * Exports the `Server` class for creating custom server entry points. - * @example - * ```js - * import { env } from 'sveltekit:env'; - * import { Server } from 'sveltekit:server'; - * import { manifest } from 'sveltekit:server-manifest'; - * - * const server = new Server(manifest); - * - * await server.init({ env }); - * ``` - */ -declare module 'sveltekit:server' { - export { Server } from '@sveltejs/kit'; -} - -/** - * Exports the SSR manifest used to initialise the server. - * @example - * ```js - * import { env } from 'sveltekit:env'; - * import { Server } from 'sveltekit:server'; - * import { manifest } from 'sveltekit:server-manifest'; - * - * const server = new Server(manifest); - * - * await server.init({ env }); - * ``` - */ -declare module 'sveltekit:server-manifest' { - import { SSRManifest } from '@sveltejs/kit'; - - export const manifest: SSRManifest; -} - -/** - * Exports the environment variables loaded by Vite. Used when initialising the server. - * @example - * ```js - * import { env } from 'sveltekit:env'; - * import { Server } from 'sveltekit:server'; - * import { manifest } from 'sveltekit:server-manifest'; - * - * const server = new Server(manifest); - * - * await server.init({ env }); - * ``` - */ -declare module 'sveltekit:env' { - export const env: Record; -} diff --git a/packages/kit/test/apps/basics/test/server.test.js b/packages/kit/test/apps/basics/test/server.test.js index bf2607ee2173..d7e45c1c9ce9 100644 --- a/packages/kit/test/apps/basics/test/server.test.js +++ b/packages/kit/test/apps/basics/test/server.test.js @@ -1557,3 +1557,7 @@ test.describe('Streaming', () => { expect(error).toBeUndefined(); }); }); + +// TODO: test adapter intercepted request + +// TODO: test adapter modified request diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 5a09f58a8fdf..cf3a7b8a0dbd 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -45,6 +45,26 @@ declare module '@sveltejs/kit' { * during dev, build and prerendering. */ emulate?: () => MaybePromise; + /** + * The path to a module whose default export is a [`CustomHandler`](https://svelte.dev/docs/kit/writing-adapters#Providing-a-custom-handler), + * used in place of SvelteKit's own request handler. This lets the adapter run the same glue + * code during `vite dev` as it does in production, so that development more closely reflects + * the deployed app. + * + * The module is part of the Vite app, so it can use `$app/...` imports and other Vite features. + * Specify it with [`import.meta.resolve`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import.meta/resolve): + * + * ```js + * /** @type {import('@sveltejs/kit').Adapter} *\/ + * const adapter = { + * name: 'my-adapter', + * adapt() {}, + * customHandler: import.meta.resolve('./handler.js') + * }; + * ``` + * @since 3.0.0 + */ + customHandler?: string; vite?: { /** * Plugins provided by the adapter are placed before any of SvelteKit's own plugins. @@ -54,6 +74,45 @@ declare module '@sveltejs/kit' { }; } + /** + * Creates the function that handles requests during development, in place of SvelteKit's own. + * Specified via an adapter's [`customHandler`](https://svelte.dev/docs/kit/writing-adapters#Providing-a-custom-handler) + * path. + * + * The `server` is already initialised — you don't need to call `server.init(...)`. The `options` + * passed to your handler are ready to forward to `server.respond`, so the simplest possible + * handler is a passthrough: + * + * ```js + * /** @type {import('@sveltejs/kit').CustomHandler} *\/ + * export default (server) => (request, options) => server.respond(request, options); + * ``` + * + * Typically you would intercept platform-specific requests and add platform-specific data: + * + * ```js + * /** @type {import('@sveltejs/kit').CustomHandler} *\/ + * export default (server) => { + * return (request, options) => { + * if (new URL(request.url).pathname === '/_platform/image') { + * return new Response('intercepted'); + * } + * + * return server.respond(request, { + * ...options, + * platform: { + * // the shape of `App.Platform` + * } + * }); + * }; + * }; + * ``` + * @since 3.0.0 + */ + export type CustomHandler = ( + server: Server + ) => MaybePromise<(request: Request, options: RequestOptions) => MaybePromise>; + export type LoadProperties | void> = input extends void ? undefined // needs to be undefined, because void will break intellisense : input extends Record @@ -3507,9 +3566,9 @@ declare module '$app/server' { * * */ function live(fn: (arg: void) => RemoteLiveQueryUserFunctionReturnType): RemoteLiveQueryFunction; - + function live(validate: "unchecked", fn: (arg: Input) => RemoteLiveQueryUserFunctionReturnType): RemoteLiveQueryFunction; - + function live(schema: Schema, fn: (arg: StandardSchemaV1.InferOutput) => RemoteLiveQueryUserFunctionReturnType): RemoteLiveQueryFunction, Output, StandardSchemaV1.InferOutput>; } /** @@ -3836,57 +3895,4 @@ declare module '$app/types' { export type AssetPath = ReturnType; } -/** - * Exports the `Server` class for creating custom server entry points. - * @example - * ```js - * import { env } from 'sveltekit:env'; - * import { Server } from 'sveltekit:server'; - * import { manifest } from 'sveltekit:server-manifest'; - * - * const server = new Server(manifest); - * - * await server.init({ env }); - * ``` - */ -declare module 'sveltekit:server' { - export { Server } from '@sveltejs/kit'; -} - -/** - * Exports the SSR manifest used to initialise the server. - * @example - * ```js - * import { env } from 'sveltekit:env'; - * import { Server } from 'sveltekit:server'; - * import { manifest } from 'sveltekit:server-manifest'; - * - * const server = new Server(manifest); - * - * await server.init({ env }); - * ``` - */ -declare module 'sveltekit:server-manifest' { - import { SSRManifest } from '@sveltejs/kit'; - - export const manifest: SSRManifest; -} - -/** - * Exports the environment variables loaded by Vite. Used when initialising the server. - * @example - * ```js - * import { env } from 'sveltekit:env'; - * import { Server } from 'sveltekit:server'; - * import { manifest } from 'sveltekit:server-manifest'; - * - * const server = new Server(manifest); - * - * await server.init({ env }); - * ``` - */ -declare module 'sveltekit:env' { - export const env: Record; -} - -//# sourceMappingURL=index.d.ts.map \ No newline at end of file +//# sourceMappingURL=index.d.ts.map diff --git a/playgrounds/basic/tsconfig.json b/playgrounds/basic/tsconfig.json index a17ce35075a3..8220dd554a2a 100644 --- a/playgrounds/basic/tsconfig.json +++ b/playgrounds/basic/tsconfig.json @@ -1,5 +1,5 @@ { "extends": "$app/tsconfig", - "include": ["src"], + "include": ["src", "vite.config.js"], "exclude": ["src/service-worker"] } diff --git a/playgrounds/basic/vite.config.js b/playgrounds/basic/vite.config.js index 6b5a7e8a064a..498307c4de5b 100644 --- a/playgrounds/basic/vite.config.js +++ b/playgrounds/basic/vite.config.js @@ -1,8 +1,9 @@ import { enhancedImages } from '@sveltejs/enhanced-img'; import { sveltekit } from '@sveltejs/kit/vite'; import adapter from '@sveltejs/adapter-node'; +import { defineConfig } from 'vite'; -export default { +export default defineConfig({ plugins: [ enhancedImages(), sveltekit({ @@ -23,4 +24,4 @@ export default { allow: ['../../packages/kit'] } } -}; +}); From 704ef2d129e8647616cebb76ed0d1e7eaf777d27 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Tue, 28 Jul 2026 05:28:37 +0800 Subject: [PATCH 053/109] make it work in prerender and preview --- packages/kit/src/core/adapt/builder.js | 3 +- packages/kit/src/core/postbuild/fallback.js | 42 ++++++++--- packages/kit/src/core/postbuild/prerender.js | 72 ++++++++++++------- packages/kit/src/exports/public.d.ts | 2 +- packages/kit/src/exports/vite/dev/handler.js | 9 +-- packages/kit/src/exports/vite/dev/index.js | 7 +- .../kit/src/exports/vite/dev/ssr_entry.js | 4 +- packages/kit/src/exports/vite/index.js | 10 ++- .../kit/src/exports/vite/preview/index.js | 33 ++++----- packages/kit/src/types/internal.d.ts | 31 ++++---- packages/kit/types/index.d.ts | 53 +++++--------- 11 files changed, 148 insertions(+), 118 deletions(-) diff --git a/packages/kit/src/core/adapt/builder.js b/packages/kit/src/core/adapt/builder.js index d871e45ff2c4..af87d4e183d9 100644 --- a/packages/kit/src/core/adapt/builder.js +++ b/packages/kit/src/core/adapt/builder.js @@ -138,7 +138,8 @@ export function create_builder({ env, out_dir: config.kit.outDir, origin: config.kit.paths.origin || 'http://sveltekit-prerender', - assets: config.kit.files.assets + assets: config.kit.files.assets, + customHandler: config.adapter?.customHandler }); if (existsSync(dest)) { diff --git a/packages/kit/src/core/postbuild/fallback.js b/packages/kit/src/core/postbuild/fallback.js index 493be5283e1a..3cad1df0ecb3 100644 --- a/packages/kit/src/core/postbuild/fallback.js +++ b/packages/kit/src/core/postbuild/fallback.js @@ -1,3 +1,4 @@ +/** @import { InternalServer } from 'types' */ import { readFileSync } from 'node:fs'; import { join } from 'node:path'; import { pathToFileURL } from 'node:url'; @@ -13,16 +14,17 @@ export default forked(import.meta.url, generate_fallback); * out_dir: string; * origin: string; * assets: string; + * customHandler: string | undefined; * }} opts */ -async function generate_fallback({ manifest_path, env, out_dir, origin, assets }) { +async function generate_fallback({ manifest_path, env, out_dir, origin, assets, customHandler }) { const server_root = join(out_dir, 'output'); /** @type {import('types').ServerInternalModule} */ const { set_building } = await import(pathToFileURL(`${server_root}/server/internal.js`).href); /** @type {import('types').ServerModule} */ - const { Server } = await import(pathToFileURL(`${server_root}/server/index.js`).href); + const { Server } = await import(pathToFileURL(`${server_root}/server/server.js`).href); /** @type {import('@sveltejs/kit').SSRManifest} */ const manifest = (await import(pathToFileURL(manifest_path).href)).manifest; @@ -32,16 +34,36 @@ async function generate_fallback({ manifest_path, env, out_dir, origin, assets } const server = new Server(manifest); await server.init({ env }); - const response = await server.respond(new Request(origin + '/[fallback]'), { + const original_respond = server.respond; + + /** @type {import('@sveltejs/kit').Server['respond'] | undefined} */ + let custom_respond; + + if (customHandler) { + /** @type {import('@sveltejs/kit').SSRHandler} */ + const handler = await import(pathToFileURL(`${server_root}/server/index.js`).href); + /** @type {InternalServer['respond']} */ + custom_respond = await handler(server, env); + } + + server.respond = (request, options) => { + return original_respond(request, { + ...options, + prerendering: { + fallback: true, + dependencies: new Map(), + remote_responses: new Map() + }, + read: (file) => readFileSync(join(assets, file)) + }); + }; + + const response = await /** @type {import('@sveltejs/kit').Server['respond']} */ ( + custom_respond ?? server.respond + )(new Request(origin + '/[fallback]'), { getClientAddress: () => { throw new Error('Cannot read clientAddress during prerendering'); - }, - prerendering: { - fallback: true, - dependencies: new Map(), - remote_responses: new Map() - }, - read: (file) => readFileSync(join(assets, file)) + } }); if (response.ok) { diff --git a/packages/kit/src/core/postbuild/prerender.js b/packages/kit/src/core/postbuild/prerender.js index f496a9f72f8d..a84b1f92fe9f 100644 --- a/packages/kit/src/core/postbuild/prerender.js +++ b/packages/kit/src/core/postbuild/prerender.js @@ -1,3 +1,5 @@ +/** @import { Server } from '@sveltejs/kit' */ +/** @import { InternalServer } from 'types' */ import { existsSync, readFileSync, statSync, writeFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { pathToFileURL } from 'node:url'; @@ -56,7 +58,7 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit set_env(env); /** @type {import('types').ServerModule} */ - const { Server } = await import(pathToFileURL(`${out}/server/index.js`).href); + const { Server } = await import(pathToFileURL(`${out}/server/server.js`).href); const throw_handled = () => { throw new Error('__handled__'); @@ -150,7 +152,8 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit env, out_dir: config.outDir, origin: prerender_origin, - assets: config.files.assets + assets: config.files.assets, + customHandler: config.adapter?.customHandler }); const file = output_filename('/', true); @@ -301,29 +304,38 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit const request = new Request(prerender_origin + encoded); - const response = await server.respond(request, { - getClientAddress() { - throw new Error('Cannot read clientAddress during prerendering'); - }, - prerendering: { - dependencies, - remote_responses - }, - read: (file) => { - // stuff we just wrote - const filepath = saved.get(file); - if (filepath) return readFileSync(filepath); - - // Static assets emitted during build - if (file.startsWith(config.appDir)) { - return readFileSync(`${out}/server/${file}`); - } + server.respond = (request, options) => { + return original_respond(request, { + ...options, + prerendering: { + dependencies, + remote_responses + }, + read: (file) => { + // stuff we just wrote + const filepath = saved.get(file); + if (filepath) return readFileSync(filepath); + + // Static assets emitted during build + if (file.startsWith(config.appDir)) { + return readFileSync(`${out}/server/${file}`); + } - // stuff in `static` - return readFileSync(join(config.files.assets, file)); - }, - emulator - }); + // stuff in `static` + return readFileSync(join(config.files.assets, file)); + }, + emulator + }); + }; + + const response = await /** @type {Server['respond']} */ (custom_respond ?? server.respond)( + request, + { + getClientAddress() { + throw new Error('Cannot read clientAddress during prerendering'); + } + } + ); const encoded_id = response.headers.get('x-sveltekit-routeid'); const decoded_id = encoded_id && decode_uri(encoded_id); @@ -594,6 +606,18 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit read: (file) => createReadableStream(`${config.outDir}/output/server/${file}`) }); + const original_respond = server.respond; + + /** @type {Server['respond'] | undefined} */ + let custom_respond; + + if (config.adapter?.customHandler) { + /** @type {import('@sveltejs/kit').SSRHandler} */ + const handler = await import(pathToFileURL(`${out}/server/index.js`).href); + /** @type {InternalServer['respond']} */ + custom_respond = await handler(server, env); + } + log.info('Prerendering'); for (const entry of config.prerender.entries) { diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index 7742d9067b76..6f65272912b5 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -118,7 +118,7 @@ export type SSRHandler = ( server: Server, /** Environment variables loaded by Vite */ env: Record -) => Promise<(request: Request, address: string | undefined) => Promise>; +) => Promise<(request: Request, options: Omit) => Promise>; export type LoadProperties | void> = input extends void ? undefined // needs to be undefined, because void will break intellisense diff --git a/packages/kit/src/exports/vite/dev/handler.js b/packages/kit/src/exports/vite/dev/handler.js index cfc51f6e012a..7217b1250e89 100644 --- a/packages/kit/src/exports/vite/dev/handler.js +++ b/packages/kit/src/exports/vite/dev/handler.js @@ -11,12 +11,5 @@ export default async function (server, env) { read: (file) => createReadableStream(file) }); - return (request, address) => { - return server.respond(request, { - getClientAddress() { - if (!address) throw new Error('Could not determine clientAddress'); - return address; - } - }); - }; + return server.respond; } diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index c1f632e505ff..6ef373e80f37 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -295,7 +295,12 @@ export function dev(vite, vite_config, svelte_config, remotes, root, set_manifes await runner.import(sveltekit_dev_server) ).default; - const rendered = await handle(request, req.socket.remoteAddress); + const rendered = await handle(request, { + getClientAddress() { + if (!req.socket.remoteAddress) throw new Error('Could not determine clientAddress'); + return req.socket.remoteAddress; + } + }); if (rendered.status === 404) { // @ts-expect-error diff --git a/packages/kit/src/exports/vite/dev/ssr_entry.js b/packages/kit/src/exports/vite/dev/ssr_entry.js index acaa3a7b6d98..453117088a7e 100644 --- a/packages/kit/src/exports/vite/dev/ssr_entry.js +++ b/packages/kit/src/exports/vite/dev/ssr_entry.js @@ -7,6 +7,6 @@ import { manifest } from './ssr_manifest.js'; /** @type {InternalServer} */ const server = new Server(manifest); -const handler = await init_server(server, env); +const handle = await init_server(server, env); -export default handler; +export default handle; diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index cc1dcbbce086..69c745386033 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -70,7 +70,7 @@ import { SVELTE_KIT_ASSETS } from '../../constants.js'; import { get_runner } from '../../runner.js'; const dev_ssr_entry = posixify(path.join(import.meta.dirname, 'dev/ssr_entry.js')); -const dev_default_handler = posixify(path.join(import.meta.dirname, 'dev/handler.js')); +const default_handler = posixify(path.join(import.meta.dirname, 'dev/handler.js')); /** @type {import('./types.js').EnforcedConfig} */ const enforced_config = { @@ -597,7 +597,7 @@ function kit({ svelte_config }) { if (id === sveltekit_dev_handler) { const custom_handler = kit.adapter?.customHandler; - if (!custom_handler) return dev_default_handler; + if (!custom_handler) return default_handler; const filepath = custom_handler.startsWith('file://') ? fileURLToPath(custom_handler) @@ -1311,12 +1311,16 @@ function kit({ svelte_config }) { /** @type {Record} */ const server_input = { - index: `${runtime_directory}/server/index.js`, + server: `${runtime_directory}/server/index.js`, internal: `${out_dir}/generated/server/internal.js`, env: '__sveltekit/env', ['remote-entry']: `${runtime_directory}/app/server/remote/index.js` }; + if (kit.adapter?.customHandler) { + server_input.index = kit.adapter.customHandler; + } + // add entry points for every endpoint... manifest_data.routes.forEach((route) => { if (route.endpoint) { diff --git a/packages/kit/src/exports/vite/preview/index.js b/packages/kit/src/exports/vite/preview/index.js index e9b6051ef775..2ec1ba80f294 100644 --- a/packages/kit/src/exports/vite/preview/index.js +++ b/packages/kit/src/exports/vite/preview/index.js @@ -1,6 +1,6 @@ /** @import { NextHandleFunction } from 'connect' */ /** @import { PreviewServer, ResolvedConfig } from 'vite' */ -/** @import { ValidatedConfig, ServerInternalModule, ServerModule } from 'types' */ +/** @import { ValidatedConfig, ServerInternalModule } from 'types' */ import fs from 'node:fs'; import { join } from 'node:path'; import { pathToFileURL } from 'node:url'; @@ -40,18 +40,21 @@ export async function preview(vite, vite_config, svelte_config) { /** @type {ServerInternalModule} */ const { set_assets } = await import(pathToFileURL(join(dir, 'internal.js')).href); - /** @type {ServerModule} */ - const { Server } = await import(pathToFileURL(join(dir, 'index.js')).href); + /** @type {typeof import('@sveltejs/kit').Server} */ + const Server = (await import(pathToFileURL(join(dir, 'server.js')).href)).Server; - const { manifest } = await import(pathToFileURL(join(dir, 'manifest.js')).href); + /** @type {import('@sveltejs/kit').SSRManifest} */ + const manifest = (await import(pathToFileURL(join(dir, 'manifest.js')).href)).manifest; set_assets(assets); const server = new Server(manifest); + const env = loadEnv(vite_config.mode, svelte_config.kit.env.dir, ''); + try { await server.init({ - env: loadEnv(vite_config.mode, svelte_config.kit.env.dir, ''), + env, read: (file) => createReadableStream(`${dir}/${file}`) }); } catch (error) { @@ -62,7 +65,13 @@ export async function preview(vite, vite_config, svelte_config) { throw error; } - const emulator = await svelte_config.kit.adapter?.emulate?.(); + let respond = server.respond; + + if (svelte_config.adapter?.customHandler) { + /** @type {import('@sveltejs/kit').SSRHandler} */ + const handler = await import(pathToFileURL(`${dir}/server/index.js`).href); + respond = await handler(server, env); + } return () => { // Remove the base middleware. It screws with the URL. @@ -211,20 +220,12 @@ export async function preview(vite, vite_config, svelte_config) { setResponse( res, - await server.respond(request, { + await respond(request, { getClientAddress: () => { const { remoteAddress } = req.socket; if (remoteAddress) return remoteAddress; throw new Error('Could not determine clientAddress'); - }, - read: (file) => { - if (file in manifest._.server_assets) { - return fs.readFileSync(join(dir, file)); - } - - return fs.readFileSync(join(svelte_config.kit.files.assets, file)); - }, - emulator + } }) ); }); diff --git a/packages/kit/src/types/internal.d.ts b/packages/kit/src/types/internal.d.ts index 1eeb6f6dee94..cfb305d51aef 100644 --- a/packages/kit/src/types/internal.d.ts +++ b/packages/kit/src/types/internal.d.ts @@ -174,24 +174,23 @@ export interface Env { public: Record; } +interface InternalRequestOptions extends RequestOptions { + prerendering?: PrerenderOptions; + /** @internal for saving dependencies during prerendering and generating fallback pages */ + read: (file: string) => Buffer; + /** @internal used during development to check feature availability depending on the current route */ + before_handle?: ( + event: RequestEvent, + config: any, + prerender: PrerenderOption, + handle: () => Promise + ) => Promise; + emulator?: Emulator; +} + export class InternalServer extends Server { init(options: ServerInitOptions): Promise; - respond( - request: Request, - options: RequestOptions & { - prerendering?: PrerenderOptions; - /** @internal for saving dependencies during prerendering and generating fallback pages */ - read: (file: string) => Buffer; - /** @internal used during development to check feature availability depending on the current route */ - before_handle?: ( - event: RequestEvent, - config: any, - prerender: PrerenderOption, - handle: () => Promise - ) => Promise; - emulator?: Emulator; - } - ): Promise; + respond(request: Request, options: InternalRequestOptions): Promise; } export interface ManifestData { diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index cf3a7b8a0dbd..a21b8af5e30e 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -46,14 +46,7 @@ declare module '@sveltejs/kit' { */ emulate?: () => MaybePromise; /** - * The path to a module whose default export is a [`CustomHandler`](https://svelte.dev/docs/kit/writing-adapters#Providing-a-custom-handler), - * used in place of SvelteKit's own request handler. This lets the adapter run the same glue - * code during `vite dev` as it does in production, so that development more closely reflects - * the deployed app. - * - * The module is part of the Vite app, so it can use `$app/...` imports and other Vite features. - * Specify it with [`import.meta.resolve`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import.meta/resolve): - * + * The path to a module whose default export is a [custom handler](https://svelte.dev/docs/kit/writing-adapters#Providing-a-custom-handler). * ```js * /** @type {import('@sveltejs/kit').Adapter} *\/ * const adapter = { @@ -75,31 +68,17 @@ declare module '@sveltejs/kit' { } /** - * Creates the function that handles requests during development, in place of SvelteKit's own. - * Specified via an adapter's [`customHandler`](https://svelte.dev/docs/kit/writing-adapters#Providing-a-custom-handler) - * path. - * - * The `server` is already initialised — you don't need to call `server.init(...)`. The `options` - * passed to your handler are ready to forward to `server.respond`, so the simplest possible - * handler is a passthrough: - * - * ```js - * /** @type {import('@sveltejs/kit').CustomHandler} *\/ - * export default (server) => (request, options) => server.respond(request, options); - * ``` - * * Typically you would intercept platform-specific requests and add platform-specific data: * * ```js - * /** @type {import('@sveltejs/kit').CustomHandler} *\/ - * export default (server) => { - * return (request, options) => { - * if (new URL(request.url).pathname === '/_platform/image') { - * return new Response('intercepted'); - * } + * export default async function (server, env) { + * await server.init({ env }); * + * return (request) => { * return server.respond(request, { - * ...options, + * getClientAddress() { + * throw new Error('Could not determine clientAddress'); + * }, * platform: { * // the shape of `App.Platform` * } @@ -109,9 +88,11 @@ declare module '@sveltejs/kit' { * ``` * @since 3.0.0 */ - export type CustomHandler = ( - server: Server - ) => MaybePromise<(request: Request, options: RequestOptions) => MaybePromise>; + export type SSRHandler = ( + server: Server, + /** Environment variables loaded by Vite */ + env: Record + ) => Promise<(request: Request, options: Omit) => Promise>; export type LoadProperties | void> = input extends void ? undefined // needs to be undefined, because void will break intellisense @@ -1820,7 +1801,7 @@ declare module '@sveltejs/kit' { export interface ServerInitOptions { /** A map of environment variables. */ - env: Record; + env: Record; /** A function that turns an asset filename into a `ReadableStream`. Required for the `read` export from `$app/server` to work. */ read?: (file: string) => MaybePromise; } @@ -1831,7 +1812,7 @@ declare module '@sveltejs/kit' { 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. */ + /** 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; @@ -3566,9 +3547,9 @@ declare module '$app/server' { * * */ function live(fn: (arg: void) => RemoteLiveQueryUserFunctionReturnType): RemoteLiveQueryFunction; - + function live(validate: "unchecked", fn: (arg: Input) => RemoteLiveQueryUserFunctionReturnType): RemoteLiveQueryFunction; - + function live(schema: Schema, fn: (arg: StandardSchemaV1.InferOutput) => RemoteLiveQueryUserFunctionReturnType): RemoteLiveQueryFunction, Output, StandardSchemaV1.InferOutput>; } /** @@ -3895,4 +3876,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 c282ab933eec5a33efc1c5c63e4541caac478039 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Tue, 28 Jul 2026 05:28:46 +0800 Subject: [PATCH 054/109] Update 99-writing-adapters.md --- .../docs/25-build-and-deploy/99-writing-adapters.md | 8 +++----- 1 file changed, 3 insertions(+), 5 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 83134de9490a..f3eba5e9b242 100644 --- a/documentation/docs/25-build-and-deploy/99-writing-adapters.md +++ b/documentation/docs/25-build-and-deploy/99-writing-adapters.md @@ -41,7 +41,7 @@ export default function (options) { } }, // TODO - customHandler: import.meta.resolve('./my-handler.js'), + customHandler: import.meta.resolve('./handler.js'), vite: { plugins: [ // add plugins here to integrate with Vite @@ -95,11 +95,9 @@ export default async (server, env) => { } }); - return (request, address) => { + return (request, options) => { return server.respond(request, { - getClientAddress() { - return address; - }, + ...options, platform: { // the shape of `App.Platform` } From 19a92465f98a917df2489217f08bacd25f5accaf Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Tue, 28 Jul 2026 05:28:51 +0800 Subject: [PATCH 055/109] update adapters --- packages/adapter-cloudflare/index.js | 2 +- packages/adapter-netlify/index.js | 4 ++-- packages/adapter-node/index.js | 2 +- packages/adapter-vercel/index.js | 8 ++++---- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/adapter-cloudflare/index.js b/packages/adapter-cloudflare/index.js index c39f57e61521..9505301b3007 100644 --- a/packages/adapter-cloudflare/index.js +++ b/packages/adapter-cloudflare/index.js @@ -119,7 +119,7 @@ export default function (options = {}) { // 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`, + SERVER: `./${posixify(path.relative(worker_dest_dir, builder.getServerDirectory()))}/server.js`, MANIFEST: `./${posixify(path.relative(worker_dest_dir, tmp))}/manifest.js`, ASSETS: assets_binding } diff --git a/packages/adapter-netlify/index.js b/packages/adapter-netlify/index.js index 2c1355b42425..12e728dd7ec4 100644 --- a/packages/adapter-netlify/index.js +++ b/packages/adapter-netlify/index.js @@ -123,7 +123,7 @@ function generate_serverless_functions({ builder, publish, split }) { builder.writeServer('.netlify/v1/server'); const replace = { - '0SERVER': './server/index.js' // digit prefix prevents CJS build from using this as a variable name, which would also get replaced + '0SERVER': './server/server.js' // digit prefix prevents CJS build from using this as a variable name, which would also get replaced }; builder.copy(files, '.netlify/v1', { replace, filter: (file) => !file.endsWith('edge.js') }); @@ -365,7 +365,7 @@ async function generate_edge_functions({ builder }) { builder.copy(`${files}/edge.js`, `${tmp}/entry.js`, { replace: { - '0SERVER': `${relativePath}/index.js`, + '0SERVER': `${relativePath}/server.js`, MANIFEST: './manifest.js' } }); diff --git a/packages/adapter-node/index.js b/packages/adapter-node/index.js index 90de3830cff3..9a34e913fe53 100644 --- a/packages/adapter-node/index.js +++ b/packages/adapter-node/index.js @@ -90,7 +90,7 @@ 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 === 'SERVER') return `${server}/server.js`; if (id === 'MANIFEST') return `${server}/manifest.js`; } }, diff --git a/packages/adapter-vercel/index.js b/packages/adapter-vercel/index.js index 13875c7e8187..579d704ad20f 100644 --- a/packages/adapter-vercel/index.js +++ b/packages/adapter-vercel/index.js @@ -56,15 +56,15 @@ const plugin = function (defaults = {}) { const dir = `${dirs.functions}/${name}.func`; const relativePath = path.posix.relative(tmp, builder.getServerDirectory()); - builder.copy(`${files}/serverless.js`, `${tmp}/index.js`, { + builder.copy(`${files}/serverless.js`, `${tmp}/server.js`, { replace: { - SERVER: `${relativePath}/index.js`, + SERVER: `${relativePath}/server.js`, MANIFEST: './manifest.js' } }); if (builder.hasServerInstrumentationFile()) { builder.instrument({ - entrypoint: `${tmp}/index.js`, + entrypoint: `${tmp}/server.js`, instrumentation: `${builder.getServerDirectory()}/instrumentation.server.js` }); } @@ -74,7 +74,7 @@ const plugin = function (defaults = {}) { `export const manifest = ${builder.generateManifest({ relativePath, routes })};\n` ); - await create_function_bundle(builder, `${tmp}/index.js`, dir, config); + await create_function_bundle(builder, `${tmp}/server.js`, dir, config); for (const asset of builder.findServerAssets(routes)) { // TODO use symlinks, once Build Output API supports doing so From c4e988e7b518d51f1cbe36db10218f6048e49861 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Tue, 28 Jul 2026 05:43:56 +0800 Subject: [PATCH 056/109] fix this needs to be an anonymous arrow function --- packages/kit/src/exports/vite/dev/handler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/src/exports/vite/dev/handler.js b/packages/kit/src/exports/vite/dev/handler.js index 7217b1250e89..3a4f13852309 100644 --- a/packages/kit/src/exports/vite/dev/handler.js +++ b/packages/kit/src/exports/vite/dev/handler.js @@ -11,5 +11,5 @@ export default async function (server, env) { read: (file) => createReadableStream(file) }); - return server.respond; + return (request, options) => server.respond(request, options); } From 9452c0f3538d6de1231a79b5d986d38ad2ea300b Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Tue, 28 Jul 2026 05:44:03 +0800 Subject: [PATCH 057/109] add debug condition --- packages/kit/src/exports/vite/dev/index.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 6ef373e80f37..a0aa02df75d7 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -1,6 +1,7 @@ /** @import { ViteDevServer } from 'vite' */ import fs from 'node:fs'; import path from 'node:path'; +import process from 'node:process'; import { URL } from 'node:url'; import { styleText } from 'node:util'; import sirv from 'sirv'; @@ -291,11 +292,11 @@ export function dev(vite, vite_config, svelte_config, remotes, root, set_manifes context.remotes = remotes; context.manifest_data = manifest_data; - const handle = /** @type {typeof import('./ssr_entry.js')} */ ( + const respond = /** @type {typeof import('./ssr_entry.js')} */ ( await runner.import(sveltekit_dev_server) ).default; - const rendered = await handle(request, { + const rendered = await respond(request, { getClientAddress() { if (!req.socket.remoteAddress) throw new Error('Could not determine clientAddress'); return req.socket.remoteAddress; @@ -314,6 +315,7 @@ export function dev(vite, vite_config, svelte_config, remotes, root, set_manifes } } catch (e) { const error = coalesce_to_error(e); + if (process.env.DEBUG) console.error(error); res.statusCode = 500; fix_stack_trace(error); res.end(error.stack || error.message); // handle `stackless` errors From d23caf0a1fa9f7f0be76d994731641b0f306c7e2 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Tue, 28 Jul 2026 05:54:29 +0800 Subject: [PATCH 058/109] set the right this context --- packages/kit/src/core/postbuild/fallback.js | 21 +++++---- packages/kit/src/core/postbuild/prerender.js | 45 +++++++++++--------- 2 files changed, 36 insertions(+), 30 deletions(-) diff --git a/packages/kit/src/core/postbuild/fallback.js b/packages/kit/src/core/postbuild/fallback.js index 3cad1df0ecb3..0dd896466d85 100644 --- a/packages/kit/src/core/postbuild/fallback.js +++ b/packages/kit/src/core/postbuild/fallback.js @@ -47,15 +47,18 @@ async function generate_fallback({ manifest_path, env, out_dir, origin, assets, } server.respond = (request, options) => { - return original_respond(request, { - ...options, - prerendering: { - fallback: true, - dependencies: new Map(), - remote_responses: new Map() - }, - read: (file) => readFileSync(join(assets, file)) - }); + return original_respond.apply(server, [ + request, + { + ...options, + prerendering: { + fallback: true, + dependencies: new Map(), + remote_responses: new Map() + }, + read: (file) => readFileSync(join(assets, file)) + } + ]); }; const response = await /** @type {import('@sveltejs/kit').Server['respond']} */ ( diff --git a/packages/kit/src/core/postbuild/prerender.js b/packages/kit/src/core/postbuild/prerender.js index a84b1f92fe9f..d44cf43a655f 100644 --- a/packages/kit/src/core/postbuild/prerender.js +++ b/packages/kit/src/core/postbuild/prerender.js @@ -305,27 +305,30 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit const request = new Request(prerender_origin + encoded); server.respond = (request, options) => { - return original_respond(request, { - ...options, - prerendering: { - dependencies, - remote_responses - }, - read: (file) => { - // stuff we just wrote - const filepath = saved.get(file); - if (filepath) return readFileSync(filepath); - - // Static assets emitted during build - if (file.startsWith(config.appDir)) { - return readFileSync(`${out}/server/${file}`); - } - - // stuff in `static` - return readFileSync(join(config.files.assets, file)); - }, - emulator - }); + return original_respond.apply(server, [ + request, + { + ...options, + prerendering: { + dependencies, + remote_responses + }, + read: (file) => { + // stuff we just wrote + const filepath = saved.get(file); + if (filepath) return readFileSync(filepath); + + // Static assets emitted during build + if (file.startsWith(config.appDir)) { + return readFileSync(`${out}/server/${file}`); + } + + // stuff in `static` + return readFileSync(join(config.files.assets, file)); + }, + emulator + } + ]); }; const response = await /** @type {Server['respond']} */ (custom_respond ?? server.respond)( From 497ac15af37a51120fa283d1fdf2e23bb2e6f4ba Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Tue, 28 Jul 2026 06:10:27 +0800 Subject: [PATCH 059/109] use bind --- packages/kit/src/core/postbuild/fallback.js | 27 +++++----- packages/kit/src/core/postbuild/prerender.js | 51 +++++++++---------- .../kit/src/exports/vite/preview/index.js | 2 +- 3 files changed, 37 insertions(+), 43 deletions(-) diff --git a/packages/kit/src/core/postbuild/fallback.js b/packages/kit/src/core/postbuild/fallback.js index 0dd896466d85..2405ceb833e7 100644 --- a/packages/kit/src/core/postbuild/fallback.js +++ b/packages/kit/src/core/postbuild/fallback.js @@ -34,31 +34,28 @@ async function generate_fallback({ manifest_path, env, out_dir, origin, assets, const server = new Server(manifest); await server.init({ env }); - const original_respond = server.respond; + const original_respond = server.respond.bind(server); /** @type {import('@sveltejs/kit').Server['respond'] | undefined} */ let custom_respond; if (customHandler) { /** @type {import('@sveltejs/kit').SSRHandler} */ - const handler = await import(pathToFileURL(`${server_root}/server/index.js`).href); + const init_server = await import(pathToFileURL(`${server_root}/server/index.js`).href); /** @type {InternalServer['respond']} */ - custom_respond = await handler(server, env); + custom_respond = await init_server(server, env); } server.respond = (request, options) => { - return original_respond.apply(server, [ - request, - { - ...options, - prerendering: { - fallback: true, - dependencies: new Map(), - remote_responses: new Map() - }, - read: (file) => readFileSync(join(assets, file)) - } - ]); + return original_respond(request, { + ...options, + prerendering: { + fallback: true, + dependencies: new Map(), + remote_responses: new Map() + }, + read: (file) => readFileSync(join(assets, file)) + }); }; const response = await /** @type {import('@sveltejs/kit').Server['respond']} */ ( diff --git a/packages/kit/src/core/postbuild/prerender.js b/packages/kit/src/core/postbuild/prerender.js index d44cf43a655f..97e204a69274 100644 --- a/packages/kit/src/core/postbuild/prerender.js +++ b/packages/kit/src/core/postbuild/prerender.js @@ -305,30 +305,27 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit const request = new Request(prerender_origin + encoded); server.respond = (request, options) => { - return original_respond.apply(server, [ - request, - { - ...options, - prerendering: { - dependencies, - remote_responses - }, - read: (file) => { - // stuff we just wrote - const filepath = saved.get(file); - if (filepath) return readFileSync(filepath); - - // Static assets emitted during build - if (file.startsWith(config.appDir)) { - return readFileSync(`${out}/server/${file}`); - } - - // stuff in `static` - return readFileSync(join(config.files.assets, file)); - }, - emulator - } - ]); + return original_respond(request, { + ...options, + prerendering: { + dependencies, + remote_responses + }, + read: (file) => { + // stuff we just wrote + const filepath = saved.get(file); + if (filepath) return readFileSync(filepath); + + // Static assets emitted during build + if (file.startsWith(config.appDir)) { + return readFileSync(`${out}/server/${file}`); + } + + // stuff in `static` + return readFileSync(join(config.files.assets, file)); + }, + emulator + }); }; const response = await /** @type {Server['respond']} */ (custom_respond ?? server.respond)( @@ -609,16 +606,16 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit read: (file) => createReadableStream(`${config.outDir}/output/server/${file}`) }); - const original_respond = server.respond; + const original_respond = server.respond.bind(server); /** @type {Server['respond'] | undefined} */ let custom_respond; if (config.adapter?.customHandler) { /** @type {import('@sveltejs/kit').SSRHandler} */ - const handler = await import(pathToFileURL(`${out}/server/index.js`).href); + const init_server = await import(pathToFileURL(`${out}/server/index.js`).href); /** @type {InternalServer['respond']} */ - custom_respond = await handler(server, env); + custom_respond = await init_server(server, env); } log.info('Prerendering'); diff --git a/packages/kit/src/exports/vite/preview/index.js b/packages/kit/src/exports/vite/preview/index.js index 2ec1ba80f294..06e79e315cb2 100644 --- a/packages/kit/src/exports/vite/preview/index.js +++ b/packages/kit/src/exports/vite/preview/index.js @@ -65,7 +65,7 @@ export async function preview(vite, vite_config, svelte_config) { throw error; } - let respond = server.respond; + let respond = server.respond.bind(server); if (svelte_config.adapter?.customHandler) { /** @type {import('@sveltejs/kit').SSRHandler} */ From 681b57901600d8019f94aae09965a70d98e17873 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Tue, 28 Jul 2026 06:21:21 +0800 Subject: [PATCH 060/109] missed a spot --- packages/kit/test/apps/basics/vite.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/test/apps/basics/vite.config.js b/packages/kit/test/apps/basics/vite.config.js index 2de19ee19ecf..f815013a8339 100644 --- a/packages/kit/test/apps/basics/vite.config.js +++ b/packages/kit/test/apps/basics/vite.config.js @@ -20,7 +20,7 @@ export default defineConfig({ name: 'test-adapter', adapt(builder) { builder.instrument({ - entrypoint: `${builder.getServerDirectory()}/index.js`, + entrypoint: `${builder.getServerDirectory()}/server.js`, instrumentation: `${builder.getServerDirectory()}/instrumentation.server.js`, module: { exports: ['Server'] From d6cb109df2d89971e8a048647c6b61a047315571 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Tue, 28 Jul 2026 06:36:01 +0800 Subject: [PATCH 061/109] fine we'll keep supporting emulate --- .../kit/src/exports/vite/preview/index.js | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/packages/kit/src/exports/vite/preview/index.js b/packages/kit/src/exports/vite/preview/index.js index 06e79e315cb2..5eff50115841 100644 --- a/packages/kit/src/exports/vite/preview/index.js +++ b/packages/kit/src/exports/vite/preview/index.js @@ -1,6 +1,6 @@ /** @import { NextHandleFunction } from 'connect' */ /** @import { PreviewServer, ResolvedConfig } from 'vite' */ -/** @import { ValidatedConfig, ServerInternalModule } from 'types' */ +/** @import { ValidatedConfig, ServerInternalModule, InternalServer } from 'types' */ import fs from 'node:fs'; import { join } from 'node:path'; import { pathToFileURL } from 'node:url'; @@ -65,12 +65,26 @@ export async function preview(vite, vite_config, svelte_config) { throw error; } - let respond = server.respond.bind(server); + const emulator = await svelte_config.kit.adapter?.emulate?.(); + + const original_respond = server.respond.bind(server); + + server.respond = async (request, options) => { + return await /** @type {InternalServer['respond']} */ (original_respond)(request, { + ...options, + // @ts-expect-error this is only needed for prerender + read: undefined, + emulator + }); + }; + + /** @type {import('@sveltejs/kit').Server['respond'] | undefined} */ + let custom_respond; if (svelte_config.adapter?.customHandler) { /** @type {import('@sveltejs/kit').SSRHandler} */ const handler = await import(pathToFileURL(`${dir}/server/index.js`).href); - respond = await handler(server, env); + custom_respond = await handler(server, env); } return () => { @@ -220,7 +234,7 @@ export async function preview(vite, vite_config, svelte_config) { setResponse( res, - await respond(request, { + await (custom_respond ?? server.respond)(request, { getClientAddress: () => { const { remoteAddress } = req.socket; if (remoteAddress) return remoteAddress; From f7bf2ae20a98f0a00b0acd0b6e89a1a4d37c4810 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Tue, 28 Jul 2026 07:52:11 +0800 Subject: [PATCH 062/109] log error --- packages/kit/src/exports/vite/dev/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index a0aa02df75d7..9be74800e585 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -315,9 +315,9 @@ export function dev(vite, vite_config, svelte_config, remotes, root, set_manifes } } catch (e) { const error = coalesce_to_error(e); - if (process.env.DEBUG) console.error(error); res.statusCode = 500; fix_stack_trace(error); + console.error(styleText(['bold', 'red'], error.message)); res.end(error.stack || error.message); // handle `stackless` errors } }); From d7246bb929df040815a2deb5938ded7a3c032da8 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Tue, 28 Jul 2026 07:52:32 +0800 Subject: [PATCH 063/109] oopsie --- packages/kit/src/exports/vite/dev/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 9be74800e585..f62cdd9ba0e5 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -245,7 +245,7 @@ export function dev(vite, vite_config, svelte_config, remotes, root, set_manifes ); if (resolved_instrumentation) { - if (svelte_config.kit.adapter && adapter_supports_instrumentation) { + if (svelte_config.kit.adapter && !adapter_supports_instrumentation) { throw new Error( `${resolved_instrumentation} is unsupported in ${svelte_config.kit.adapter.name}.` ); From 3c506e97166a89013d7e9ac08b247272338c0d72 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Tue, 28 Jul 2026 08:10:14 +0800 Subject: [PATCH 064/109] Update index.js --- packages/kit/src/exports/vite/dev/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index f62cdd9ba0e5..26047982d631 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -1,7 +1,6 @@ /** @import { ViteDevServer } from 'vite' */ import fs from 'node:fs'; import path from 'node:path'; -import process from 'node:process'; import { URL } from 'node:url'; import { styleText } from 'node:util'; import sirv from 'sirv'; From b6c119685201cac5eb235680b68d752143f10a9c Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Tue, 28 Jul 2026 08:17:32 +0800 Subject: [PATCH 065/109] Update 99-writing-adapters.md --- .../25-build-and-deploy/99-writing-adapters.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 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 f3eba5e9b242..28e08169ef3a 100644 --- a/documentation/docs/25-build-and-deploy/99-writing-adapters.md +++ b/documentation/docs/25-build-and-deploy/99-writing-adapters.md @@ -40,7 +40,7 @@ export default function (options) { // Return `false if it can't, or throw a descriptive error. } }, - // TODO + // Specify the path of a module to customise request handling customHandler: import.meta.resolve('./handler.js'), vite: { plugins: [ @@ -71,32 +71,32 @@ Where possible, we recommend putting the adapter output under the `build/` direc ## Custom request handler -You can add a `customHandler` property set to the path of a module that initialises the server and handles requests: +You can customise your server's initialisation and request handling by adding a `customHandler` property with a path to your handler file. ```js /** @type {import('@sveltejs/kit').Adapter} */ const adapter = { name: 'adapter-package-name', adapt() {}, - customHandler: import.meta.resolve('./handler.js') + customHandler: import.meta.resolve('./src/handler.js') }; ``` -The default export of the module is given an instance of `Server`. TODO +The handler file should export a default function that calls `server.init` with an implementation for [`read`]($app-server#read) (if it supports it). It should also return a function which returns a `Response` or calls `server.respond` with any [platform-specific context](types#Platform). ```js -/// file: handler.js -/** @type {import('@sveltejs/kit').CustomHandler} */ +/// file: src/handler.js +/** @type {import('@sveltejs/kit').SSRHandler} */ export default async (server, env) => { await server.init({ env, - read: () => { + read: (file) => { // how your platform reads files } }); - return (request, options) => { - return server.respond(request, { + return async (request, options) => { + return await server.respond(request, { ...options, platform: { // the shape of `App.Platform` From 26c78ec88ec9dc3da3e001dcefc7684a03cbb795 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Tue, 28 Jul 2026 08:34:23 +0800 Subject: [PATCH 066/109] try fix docs --- .../99-writing-adapters.md | 10 +++-- packages/kit/src/exports/public.d.ts | 38 +++++++++---------- packages/kit/types/index.d.ts | 38 +++++++++---------- 3 files changed, 44 insertions(+), 42 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 28e08169ef3a..128d799f43e1 100644 --- a/documentation/docs/25-build-and-deploy/99-writing-adapters.md +++ b/documentation/docs/25-build-and-deploy/99-writing-adapters.md @@ -75,10 +75,12 @@ You can customise your server's initialisation and request handling by adding a ```js /** @type {import('@sveltejs/kit').Adapter} */ -const adapter = { +export default { name: 'adapter-package-name', - adapt() {}, - customHandler: import.meta.resolve('./src/handler.js') + adapt(builder) { + // adapter implementation + }, + +++customHandler: import.meta.resolve('./src/handler.js')+++ }; ``` @@ -87,7 +89,7 @@ The handler file should export a default function that calls `server.init` with ```js /// file: src/handler.js /** @type {import('@sveltejs/kit').SSRHandler} */ -export default async (server, env) => { +export default async function (server, env) { await server.init({ env, read: (file) => { diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index ffbdc96e81d4..c22a37157efb 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -72,15 +72,7 @@ export interface Adapter { */ emulate?: () => MaybePromise; /** - * The path to a module whose default export is a [custom handler](https://svelte.dev/docs/kit/writing-adapters#Providing-a-custom-handler). - * ```js - * /** @type {import('@sveltejs/kit').Adapter} *\/ - * const adapter = { - * name: 'my-adapter', - * adapt() {}, - * customHandler: import.meta.resolve('./handler.js') - * }; - * ``` + * The path to a module whose default export is a [custom handler](https://svelte.dev/docs/kit/writing-adapters#Custom-request-handler). * @since 3.0.0 */ customHandler?: string; @@ -94,17 +86,20 @@ export interface Adapter { } /** - * Typically you would intercept platform-specific requests and add platform-specific data: + * A function that calls `server.init` with an implementation for [`read`](https://svelte.dev/docs/kit/$app-server#read) (if it supports it). It should also return a function which returns a `Response` or calls `server.respond` with any [platform-specific context](https://svelte.dev/docs/kit/types#Platform). * * ```js * export default async function (server, env) { - * await server.init({ env }); + * await server.init({ + * env, + * read: (file) => { + * // how your platform reads files + * }, + * }); * - * return (request) => { - * return server.respond(request, { - * getClientAddress() { - * throw new Error('Could not determine clientAddress'); - * }, + * return async (request, options) => { + * return await server.respond(request, { + * ...options, * platform: { * // the shape of `App.Platform` * } @@ -1435,7 +1430,10 @@ export interface NavigationLink extends NavigationBase { } export type Navigation = - NavigationExternal | NavigationFormSubmit | NavigationPopState | NavigationLink; + | NavigationExternal + | NavigationFormSubmit + | NavigationPopState + | NavigationLink; /** * The argument passed to [`beforeNavigate`](https://svelte.dev/docs/kit/$app-navigation#beforeNavigate) callbacks. @@ -1540,7 +1538,8 @@ export type ParamValue = string | number | boolean | bigint; * A param matcher definition passed to [`defineParams`](https://svelte.dev/docs/kit/@sveltejs-kit#defineParams). */ export type ParamDefinition = - ((param: string) => ParamValue | undefined) | StandardSchemaV1; + | ((param: string) => ParamValue | undefined) + | StandardSchemaV1; /** * The return type of [`defineParams`](https://svelte.dev/docs/kit/@sveltejs-kit#defineParams). @@ -1645,7 +1644,8 @@ export type LiveQueryRequestedResult = Iterable< }; export type RequestedResult = - QueryRequestedResult | LiveQueryRequestedResult; + | QueryRequestedResult + | LiveQueryRequestedResult; export interface RequestEvent< Params extends AppLayoutParams<'/'> = AppLayoutParams<'/'>, diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 9c4e8c3ccf44..8faa8b3452f7 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -46,15 +46,7 @@ declare module '@sveltejs/kit' { */ emulate?: () => MaybePromise; /** - * The path to a module whose default export is a [custom handler](https://svelte.dev/docs/kit/writing-adapters#Providing-a-custom-handler). - * ```js - * /** @type {import('@sveltejs/kit').Adapter} *\/ - * const adapter = { - * name: 'my-adapter', - * adapt() {}, - * customHandler: import.meta.resolve('./handler.js') - * }; - * ``` + * The path to a module whose default export is a [custom handler](https://svelte.dev/docs/kit/writing-adapters#Custom-request-handler). * @since 3.0.0 */ customHandler?: string; @@ -68,17 +60,20 @@ declare module '@sveltejs/kit' { } /** - * Typically you would intercept platform-specific requests and add platform-specific data: + * A function that calls `server.init` with an implementation for [`read`](https://svelte.dev/docs/kit/$app-server#read) (if it supports it). It should also return a function which returns a `Response` or calls `server.respond` with any [platform-specific context](https://svelte.dev/docs/kit/types#Platform). * * ```js * export default async function (server, env) { - * await server.init({ env }); + * await server.init({ + * env, + * read: (file) => { + * // how your platform reads files + * }, + * }); * - * return (request) => { - * return server.respond(request, { - * getClientAddress() { - * throw new Error('Could not determine clientAddress'); - * }, + * return async (request, options) => { + * return await server.respond(request, { + * ...options, * platform: { * // the shape of `App.Platform` * } @@ -1407,7 +1402,10 @@ declare module '@sveltejs/kit' { } export type Navigation = - NavigationExternal | NavigationFormSubmit | NavigationPopState | NavigationLink; + | NavigationExternal + | NavigationFormSubmit + | NavigationPopState + | NavigationLink; /** * The argument passed to [`beforeNavigate`](https://svelte.dev/docs/kit/$app-navigation#beforeNavigate) callbacks. @@ -1512,7 +1510,8 @@ declare module '@sveltejs/kit' { * A param matcher definition passed to [`defineParams`](https://svelte.dev/docs/kit/@sveltejs-kit#defineParams). */ export type ParamDefinition = - ((param: string) => ParamValue | undefined) | StandardSchemaV1; + | ((param: string) => ParamValue | undefined) + | StandardSchemaV1; /** * The return type of [`defineParams`](https://svelte.dev/docs/kit/@sveltejs-kit#defineParams). @@ -1615,7 +1614,8 @@ declare module '@sveltejs/kit' { }; export type RequestedResult = - QueryRequestedResult | LiveQueryRequestedResult; + | QueryRequestedResult + | LiveQueryRequestedResult; export interface RequestEvent< Params extends AppLayoutParams<'/'> = AppLayoutParams<'/'>, From a55dfa45de4bff4c38c661a41a33d8681202b79a Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Tue, 28 Jul 2026 08:54:59 +0800 Subject: [PATCH 067/109] format --- packages/kit/src/exports/public.d.ts | 11 +++-------- packages/kit/types/index.d.ts | 11 +++-------- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index c22a37157efb..7b5fbbceb5c4 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -1430,10 +1430,7 @@ export interface NavigationLink extends NavigationBase { } export type Navigation = - | NavigationExternal - | NavigationFormSubmit - | NavigationPopState - | NavigationLink; + NavigationExternal | NavigationFormSubmit | NavigationPopState | NavigationLink; /** * The argument passed to [`beforeNavigate`](https://svelte.dev/docs/kit/$app-navigation#beforeNavigate) callbacks. @@ -1538,8 +1535,7 @@ export type ParamValue = string | number | boolean | bigint; * A param matcher definition passed to [`defineParams`](https://svelte.dev/docs/kit/@sveltejs-kit#defineParams). */ export type ParamDefinition = - | ((param: string) => ParamValue | undefined) - | StandardSchemaV1; + ((param: string) => ParamValue | undefined) | StandardSchemaV1; /** * The return type of [`defineParams`](https://svelte.dev/docs/kit/@sveltejs-kit#defineParams). @@ -1644,8 +1640,7 @@ export type LiveQueryRequestedResult = Iterable< }; export type RequestedResult = - | QueryRequestedResult - | LiveQueryRequestedResult; + QueryRequestedResult | LiveQueryRequestedResult; export interface RequestEvent< Params extends AppLayoutParams<'/'> = AppLayoutParams<'/'>, diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 8faa8b3452f7..384ad671f8fc 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -1402,10 +1402,7 @@ declare module '@sveltejs/kit' { } export type Navigation = - | NavigationExternal - | NavigationFormSubmit - | NavigationPopState - | NavigationLink; + NavigationExternal | NavigationFormSubmit | NavigationPopState | NavigationLink; /** * The argument passed to [`beforeNavigate`](https://svelte.dev/docs/kit/$app-navigation#beforeNavigate) callbacks. @@ -1510,8 +1507,7 @@ declare module '@sveltejs/kit' { * A param matcher definition passed to [`defineParams`](https://svelte.dev/docs/kit/@sveltejs-kit#defineParams). */ export type ParamDefinition = - | ((param: string) => ParamValue | undefined) - | StandardSchemaV1; + ((param: string) => ParamValue | undefined) | StandardSchemaV1; /** * The return type of [`defineParams`](https://svelte.dev/docs/kit/@sveltejs-kit#defineParams). @@ -1614,8 +1610,7 @@ declare module '@sveltejs/kit' { }; export type RequestedResult = - | QueryRequestedResult - | LiveQueryRequestedResult; + QueryRequestedResult | LiveQueryRequestedResult; export interface RequestEvent< Params extends AppLayoutParams<'/'> = AppLayoutParams<'/'>, From 9d7d821fd7a18e8ca2435db8244fe4a252be9e77 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Tue, 28 Jul 2026 13:47:34 +0800 Subject: [PATCH 068/109] disable vite error overlay during tests --- packages/kit/src/runtime/server/index.js | 2 +- packages/kit/test/apps/async/vite.config.js | 4 ++++ packages/kit/test/apps/basics/vite.config.js | 3 +++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js index 911ec07ed280..8e13057be29a 100644 --- a/packages/kit/src/runtime/server/index.js +++ b/packages/kit/src/runtime/server/index.js @@ -139,7 +139,7 @@ export class Server { stack = args.error.stack ?? ''; } - if (__SVELTEKIT_DEV__ && message) { + if (__SVELTEKIT_DEV__ && !(args.error instanceof SvelteKitError) && message) { import.meta.hot?.send('vite:error', { type: 'error', err: { diff --git a/packages/kit/test/apps/async/vite.config.js b/packages/kit/test/apps/async/vite.config.js index d01ad4345392..5a3d39d9b721 100644 --- a/packages/kit/test/apps/async/vite.config.js +++ b/packages/kit/test/apps/async/vite.config.js @@ -1,4 +1,5 @@ import * as path from 'node:path'; +import process from 'node:process'; import { sveltekit } from '@sveltejs/kit/vite'; import { defineConfig } from 'vitest/config'; @@ -24,6 +25,9 @@ export default defineConfig({ server: { fs: { allow: [path.resolve('../../../src')] + }, + hmr: { + overlay: process.env.PLAYWRIGHT_TEST !== '1' } }, test: { diff --git a/packages/kit/test/apps/basics/vite.config.js b/packages/kit/test/apps/basics/vite.config.js index f815013a8339..60fc4bfcc272 100644 --- a/packages/kit/test/apps/basics/vite.config.js +++ b/packages/kit/test/apps/basics/vite.config.js @@ -93,6 +93,9 @@ export default defineConfig({ }, cors: { origin: '*' + }, + hmr: { + overlay: process.env.PLAYWRIGHT_TEST !== '1' } }, test: { From db3e67b434c4053fe0f23e1458862680225ff4ac Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Tue, 28 Jul 2026 13:52:24 +0800 Subject: [PATCH 069/109] fix docs --- packages/kit/src/exports/public.d.ts | 33 ++++++++-------------------- 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index 7b5fbbceb5c4..07e8c027a96d 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -86,27 +86,7 @@ export interface Adapter { } /** - * A function that calls `server.init` with an implementation for [`read`](https://svelte.dev/docs/kit/$app-server#read) (if it supports it). It should also return a function which returns a `Response` or calls `server.respond` with any [platform-specific context](https://svelte.dev/docs/kit/types#Platform). - * - * ```js - * export default async function (server, env) { - * await server.init({ - * env, - * read: (file) => { - * // how your platform reads files - * }, - * }); - * - * return async (request, options) => { - * return await server.respond(request, { - * ...options, - * platform: { - * // the shape of `App.Platform` - * } - * }); - * }; - * }; - * ``` + * The [custom handler](https://svelte.dev/docs/kit/writing-adapters#Custom-request-handler) an adapter can specify to customise request handling. It is expected to call `server.init` with an implementation for [`read`](https://svelte.dev/docs/kit/$app-server#read) (if it supports it) and return a function which returns a `Response` or calls `server.respond` with any [platform-specific context](https://svelte.dev/docs/kit/types#Platform). * @since 3.0.0 */ export type SSRHandler = ( @@ -1430,7 +1410,10 @@ export interface NavigationLink extends NavigationBase { } export type Navigation = - NavigationExternal | NavigationFormSubmit | NavigationPopState | NavigationLink; + | NavigationExternal + | NavigationFormSubmit + | NavigationPopState + | NavigationLink; /** * The argument passed to [`beforeNavigate`](https://svelte.dev/docs/kit/$app-navigation#beforeNavigate) callbacks. @@ -1535,7 +1518,8 @@ export type ParamValue = string | number | boolean | bigint; * A param matcher definition passed to [`defineParams`](https://svelte.dev/docs/kit/@sveltejs-kit#defineParams). */ export type ParamDefinition = - ((param: string) => ParamValue | undefined) | StandardSchemaV1; + | ((param: string) => ParamValue | undefined) + | StandardSchemaV1; /** * The return type of [`defineParams`](https://svelte.dev/docs/kit/@sveltejs-kit#defineParams). @@ -1640,7 +1624,8 @@ export type LiveQueryRequestedResult = Iterable< }; export type RequestedResult = - QueryRequestedResult | LiveQueryRequestedResult; + | QueryRequestedResult + | LiveQueryRequestedResult; export interface RequestEvent< Params extends AppLayoutParams<'/'> = AppLayoutParams<'/'>, From cba1b7b3a2ca76cc7c41db7e9e933487c4b8a8ae Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Tue, 28 Jul 2026 13:54:41 +0800 Subject: [PATCH 070/109] format again? what's wrong with prettier --- packages/kit/src/exports/public.d.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index 07e8c027a96d..3d170e68b3af 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -1410,10 +1410,7 @@ export interface NavigationLink extends NavigationBase { } export type Navigation = - | NavigationExternal - | NavigationFormSubmit - | NavigationPopState - | NavigationLink; + NavigationExternal | NavigationFormSubmit | NavigationPopState | NavigationLink; /** * The argument passed to [`beforeNavigate`](https://svelte.dev/docs/kit/$app-navigation#beforeNavigate) callbacks. @@ -1518,8 +1515,7 @@ export type ParamValue = string | number | boolean | bigint; * A param matcher definition passed to [`defineParams`](https://svelte.dev/docs/kit/@sveltejs-kit#defineParams). */ export type ParamDefinition = - | ((param: string) => ParamValue | undefined) - | StandardSchemaV1; + ((param: string) => ParamValue | undefined) | StandardSchemaV1; /** * The return type of [`defineParams`](https://svelte.dev/docs/kit/@sveltejs-kit#defineParams). @@ -1624,8 +1620,7 @@ export type LiveQueryRequestedResult = Iterable< }; export type RequestedResult = - | QueryRequestedResult - | LiveQueryRequestedResult; + QueryRequestedResult | LiveQueryRequestedResult; export interface RequestEvent< Params extends AppLayoutParams<'/'> = AppLayoutParams<'/'>, From c5b8c2a327075cb9b3662cc41134308b80d29857 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 28 Jul 2026 07:33:22 +0000 Subject: [PATCH 071/109] chore: autofix lint --- packages/kit/types/index.d.ts | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 384ad671f8fc..9d5b030945b2 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -60,27 +60,7 @@ declare module '@sveltejs/kit' { } /** - * A function that calls `server.init` with an implementation for [`read`](https://svelte.dev/docs/kit/$app-server#read) (if it supports it). It should also return a function which returns a `Response` or calls `server.respond` with any [platform-specific context](https://svelte.dev/docs/kit/types#Platform). - * - * ```js - * export default async function (server, env) { - * await server.init({ - * env, - * read: (file) => { - * // how your platform reads files - * }, - * }); - * - * return async (request, options) => { - * return await server.respond(request, { - * ...options, - * platform: { - * // the shape of `App.Platform` - * } - * }); - * }; - * }; - * ``` + * The [custom handler](https://svelte.dev/docs/kit/writing-adapters#Custom-request-handler) an adapter can specify to customise request handling. It is expected to call `server.init` with an implementation for [`read`](https://svelte.dev/docs/kit/$app-server#read) (if it supports it) and return a function which returns a `Response` or calls `server.respond` with any [platform-specific context](https://svelte.dev/docs/kit/types#Platform). * @since 3.0.0 */ export type SSRHandler = ( From 20698cd2d4945a902422dad8f0137cc77b4f7e36 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Tue, 28 Jul 2026 17:02:39 +0800 Subject: [PATCH 072/109] try fix docs --- .../99-writing-adapters.md | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 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 128d799f43e1..fcc641853c47 100644 --- a/documentation/docs/25-build-and-deploy/99-writing-adapters.md +++ b/documentation/docs/25-build-and-deploy/99-writing-adapters.md @@ -71,16 +71,27 @@ Where possible, we recommend putting the adapter output under the `build/` direc ## Custom request handler -You can customise your server's initialisation and request handling by adding a `customHandler` property with a path to your handler file. +You can customise your server's initialisation and request handling by adding a `customHandler` property resolved to the path of your handler file. ```js -/** @type {import('@sveltejs/kit').Adapter} */ -export default { - name: 'adapter-package-name', - adapt(builder) { - // adapter implementation - }, - +++customHandler: import.meta.resolve('./src/handler.js')+++ +// @errors: 2322 +// @filename: ambient.d.ts +type AdapterSpecificOptions = any; + +// @filename: index.js +// ---cut--- +/** @param {AdapterSpecificOptions} options */ +export default function (options) { + /** @type {import('@sveltejs/kit').Adapter} */ + const adapter = { + name: 'adapter-package-name', + async adapt(builder) { + // adapter implementation + }, + +++customHandler: import.meta.resolve('./src/handler.js')+++ + } + + return adapter; }; ``` @@ -105,5 +116,5 @@ export default async function (server, env) { } }); }; -}; +} ``` From a726414bc7a5c88ac1b96af1146aa458ad8f75a2 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Tue, 28 Jul 2026 17:04:15 +0800 Subject: [PATCH 073/109] order imports --- packages/kit/src/exports/vite/dev/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 26047982d631..9c0d167c86c1 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -4,6 +4,8 @@ import path from 'node:path'; import { URL } from 'node:url'; import { styleText } from 'node:util'; import sirv from 'sirv'; +import { sveltekit_dev_server } from '../module_ids.js'; +import { fix_stack_trace } from './sourcemaps.js'; import { getRequest, setResponse } from '../../../exports/node/index.js'; import { coalesce_to_error } from '../../../utils/error.js'; import { resolve_entry } from '../../../utils/filesystem.js'; @@ -20,8 +22,6 @@ import { remote_module_pattern } from '../utils.js'; import { escape_html } from '../../../utils/escape.js'; -import { fix_stack_trace } from './sourcemaps.js'; -import { sveltekit_dev_server } from '../module_ids.js'; import { get_runner } from '../../../runner.js'; export const dev_context = posixify(path.join(import.meta.dirname, 'context.js')); From e5d69b225ed6e98adb8e3f9fe9d04c8852614df8 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Tue, 28 Jul 2026 17:55:05 +0800 Subject: [PATCH 074/109] split out change --- packages/kit/src/exports/vite/dev/index.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 9c0d167c86c1..d9b3e1194201 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -244,12 +244,6 @@ export function dev(vite, vite_config, svelte_config, remotes, root, set_manifes ); if (resolved_instrumentation) { - if (svelte_config.kit.adapter && !adapter_supports_instrumentation) { - throw new Error( - `${resolved_instrumentation} is unsupported in ${svelte_config.kit.adapter.name}.` - ); - } - await runner.import(resolved_instrumentation); } From 20234009833ccfafa83ed67cfb8925fb3a796589 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Tue, 28 Jul 2026 18:16:42 +0800 Subject: [PATCH 075/109] split out --- packages/kit/src/exports/vite/dev/index.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index d9b3e1194201..6ae16cbccd1e 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -173,8 +173,6 @@ export function dev(vite, vite_config, svelte_config, remotes, root, set_manifes vite.environments.ssr.hot.on('vite:error', vite.hot.send); - const adapter_supports_instrumentation = svelte_config.kit.adapter?.supports?.instrumentation?.(); - return () => { const serve_static_middleware = vite.middlewares.stack.find( (middleware) => @@ -310,7 +308,6 @@ export function dev(vite, vite_config, svelte_config, remotes, root, set_manifes const error = coalesce_to_error(e); res.statusCode = 500; fix_stack_trace(error); - console.error(styleText(['bold', 'red'], error.message)); res.end(error.stack || error.message); // handle `stackless` errors } }); From e8a1129d67bc9a422b0e53d8febf70b5ef3b44f7 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Tue, 28 Jul 2026 18:41:10 +0800 Subject: [PATCH 076/109] split out --- packages/kit/src/exports/public.d.ts | 3 +-- packages/kit/src/types/ambient-private.d.ts | 2 +- packages/kit/types/index.d.ts | 3 +-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index 3d170e68b3af..75aac340c782 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -91,8 +91,7 @@ export interface Adapter { */ export type SSRHandler = ( server: Server, - /** Environment variables loaded by Vite */ - env: Record + env: Record ) => Promise<(request: Request, options: Omit) => Promise>; export type LoadProperties | void> = input extends void diff --git a/packages/kit/src/types/ambient-private.d.ts b/packages/kit/src/types/ambient-private.d.ts index c5d7c15d74ed..7d63876eb3b4 100644 --- a/packages/kit/src/types/ambient-private.d.ts +++ b/packages/kit/src/types/ambient-private.d.ts @@ -25,7 +25,7 @@ declare module '__sveltekit/env' { // exported environment variables are defined in env.d.ts /** Populate exported environment variables */ - export function set_env(environment: Record): void; + export function set_env(environment: Record): void; /** public env vars */ export const explicit_public_env: Record; diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 9d5b030945b2..534217b80263 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -65,8 +65,7 @@ declare module '@sveltejs/kit' { */ export type SSRHandler = ( server: Server, - /** Environment variables loaded by Vite */ - env: Record + env: Record ) => Promise<(request: Request, options: Omit) => Promise>; export type LoadProperties | void> = input extends void From 0329d9e7f642e98ac1764cde771a7f0a4b0f5cf9 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Tue, 28 Jul 2026 18:42:26 +0800 Subject: [PATCH 077/109] ditto --- packages/kit/src/exports/public.d.ts | 2 +- packages/kit/types/index.d.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index 75aac340c782..9af0f9ba0f1d 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -1800,7 +1800,7 @@ export class Server { export interface ServerInitOptions { /** A map of environment variables. */ - env: Record; + env: Record; /** A function that turns an asset filename into a `ReadableStream`. Required for the `read` export from `$app/server` to work. */ read?: (file: string) => MaybePromise; } diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 534217b80263..81af3e3647e5 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -1770,7 +1770,7 @@ declare module '@sveltejs/kit' { export interface ServerInitOptions { /** A map of environment variables. */ - env: Record; + env: Record; /** A function that turns an asset filename into a `ReadableStream`. Required for the `read` export from `$app/server` to work. */ read?: (file: string) => MaybePromise; } From 03b5ff4c8f5d41f01b418fc717761377637e7ead Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Tue, 28 Jul 2026 18:47:43 +0800 Subject: [PATCH 078/109] split out --- playgrounds/basic/tsconfig.json | 2 +- playgrounds/basic/vite.config.js | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/playgrounds/basic/tsconfig.json b/playgrounds/basic/tsconfig.json index 8220dd554a2a..a17ce35075a3 100644 --- a/playgrounds/basic/tsconfig.json +++ b/playgrounds/basic/tsconfig.json @@ -1,5 +1,5 @@ { "extends": "$app/tsconfig", - "include": ["src", "vite.config.js"], + "include": ["src"], "exclude": ["src/service-worker"] } diff --git a/playgrounds/basic/vite.config.js b/playgrounds/basic/vite.config.js index 498307c4de5b..6b5a7e8a064a 100644 --- a/playgrounds/basic/vite.config.js +++ b/playgrounds/basic/vite.config.js @@ -1,9 +1,8 @@ import { enhancedImages } from '@sveltejs/enhanced-img'; import { sveltekit } from '@sveltejs/kit/vite'; import adapter from '@sveltejs/adapter-node'; -import { defineConfig } from 'vite'; -export default defineConfig({ +export default { plugins: [ enhancedImages(), sveltekit({ @@ -24,4 +23,4 @@ export default defineConfig({ allow: ['../../packages/kit'] } } -}); +}; From d99d5fd9b7affb5126924e1b8aa792cc7f02dfbb Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Tue, 28 Jul 2026 19:00:14 +0800 Subject: [PATCH 079/109] revert --- packages/kit/src/types/internal.d.ts | 31 ++++++++++++++-------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/packages/kit/src/types/internal.d.ts b/packages/kit/src/types/internal.d.ts index efb0f492a83e..3d5c723af32b 100644 --- a/packages/kit/src/types/internal.d.ts +++ b/packages/kit/src/types/internal.d.ts @@ -174,23 +174,24 @@ export interface Env { public: Record; } -interface InternalRequestOptions extends RequestOptions { - prerendering?: PrerenderOptions; - /** @internal for saving dependencies during prerendering and generating fallback pages */ - read: (file: string) => Buffer; - /** @internal used during development to check feature availability depending on the current route */ - before_handle?: ( - event: RequestEvent, - config: any, - prerender: PrerenderOption, - handle: () => Promise - ) => Promise; - emulator?: Emulator; -} - export class InternalServer extends Server { init(options: ServerInitOptions): Promise; - respond(request: Request, options: InternalRequestOptions): Promise; + respond( + request: Request, + options: RequestOptions & { + prerendering?: PrerenderOptions; + /** @internal for saving dependencies during prerendering and generating fallback pages */ + read: (file: string) => Buffer; + /** @internal used during development to check feature availability depending on the current route */ + before_handle?: ( + event: RequestEvent, + config: any, + prerender: PrerenderOption, + handle: () => Promise + ) => Promise; + emulator?: Emulator; + } + ): Promise; } export interface ManifestData { From e4bee61a46a445ddd3526eec15ddc55b748d1898 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Tue, 28 Jul 2026 21:33:00 +0800 Subject: [PATCH 080/109] add function name to docs snippet --- documentation/docs/25-build-and-deploy/99-writing-adapters.md | 4 +++- 1 file changed, 3 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 fcc641853c47..7ec5beb9c987 100644 --- a/documentation/docs/25-build-and-deploy/99-writing-adapters.md +++ b/documentation/docs/25-build-and-deploy/99-writing-adapters.md @@ -1,4 +1,5 @@ --- +NOTE: do not edit this file, it is generated in apps/svelte.dev/scripts/sync-docs/index.ts title: Writing adapters --- @@ -99,8 +100,9 @@ The handler file should export a default function that calls `server.init` with ```js /// file: src/handler.js +// @errors: 2322 /** @type {import('@sveltejs/kit').SSRHandler} */ -export default async function (server, env) { +export default async function handler(server, env) { await server.init({ env, read: (file) => { From 793c61871f271927c981624df86e5b99653602ed Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Tue, 28 Jul 2026 22:05:15 +0800 Subject: [PATCH 081/109] oops --- documentation/docs/25-build-and-deploy/99-writing-adapters.md | 1 - 1 file changed, 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 7ec5beb9c987..ef18dda88ff2 100644 --- a/documentation/docs/25-build-and-deploy/99-writing-adapters.md +++ b/documentation/docs/25-build-and-deploy/99-writing-adapters.md @@ -1,5 +1,4 @@ --- -NOTE: do not edit this file, it is generated in apps/svelte.dev/scripts/sync-docs/index.ts title: Writing adapters --- From 7ab6d0a5891e64efb97418080123139047ff0d75 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Tue, 28 Jul 2026 22:10:04 +0800 Subject: [PATCH 082/109] revert --- packages/kit/src/exports/vite/preview/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/kit/src/exports/vite/preview/index.js b/packages/kit/src/exports/vite/preview/index.js index 5eff50115841..2cf027678804 100644 --- a/packages/kit/src/exports/vite/preview/index.js +++ b/packages/kit/src/exports/vite/preview/index.js @@ -1,6 +1,6 @@ /** @import { NextHandleFunction } from 'connect' */ /** @import { PreviewServer, ResolvedConfig } from 'vite' */ -/** @import { ValidatedConfig, ServerInternalModule, InternalServer } from 'types' */ +/** @import { ValidatedConfig, ServerInternalModule, InternalServer, ServerModule } from 'types' */ import fs from 'node:fs'; import { join } from 'node:path'; import { pathToFileURL } from 'node:url'; @@ -40,8 +40,8 @@ export async function preview(vite, vite_config, svelte_config) { /** @type {ServerInternalModule} */ const { set_assets } = await import(pathToFileURL(join(dir, 'internal.js')).href); - /** @type {typeof import('@sveltejs/kit').Server} */ - const Server = (await import(pathToFileURL(join(dir, 'server.js')).href)).Server; + /** @type {ServerModule} */ + const { Server } = await import(pathToFileURL(join(dir, 'server.js')).href); /** @type {import('@sveltejs/kit').SSRManifest} */ const manifest = (await import(pathToFileURL(join(dir, 'manifest.js')).href)).manifest; From 750440373496b7ae1bcd81dd2cc50165c9cadad0 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Tue, 28 Jul 2026 22:14:53 +0800 Subject: [PATCH 083/109] revert --- packages/kit/src/exports/vite/index.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index c68e85528c61..5112155718ec 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -269,7 +269,6 @@ function kit({ svelte_config }) { /** @type {string | null} */ let service_worker_entry_file; - /** @type {string} */ let normalized_cwd; /** @type {Array<{ alias: string, path: string }>} */ @@ -386,6 +385,8 @@ function kit({ svelte_config }) { const client_hooks = resolve_entry(kit.files.hooks.client); if (client_hooks) allow.add(path.dirname(client_hooks)); + const generated = path.posix.join(out_dir, 'generated'); + // dev and preview config can be shared /** @type {UserConfig} */ const new_config = { @@ -399,7 +400,7 @@ function kit({ svelte_config }) { }, resolve: { alias: [ - { find: '__SERVER__', replacement: `${out_dir}/generated/server` }, + { find: '__SERVER__', replacement: `${generated}/server` }, { find: '$app', replacement: `${runtime_directory}/app` }, { find: '$env', replacement: `${runtime_directory}/env` }, { From d4359597e8a01b184d87d20a9e248d6762aec3c5 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Tue, 28 Jul 2026 22:26:04 +0800 Subject: [PATCH 084/109] remove one step of indirection --- packages/kit/src/exports/vite/dev/index.js | 5 +++-- packages/kit/src/exports/vite/index.js | 8 +------- packages/kit/src/exports/vite/module_ids.js | 1 - 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 6ae16cbccd1e..200e5a5de154 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -4,7 +4,6 @@ import path from 'node:path'; import { URL } from 'node:url'; import { styleText } from 'node:util'; import sirv from 'sirv'; -import { sveltekit_dev_server } from '../module_ids.js'; import { fix_stack_trace } from './sourcemaps.js'; import { getRequest, setResponse } from '../../../exports/node/index.js'; import { coalesce_to_error } from '../../../utils/error.js'; @@ -24,6 +23,8 @@ import { import { escape_html } from '../../../utils/escape.js'; import { get_runner } from '../../../runner.js'; +const dev_ssr_entry = posixify(path.join(import.meta.dirname, 'ssr_entry.js')); + export const dev_context = posixify(path.join(import.meta.dirname, 'context.js')); /** @@ -284,7 +285,7 @@ export function dev(vite, vite_config, svelte_config, remotes, root, set_manifes context.manifest_data = manifest_data; const respond = /** @type {typeof import('./ssr_entry.js')} */ ( - await runner.import(sveltekit_dev_server) + await runner.import(dev_ssr_entry) ).default; const rendered = await respond(request, { diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 5112155718ec..5cd3fc1bdc37 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -58,7 +58,6 @@ import { sveltekit_env_public_client, sveltekit_env_public_server, sveltekit_dev_env, - sveltekit_dev_server, sveltekit_dev_handler } from './module_ids.js'; import { import_peer } from '../../utils/import.js'; @@ -69,7 +68,6 @@ import { treeshake_prerendered_remotes } from './build/remote.js'; import { SVELTE_KIT_ASSETS } from '../../constants.js'; import { get_runner } from '../../runner.js'; -const dev_ssr_entry = posixify(path.join(import.meta.dirname, 'dev/ssr_entry.js')); const default_handler = posixify(path.join(import.meta.dirname, 'dev/handler.js')); /** @type {import('./types.js').EnforcedConfig} */ @@ -589,13 +587,9 @@ function kit({ svelte_config }) { resolveId: { filter: { - id: [exactRegex(sveltekit_dev_server), exactRegex(sveltekit_dev_handler)] + id: [exactRegex(sveltekit_dev_handler)] }, handler(id) { - if (id === sveltekit_dev_server) { - return dev_ssr_entry; - } - if (id === sveltekit_dev_handler) { const custom_handler = kit.adapter?.customHandler; if (!custom_handler) return default_handler; diff --git a/packages/kit/src/exports/vite/module_ids.js b/packages/kit/src/exports/vite/module_ids.js index b26b15aa18a7..5f0b6cbdaf39 100644 --- a/packages/kit/src/exports/vite/module_ids.js +++ b/packages/kit/src/exports/vite/module_ids.js @@ -15,7 +15,6 @@ export const sveltekit_manifest_data = '\0virtual:__sveltekit/manifest-data'; export const sveltekit_dev_env = '\0virtual:__sveltekit/dev-env'; /** Resolves to the adapter's `customHandler`, falling back to SvelteKit's default handler */ -export const sveltekit_dev_server = '__sveltekit/dev-server.js'; export const sveltekit_dev_handler = '__sveltekit/dev-handler'; export const app_server = posixify( From 05c41b2f39095b6b0993ac3e1d472264d1cca843 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Tue, 28 Jul 2026 22:54:49 +0800 Subject: [PATCH 085/109] split ssr manifest refactoring changes --- packages/kit/src/exports/public.d.ts | 2 +- packages/kit/src/exports/vite/dev/context.js | 45 -- packages/kit/src/exports/vite/dev/index.js | 460 ++++++++++++++++-- packages/kit/src/exports/vite/dev/server.js | 81 --- .../kit/src/exports/vite/dev/sourcemaps.js | 43 -- .../kit/src/exports/vite/dev/ssr_entry.js | 12 - .../kit/src/exports/vite/dev/ssr_manifest.js | 242 --------- packages/kit/src/exports/vite/index.js | 65 +-- packages/kit/src/exports/vite/module_ids.js | 1 - .../kit/src/exports/vite/preview/index.js | 4 +- packages/kit/src/types/ambient-private.d.ts | 9 +- packages/kit/src/utils/params.js | 8 +- packages/kit/types/index.d.ts | 2 +- 13 files changed, 457 insertions(+), 517 deletions(-) delete mode 100644 packages/kit/src/exports/vite/dev/context.js delete mode 100644 packages/kit/src/exports/vite/dev/server.js delete mode 100644 packages/kit/src/exports/vite/dev/sourcemaps.js delete mode 100644 packages/kit/src/exports/vite/dev/ssr_entry.js delete mode 100644 packages/kit/src/exports/vite/dev/ssr_manifest.js diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index 9af0f9ba0f1d..0db40580bfa8 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -1811,7 +1811,7 @@ export interface ServerInitOptions { 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. */ + /** 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; diff --git a/packages/kit/src/exports/vite/dev/context.js b/packages/kit/src/exports/vite/dev/context.js deleted file mode 100644 index 98a8336c05ab..000000000000 --- a/packages/kit/src/exports/vite/dev/context.js +++ /dev/null @@ -1,45 +0,0 @@ -/** @import { ManifestData, ValidatedConfig } from 'types' */ -/** @import { ViteDevServer } from 'vite' */ - -/** - * @typedef {object} Context - * @property {ValidatedConfig} svelte_config - * @property {ViteDevServer} vite - * @property {ManifestData} manifest_data - * @property {Array<{ hash: string, file: string }>} remotes - */ - -class DevContext { - /** @type {Context} */ - context = import.meta.hot?.data ?? {}; - - get svelte_config() { - return this.context.svelte_config; - } - set svelte_config(config) { - this.context.svelte_config = config; - } - - get vite() { - return this.context.vite; - } - set vite(vite) { - this.context.vite = vite; - } - - get manifest_data() { - return this.context.manifest_data; - } - set manifest_data(manifest_data) { - this.context.manifest_data = manifest_data; - } - - get remotes() { - return this.context.remotes; - } - set remotes(remotes) { - this.context.remotes = remotes; - } -} - -export const dev = new DevContext(); diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 200e5a5de154..0714848bfafc 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -1,55 +1,146 @@ -/** @import { ViteDevServer } from 'vite' */ +/** @import { RequestEvent } from '@sveltejs/kit' */ +/** @import { PrerenderOption, UniversalNode } from 'types' */ +import process from 'node:process'; import fs from 'node:fs'; import path from 'node:path'; import { URL } from 'node:url'; +import { AsyncLocalStorage } from 'node:async_hooks'; import { styleText } from 'node:util'; import sirv from 'sirv'; -import { fix_stack_trace } from './sourcemaps.js'; +import { isCSSRequest, loadEnv, buildErrorMessage } from 'vite'; import { getRequest, setResponse } from '../../../exports/node/index.js'; import { coalesce_to_error } from '../../../utils/error.js'; import { resolve_entry } from '../../../utils/filesystem.js'; -import { to_fs } from '../../../utils/vite.js'; +import { load_and_validate_params } from '../../../utils/params.js'; +import { from_fs, to_fs } from '../../../utils/vite.js'; import { posixify } from '../../../utils/os.js'; import { load_error_page } from '../../../core/config/index.js'; -import { SVELTE_KIT_ASSETS } from '../../../constants.js'; +import { SRC_ROOT, SVELTE_KIT_ASSETS } from '../../../constants.js'; import * as sync from '../../../core/sync/sync.js'; +import { get_mime_lookup, get_runtime_base } from '../../../core/utils.js'; import '../../../utils/mime.js'; // extend mrmime with additional types (affects sirv too) +import { compact } from '../../../utils/array.js'; import { is_chrome_devtools_request, log_response, not_found, remote_module_pattern } from '../utils.js'; +import { SCHEME } from '../../../utils/url.js'; +import { check_feature } from '../../../utils/features.js'; import { escape_html } from '../../../utils/escape.js'; import { get_runner } from '../../../runner.js'; -const dev_ssr_entry = posixify(path.join(import.meta.dirname, 'ssr_entry.js')); - -export const dev_context = posixify(path.join(import.meta.dirname, 'context.js')); +// vite-specifc queries that we should skip handling for css urls +const vite_css_query_regex = /(?:\?|&)(?:raw|url|inline)(?:&|$)/; /** * @param {import('vite').ViteDevServer} vite * @param {import('vite').ResolvedConfig} vite_config * @param {import('types').ValidatedConfig} svelte_config - * @param {import('types').RemoteChunk[]} remotes + * @param {() => Array<{ hash: string, file: string }>} get_remotes * @param {string} root The project root directory * @param {(manifest_data: import('types').ManifestData) => void} set_manifest_data - * @return {() => void} + * @return {Promise void>>} */ -export function dev(vite, vite_config, svelte_config, remotes, root, set_manifest_data) { +export async function dev(vite, vite_config, svelte_config, get_remotes, root, set_manifest_data) { + /** @type {AsyncLocalStorage<{ event: RequestEvent, config: any, prerender: PrerenderOption }>} */ + const async_local_storage = new AsyncLocalStorage(); + + globalThis.__SVELTEKIT_TRACK__ = (label) => { + const context = async_local_storage.getStore(); + if (!context || context.prerender === true) return; + + check_feature( + /** @type {string} */ (context.event.route.id), + context.config, + label, + svelte_config.kit.adapter + ); + }; + + const fetch = globalThis.fetch; + globalThis.fetch = (info, init) => { + if (typeof info === 'string' && !SCHEME.test(info)) { + throw new Error( + `Cannot use relative URL (${info}) with global fetch — use \`event.fetch\` instead: https://svelte.dev/docs/kit/web-standards#fetch-apis` + ); + } + + return fetch(info, init); + }; + sync.init(svelte_config, root); - /** @type {import('types').ManifestData | undefined} */ + /** @type {import('types').ManifestData} */ let manifest_data; + /** @type {import('@sveltejs/kit').SSRManifest} */ + let manifest; /** @type {Error | null} */ let manifest_error = null; - function update_manifest() { + const runner = get_runner(vite); + + /** + * @param {string} url + * @returns {Promise>} + */ + async function loud_ssr_load_module(url) { + try { + return await runner.import(url); + } catch (/** @type {any} */ err) { + const msg = buildErrorMessage(err, [ + styleText('red', `Internal server error: ${err.message}`) + ]); + + if (!vite.config.logger.hasErrorLogged(err)) { + vite.config.logger.error(msg, { error: err }); + } + + // TODO this is inadequate — it doesn't reliably show the overlay on every page load, + // and when it does appear it may immediately vanish. `vite.hot.send` broadcasts + // to all connected clients, even ones that are unaffected by the error. + // we need a more considered approach + vite.hot.send({ + type: 'error', + err: /** @type {import('vite').ErrorPayload['err']} */ ({ + ...err, + // these properties are non-enumerable and will + // not be serialized unless we explicitly include them + message: err.message, + stack: err.stack ?? '' + }) + }); + + throw err; + } + } + + /** @param {string} id */ + async function resolve(id) { + const url = id.startsWith('..') ? to_fs(path.resolve(id)) : `/${id}`; + + const module = await loud_ssr_load_module(url); + + const module_node = await vite.environments.ssr.moduleGraph.getModuleByUrl(url); + if (!module_node) throw new Error(`Could not find node for ${url}`); + + return { module, module_node, url }; + } + + async function update_manifest() { try { ({ manifest_data } = sync.create(svelte_config, root)); set_manifest_data(manifest_data); + await load_and_validate_params({ + routes: manifest_data.routes, + params_path: manifest_data.params, + root, + load: (file) => loud_ssr_load_module(file) + }); + if (manifest_error) { manifest_error = null; vite.hot.send({ type: 'full-reload' }); @@ -68,9 +159,219 @@ export function dev(vite, vite_config, svelte_config, remotes, root, set_manifes return; } + + manifest = { + appDir: svelte_config.kit.appDir, + appPath: svelte_config.kit.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.kit.outDir)}/generated/client/app.js`, + imports: [], + stylesheets: [], + fonts: [], + uses_env_dynamic_public: true, + nodes: + svelte_config.kit.router.resolution === 'client' + ? undefined + : manifest_data.nodes.map((node, i) => { + if (node.component || node.universal) { + return `${svelte_config.kit.paths.base}${to_fs(svelte_config.kit.outDir)}/generated/client/nodes/${i}.js`; + } + }), + // `css` is not necessary in dev, as the JS file from `nodes` will reference the CSS file + routes: + svelte_config.kit.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 () => { + /** @type {import('types').SSRNode} */ + const result = {}; + 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 {import('vite').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.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(); + + for (const module_node of module_nodes) { + await find_deps(vite, module_node, deps); + } + + /** @type {Record} */ + const styles = {}; + + for (const dep of deps) { + if (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; + }; + }), + 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); + + if (!module.params) { + throw new Error( + `${manifest_data.params} does not export \`params\` from \`defineParams\`` + ); + } + + return module.params; + } + } + }; } - update_manifest(); + /** @param {Error} error */ + function fix_stack_trace(error) { + if (!error.stack) { + return; + } + + let end = 0; + + error.stack = error.stack + .replaceAll('\0', '') // remove null bytes from e.g. virtual module IDs, or the response will fail + .split('\n') + .map((line, i) => { + const match = /^ {4}at (?:[^ ]+ \((.+)\)|(.+))$/.exec(line); + if (!match) { + end = i + 1; + return line; + } + + const loc = match[1] ?? match[2]; + const file = loc.replace(/:\d+:\d+$/, ''); + + if (fs.existsSync(file)) { + if (!file.includes('node_modules') && !file.includes(SRC_ROOT)) { + end = i + 1; + } + + return line.replace(file, path.relative(process.cwd(), file)); + } + + return line; + }) + .slice(0, end) + .join('\n'); + + return error.stack; + } const params_file = resolve_entry(svelte_config.kit.files.params); @@ -110,6 +411,10 @@ export function dev(vite, vite_config, svelte_config, remotes, root, set_manifes watch('add', () => debounce(update_manifest)); watch('unlink', () => debounce(update_manifest)); watch('change', (file) => { + // `manifest_data` is populated lazily on the first request (see `update_manifest` + // call in the middleware below), so it may still be undefined if a file changes + // before the dev server has served a request. In that case there's nothing to + // update — the manifest will be created from scratch on the first request. if (!manifest_data) return; // Don't run for a single file if the whole manifest is about to get updated // Unless it's a file where the trailing slash page option might have changed @@ -172,7 +477,11 @@ export function dev(vite, vite_config, svelte_config, remotes, root, set_manifes next(); }); - vite.environments.ssr.hot.on('vite:error', vite.hot.send); + const env = loadEnv(vite_config.mode, svelte_config.kit.env.dir, ''); + const emulator = await svelte_config.kit.adapter?.emulate?.(); + + /** @type {Promise | undefined} */ + let init_manifest; return () => { const serve_static_middleware = vite.middlewares.stack.find( @@ -185,6 +494,11 @@ export function dev(vite, vite_config, svelte_config, remotes, root, set_manifes remove_static_middlewares(vite.middlewares); vite.middlewares.use(async (req, res) => { + // Vite throws a Cannot read properties of undefined (reading 'wrapDynamicImport') + // if you try to run ssr.runner.import before the server has started so + // we do it inside here to avoid that + await (init_manifest ??= update_manifest()); + // Vite's base middleware strips out the base path. Restore it const original_url = req.url; req.url = req.originalUrl; @@ -233,11 +547,6 @@ export function dev(vite, vite_config, svelte_config, remotes, root, set_manifes return; } - const runner = get_runner(vite); - - // resolve the instrumentation file per request so that changes to it - // are picked up on new requests - /** @type {string | null} */ const resolved_instrumentation = resolve_entry( path.join(svelte_config.kit.files.src, 'instrumentation.server') ); @@ -246,6 +555,47 @@ export function dev(vite, vite_config, svelte_config, remotes, root, set_manifes await runner.import(resolved_instrumentation); } + // we have to import `Server` before calling `set_assets` + /** @type {import('types').ServerModule} */ + const { Server } = await runner.import(`${get_runtime_base(root)}/server/index.js`); + + /** @type {typeof import('../../../runtime/server/internal.js')} */ + const { set_fix_stack_trace } = await runner.import( + `${get_runtime_base(root)}/server/internal.js` + ); + set_fix_stack_trace(fix_stack_trace); + + /** @type {typeof import('$app/paths/internal/server')} */ + const { set_assets } = await runner.import('$app/paths/internal/server'); + set_assets(assets); + + const server = new Server(manifest); + const original_respond = server.respond.bind(server); + server.respond = (request, options) => { + return original_respond(request, { + ...options, + read: (file) => { + if (file in manifest._.server_assets) { + return fs.readFileSync(from_fs(file)); + } + + return fs.readFileSync(path.join(svelte_config.kit.files.assets, file)); + }, + before_handle: async (event, config, prerender, handle) => { + // we need to use .run because .enterWith() is not supported in Cloudflare Workers + // see https://blog.cloudflare.com/workers-node-js-asynclocalstorage/ + return await async_local_storage.run({ event, config, prerender }, handle); + }, + emulator + }); + }; + + const init_server = /** @type {typeof import('__sveltekit/dev-handler')} */ ( + await runner.import('__sveltekit/dev-handler') + ).default; + + const respond = await init_server(server, env); + const request = getRequest({ base, request: req @@ -273,25 +623,11 @@ export function dev(vite, vite_config, svelte_config, remotes, root, set_manifes return; } - if (!manifest_data) throw new Error('Failed to update the manifest'); - - // set the context on every request in case the user has hmr disabled - const context = /** @type {typeof import('./context.js')} */ ( - await runner.import(dev_context) - ).dev.context; - context.vite = vite; - context.svelte_config = svelte_config; - context.remotes = remotes; - context.manifest_data = manifest_data; - - const respond = /** @type {typeof import('./ssr_entry.js')} */ ( - await runner.import(dev_ssr_entry) - ).default; - const rendered = await respond(request, { - getClientAddress() { - if (!req.socket.remoteAddress) throw new Error('Could not determine clientAddress'); - return req.socket.remoteAddress; + getClientAddress: () => { + const { remoteAddress } = req.socket; + if (remoteAddress) return remoteAddress; + throw new Error('Could not determine clientAddress'); } }); @@ -328,6 +664,49 @@ function remove_static_middlewares(server) { } } +/** + * @param {import('vite').ViteDevServer} vite + * @param {import('vite').EnvironmentModuleNode} node + * @param {Set} deps + */ +async function find_deps(vite, node, deps) { + // since `transformResult.deps` contains URLs instead of `ModuleNode`s, this process is asynchronous. + // instead of using `await`, we resolve all branches in parallel. + /** @type {Promise[]} */ + const branches = []; + + /** @param {import('vite').EnvironmentModuleNode} node */ + async function add(node) { + if (!deps.has(node)) { + deps.add(node); + await find_deps(vite, node, deps); + } + } + + /** @param {string} url */ + async function add_by_url(url) { + const node = await vite.environments.ssr.moduleGraph.getModuleByUrl(url); + + if (node) { + await add(node); + } + } + + if (node.transformResult) { + if (node.transformResult.deps) { + node.transformResult.deps.forEach((url) => branches.push(add_by_url(url))); + } + + if (node.transformResult.dynamicDeps) { + node.transformResult.dynamicDeps.forEach((url) => branches.push(add_by_url(url))); + } + } else { + node.importedModules.forEach((node) => branches.push(add(node))); + } + + await Promise.all(branches); +} + /** * Determine if a file is being requested with the correct case, * to ensure consistent behaviour between dev and prod and across @@ -351,17 +730,16 @@ function has_correct_case(file, assets) { /** * Invalidates a module in all environments. - * @param {ViteDevServer} server + * @param {import('vite').ViteDevServer} server * @param {string} id - * @param {{ reload?: boolean }=} opts * @returns {void} */ -export function invalidate_module(server, id, opts = { reload: false }) { +export function invalidate_module(server, id) { for (const environment in server.environments) { const module = server.environments[environment].moduleGraph.getModuleById(id); if (module) { server.environments[environment].moduleGraph.invalidateModule(module); - if (opts.reload) void server.environments[environment].reloadModule(module); + void server.environments[environment].reloadModule(module); } } } diff --git a/packages/kit/src/exports/vite/dev/server.js b/packages/kit/src/exports/vite/dev/server.js deleted file mode 100644 index fd8060e75886..000000000000 --- a/packages/kit/src/exports/vite/dev/server.js +++ /dev/null @@ -1,81 +0,0 @@ -/** @import { RequestEvent } from '@sveltejs/kit'; */ -/** @import { InternalServer, PrerenderOption, RequestOptions } from 'types'; */ -import fs from 'node:fs'; -import path from 'node:path'; -import { AsyncLocalStorage } from 'node:async_hooks'; -import { dev } from './context.js'; -import { fix_stack_trace } from './sourcemaps.js'; -import { manifest } from './ssr_manifest.js'; -import { Server as KitServer } from '../../../runtime/server/index.js'; -import { check_feature } from '../../../utils/features.js'; -import { SCHEME } from '../../../utils/url.js'; -import { from_fs } from '../../../utils/vite.js'; - -const { set_fix_stack_trace } = await import(/* @vite-ignore */ '__sveltekit/server'); -set_fix_stack_trace(fix_stack_trace); - -const { set_assets } = await import(/* @vite-ignore */ '$app/paths/internal/server'); -set_assets(__SVELTEKIT_PATHS_ASSETS__); - -/** - * @typedef {object} FeatureContext - * @property {RequestEvent} event - * @property {Record} config - * @property {PrerenderOption} prerender - */ - -/** @type {AsyncLocalStorage} */ -const async_local_storage = new AsyncLocalStorage(); - -/** @param {string} label */ -globalThis.__SVELTEKIT_TRACK__ = (label) => { - const context = async_local_storage.getStore(); - if (!context || context.prerender === true) return; - - check_feature( - /** @type {string} */ (context.event.route.id), - context.config, - label, - dev.svelte_config.kit.adapter - ); -}; - -const fetch = globalThis.fetch; -/** @type {typeof fetch} */ -globalThis.fetch = (info, init) => { - if (typeof info === 'string' && !SCHEME.test(info)) { - throw new Error( - `Cannot use relative URL (${info}) with global fetch — use \`event.fetch\` instead: https://svelte.dev/docs/kit/web-standards#fetch-apis` - ); - } - - return fetch(info, init); -}; - -const emulator = await dev.svelte_config.kit.adapter?.emulate?.(); - -export class Server extends KitServer { - /** - * @param {Request} request - * @param {RequestOptions} options - * @returns {Promise} - */ - async respond(request, options) { - return await /** @type {InternalServer['respond']} */ (super.respond)(request, { - ...options, - read: (file) => { - if (file in manifest._.server_assets) { - return fs.readFileSync(from_fs(file)); - } - - return fs.readFileSync(path.join(__SVELTEKIT_FILES_ASSETS__, file)); - }, - before_handle: async (event, config, prerender, handle) => { - // we need to use .run because .enterWith() is not supported in Cloudflare Workers - // see https://blog.cloudflare.com/workers-node-js-asynclocalstorage/ - return await async_local_storage.run({ event, config, prerender }, handle); - }, - emulator - }); - } -} diff --git a/packages/kit/src/exports/vite/dev/sourcemaps.js b/packages/kit/src/exports/vite/dev/sourcemaps.js deleted file mode 100644 index 0240f652ad38..000000000000 --- a/packages/kit/src/exports/vite/dev/sourcemaps.js +++ /dev/null @@ -1,43 +0,0 @@ -import fs from 'node:fs'; -import path from 'node:path'; -import process from 'node:process'; -import { SRC_ROOT } from '../../../constants.js'; - -const cwd = process.cwd(); - -/** @param {Error} error */ -export function fix_stack_trace(error) { - if (!error.stack) { - return ''; - } - - let end = 0; - - error.stack = error.stack - .replaceAll('\0', '') // remove null bytes from e.g. virtual module IDs, or the response will fail - .split('\n') - .map((line, i) => { - const match = /^ {4}at (?:[^ ]+ \((.+)\)|(.+))$/.exec(line); - if (!match) { - end = i + 1; - return line; - } - - const loc = match[1] ?? match[2]; - const file = loc.replace(/:\d+:\d+$/, ''); - - if (fs.existsSync(file)) { - if (!file.includes('node_modules') && !file.includes(SRC_ROOT)) { - end = i + 1; - } - - return line.replace(file, path.relative(cwd, file)); - } - - return line; - }) - .slice(0, end) - .join('\n'); - - return error.stack; -} diff --git a/packages/kit/src/exports/vite/dev/ssr_entry.js b/packages/kit/src/exports/vite/dev/ssr_entry.js deleted file mode 100644 index 453117088a7e..000000000000 --- a/packages/kit/src/exports/vite/dev/ssr_entry.js +++ /dev/null @@ -1,12 +0,0 @@ -/** @import { InternalServer } from 'types' */ -import init_server from '__sveltekit/dev-handler'; -import { env } from '__sveltekit/dev-env'; -import { Server } from './server.js'; -import { manifest } from './ssr_manifest.js'; - -/** @type {InternalServer} */ -const server = new Server(manifest); - -const handle = await init_server(server, env); - -export default handle; diff --git a/packages/kit/src/exports/vite/dev/ssr_manifest.js b/packages/kit/src/exports/vite/dev/ssr_manifest.js deleted file mode 100644 index c9d56b466556..000000000000 --- a/packages/kit/src/exports/vite/dev/ssr_manifest.js +++ /dev/null @@ -1,242 +0,0 @@ -/** @import { ParamMatcher, SSRManifest } from '@sveltejs/kit' */ -/** @import { EnvironmentModuleNode } from 'vite' */ -import fs from 'node:fs'; -import { isCSSRequest } from 'vite'; -import { dev } from './context.js'; -import { loud_ssr_load_module, resolve } from './utils.js'; -import { get_mime_lookup } from '../../../core/utils.js'; -import { compact } from '../../../utils/array.js'; -import { load_and_validate_params } from '../../../utils/params.js'; -import { join } from '../../../utils/path.js'; -import { from_fs, to_fs } from '../../../utils/vite.js'; - -// vite-specifc queries that we should skip handling for css urls -const vite_css_query_regex = /(?:\?|&)(?:raw|url|inline)(?:&|$)/; - -await load_and_validate_params({ - routes: dev.manifest_data.routes, - params_path: dev.manifest_data.params, - root: __SVELTEKIT_ROOT__, - load: (file) => loud_ssr_load_module(to_fs(file)) -}); - -/** @type {SSRManifest} */ -export const manifest = { - appDir: __SVELTEKIT_APP_DIR__, - appPath: `${__SVELTEKIT_PATHS_BASE__.slice(1)}${__SVELTEKIT_PATHS_BASE__ ? '/' : ''}${__SVELTEKIT_APP_DIR__}`, - assets: new Set(dev.manifest_data.assets.map((asset) => asset.file)), - mimeTypes: get_mime_lookup(dev.manifest_data), - _: { - client: { - start: `${__SVELTEKIT_RUNTIME__}/client/entry.js`, - app: `${to_fs(__SVELTEKIT_OUT_DIR__)}/generated/client/app.js`, - imports: [], - stylesheets: [], - fonts: [], - uses_env_dynamic_public: true, - nodes: __SVELTEKIT_CLIENT_ROUTING__ - ? undefined - : dev.manifest_data.nodes.map((node, i) => { - if (node.component || node.universal) { - return `${__SVELTEKIT_PATHS_BASE__}${to_fs(__SVELTEKIT_OUT_DIR__)}/generated/client/nodes/${i}.js`; - } - }), - - // \`css\` is not necessary in dev, as the JS file from \`nodes\` will reference the CSS file - routes: __SVELTEKIT_CLIENT_ROUTING__ - ? undefined - : compact( - dev.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 ? [!!dev.manifest_data.nodes[l].server, l] : undefined - ), - errors: route.page.errors, - leaf: [!!dev.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: dev.manifest_data.nodes.map((node, i) => { - return async () => { - /** @type {import('types').SSRNode} */ - const result = {}; - result.index = i; - 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, module_node } = await resolve( - join(__SVELTEKIT_ROOT__, /** @type {string} */ (node.component)) - ); - module_nodes.push(module_node); - return module.default; - }; - } - - if (node.universal) { - if (node.page_options?.ssr === false) { - result.universal = 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(join(__SVELTEKIT_ROOT__, node.universal)); - module_nodes.push(module_node); - result.universal = module; - } - } - - if (node.server) { - const { module } = await resolve(join(__SVELTEKIT_ROOT__, 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(); - - for (const module_node of module_nodes) { - await find_deps(module_node, deps); - } - - /** @type {Record} */ - const styles = {}; - - for (const dep of deps) { - if (isCSSRequest(dep.url) && !vite_css_query_regex.test(dep.url)) { - const inline_css_url = dep.url.includes('?') - ? dep.url.replace('?', '?inline&') - : dep.url + '?inline'; - try { - const mod = await import(/* @vite-ignore */ inline_css_url); - 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; - }; - }), - prerendered_routes: new Set(), - get remotes() { - return Object.fromEntries( - dev.remotes.map((remote) => [ - remote.hash, - () => - import(/* @vite-ignore */ join(__SVELTEKIT_ROOT__, remote.file)).then((module) => ({ - default: module - })) - ]) - ); - }, - routes: compact( - dev.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 = join(__SVELTEKIT_ROOT__, endpoint.file); - const { module } = await resolve(url); - return module; - } - : null, - endpoint_id: endpoint?.file - }; - }) - ), - matchers: async () => { - if (!dev.manifest_data.params) return {}; - - const url = join(__SVELTEKIT_ROOT__, dev.manifest_data.params); - /** @type {{ module: { params?: Record> } }} */ - const { module } = await resolve(url); - - if (!module.params) { - throw new Error( - `${dev.manifest_data.params} does not export \`params\` from \`defineParams\`` - ); - } - - return module.params; - } - } -}; - -/** - * @param {EnvironmentModuleNode} node - * @param {Set} deps - */ -async function find_deps(node, deps) { - // since `ssrTransformResult.deps` contains URLs instead of `ModuleNode`s, this process is asynchronous. - // instead of using `await`, we resolve all branches in parallel. - /** @type {Promise[]} */ - const branches = []; - - /** @param {EnvironmentModuleNode} node */ - async function add(node) { - if (!deps.has(node)) { - deps.add(node); - await find_deps(node, deps); - } - } - - /** @param {string} url */ - async function add_by_url(url) { - const node = await dev.vite.environments.ssr.moduleGraph.getModuleByUrl(url); - - if (node) { - await add(node); - } - } - - if (node.transformResult) { - if (node.transformResult.deps) { - node.transformResult.deps.forEach((url) => branches.push(add_by_url(url))); - } - - if (node.transformResult.dynamicDeps) { - node.transformResult.dynamicDeps.forEach((url) => branches.push(add_by_url(url))); - } - } else { - node.importedModules.forEach((node) => branches.push(add(node))); - } - - await Promise.all(branches); -} diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 5cd3fc1bdc37..cded25341033 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -1,12 +1,12 @@ /** @import { EnvVarConfig, KitConfig } from '@sveltejs/kit' */ /** @import { Options, SvelteConfig } from '@sveltejs/vite-plugin-svelte' */ /** @import { PreprocessorGroup } from 'svelte/compiler' */ -/** @import { BuildData, ManifestData, Prerendered, ServerMetadata, RemoteInternals, ValidatedConfig, ValidatedKitConfig, RemoteChunk } from 'types' */ +/** @import { BuildData, ManifestData, Prerendered, ServerMetadata, RemoteInternals, ValidatedConfig, ValidatedKitConfig } from 'types' */ /** @import { Manifest, Plugin, ResolvedConfig, Rolldown, UserConfig, ViteDevServer } from 'vite' */ import fs from 'node:fs'; import path from 'node:path'; import process from 'node:process'; -import { fileURLToPath, pathToFileURL } from 'node:url'; +import { fileURLToPath } from 'node:url'; import { styleText } from 'node:util'; import MagicString from 'magic-string'; import { loadEnv } from 'vite'; @@ -26,11 +26,11 @@ import { } from '../../core/env.js'; import * as sync from '../../core/sync/sync.js'; import { load_and_validate_params } from '../../utils/params.js'; -import { runtime_directory, logger, get_runtime_base } from '../../core/utils.js'; +import { runtime_directory, logger } from '../../core/utils.js'; import { generate_manifest } from '../../core/generate_manifest/index.js'; import { build_server_nodes } from './build/build_server.js'; import { find_deps, resolve_symlinks } from './build/utils.js'; -import { dev, dev_context, invalidate_module } from './dev/index.js'; +import { dev, invalidate_module } from './dev/index.js'; import { preview } from './preview/index.js'; import { error_for_missing_config, @@ -57,7 +57,6 @@ import { sveltekit_manifest_data, sveltekit_env_public_client, sveltekit_env_public_server, - sveltekit_dev_env, sveltekit_dev_handler } from './module_ids.js'; import { import_peer } from '../../utils/import.js'; @@ -65,7 +64,6 @@ import { compact } from '../../utils/array.js'; import { should_ignore, has_children } from './static_analysis/utils.js'; import { process_config, split_config, validate_config } from '../../core/config/index.js'; import { treeshake_prerendered_remotes } from './build/remote.js'; -import { SVELTE_KIT_ASSETS } from '../../constants.js'; import { get_runner } from '../../runner.js'; const default_handler = posixify(path.join(import.meta.dirname, 'dev/handler.js')); @@ -483,7 +481,6 @@ function kit({ svelte_config }) { const define = { __SVELTEKIT_APP_DIR__: s(posixify(kit.appDir)), - __SVELTEKIT_OUT_DIR__: s(out_dir), __SVELTEKIT_APP_VERSION__: s(kit.version.name), __SVELTEKIT_APP_VERSION_CHECKS_ENABLED__: s(kit.output.bundleStrategy !== 'inline'), __SVELTEKIT_EMBEDDED__: s(kit.embedded), @@ -497,8 +494,6 @@ function kit({ svelte_config }) { __SVELTEKIT_SUPPORTS_ASYNC__: s( svelte_config.compilerOptions?.experimental?.async ?? false ), - __SVELTEKIT_ROOT__: s(root), - __SVELTEKIT_RUNTIME__: s(get_runtime_base(root)), __SVELTEKIT_DEV__: s(!is_build) }; @@ -517,9 +512,7 @@ function kit({ svelte_config }) { __SVELTEKIT_APP_VERSION_POLL_INTERVAL__: '0', __SVELTEKIT_PAYLOAD__: kit_global, // only relevant when bundleStrategy !== 'split' __SVELTEKIT_HAS_SERVER_LOAD__: 'true', - __SVELTEKIT_HAS_UNIVERSAL_LOAD__: 'true', - __SVELTEKIT_PATHS_ASSETS__: kit.paths.assets ? s(SVELTE_KIT_ASSETS) : s(kit.paths.base), - __SVELTEKIT_FILES_ASSETS__: s(posixify(kit.files.assets)) + __SVELTEKIT_HAS_UNIVERSAL_LOAD__: 'true' }; // These Kit dependencies are packaged as CommonJS, which means they must always be externalized. @@ -589,25 +582,15 @@ function kit({ svelte_config }) { filter: { id: [exactRegex(sveltekit_dev_handler)] }, - handler(id) { - if (id === sveltekit_dev_handler) { - const custom_handler = kit.adapter?.customHandler; - if (!custom_handler) return default_handler; + handler() { + const custom_handler = kit.adapter?.customHandler; + if (!custom_handler) return default_handler; - const filepath = custom_handler.startsWith('file://') - ? fileURLToPath(custom_handler) - : path.resolve(root, custom_handler); + const filepath = custom_handler.startsWith('file://') + ? fileURLToPath(custom_handler) + : path.resolve(root, custom_handler); - return posixify(filepath); - } - } - }, - load: { - filter: { - id: [exactRegex(sveltekit_dev_env)] - }, - handler() { - return `export const env = ${s(env)};`; + return posixify(filepath); } } }; @@ -884,7 +867,7 @@ function kit({ svelte_config }) { /** @type {ViteDevServer} */ let dev_server; - /** @type {RemoteChunk[]} */ + /** @type {Array<{ hash: string, file: string }} */ const remotes = []; /** @type {Map} Maps remote hash -> original module id */ @@ -1564,12 +1547,19 @@ function kit({ svelte_config }) { * Adds the SvelteKit middleware to do SSR in dev mode. * @see https://vitejs.dev/guide/api-plugin.html#configureserver */ - configureServer(server) { - return dev(server, vite_config, svelte_config, remotes, root, (data) => { - manifest_data = data; - invalidate_module(server, sveltekit_manifest_data, { reload: true }); - invalidate_module(server, dev_context); - }); + async configureServer(server) { + return await dev( + server, + vite_config, + svelte_config, + () => remotes, + root, + (data) => { + manifest_data = data; + // Invalidate the manifest data module so it reloads with new routes/files + invalidate_module(server, sveltekit_manifest_data); + } + ); }, /** @@ -1618,8 +1608,7 @@ function kit({ svelte_config }) { await load_and_validate_params({ routes: manifest_data.routes, params_path: manifest_data.params, - root, - load: (file) => import(pathToFileURL(file).href) + root }); const { output: server_chunks } = /** @type {Rolldown.RolldownOutput} */ ( diff --git a/packages/kit/src/exports/vite/module_ids.js b/packages/kit/src/exports/vite/module_ids.js index 5f0b6cbdaf39..7b8da3024043 100644 --- a/packages/kit/src/exports/vite/module_ids.js +++ b/packages/kit/src/exports/vite/module_ids.js @@ -12,7 +12,6 @@ export const service_worker = '\0virtual:service-worker'; /** Used to construct `$app/manifest` */ export const sveltekit_manifest_data = '\0virtual:__sveltekit/manifest-data'; /** The environment variables loaded by Vite, used to initialise the dev server instance */ -export const sveltekit_dev_env = '\0virtual:__sveltekit/dev-env'; /** Resolves to the adapter's `customHandler`, falling back to SvelteKit's default handler */ export const sveltekit_dev_handler = '__sveltekit/dev-handler'; diff --git a/packages/kit/src/exports/vite/preview/index.js b/packages/kit/src/exports/vite/preview/index.js index 2cf027678804..0c0d7a8caf5c 100644 --- a/packages/kit/src/exports/vite/preview/index.js +++ b/packages/kit/src/exports/vite/preview/index.js @@ -234,7 +234,9 @@ export async function preview(vite, vite_config, svelte_config) { setResponse( res, - await (custom_respond ?? server.respond)(request, { + await /** @type {import('@sveltejs/kit').Server['respond']} */ ( + custom_respond ?? server.respond + )(request, { getClientAddress: () => { const { remoteAddress } = req.socket; if (remoteAddress) return remoteAddress; diff --git a/packages/kit/src/types/ambient-private.d.ts b/packages/kit/src/types/ambient-private.d.ts index 7d63876eb3b4..cbe4f0324a89 100644 --- a/packages/kit/src/types/ambient-private.d.ts +++ b/packages/kit/src/types/ambient-private.d.ts @@ -54,16 +54,11 @@ declare module '__sveltekit/manifest-data' { export const routes: Array<{ id: string }>; } -/** The environment variables loaded by Vite, used to initialise the development server */ -declare module '__sveltekit/dev-env' { - export const env: Record; -} - /** Resolves to the adapter's `customHandler`, falling back to SvelteKit's default handler */ declare module '__sveltekit/dev-handler' { // eslint-disable-next-line no-duplicate-imports import { SSRHandler } from '@sveltejs/kit'; - const create_handler: SSRHandler; - export default create_handler; + const init_server: SSRHandler; + export default init_server; } diff --git a/packages/kit/src/utils/params.js b/packages/kit/src/utils/params.js index 2b92ead2d167..70832b288c83 100644 --- a/packages/kit/src/utils/params.js +++ b/packages/kit/src/utils/params.js @@ -1,5 +1,5 @@ import path from 'node:path'; -import { posixify } from './os.js'; +import { pathToFileURL } from 'node:url'; /** * @param {import('types').RouteData[]} routes @@ -36,7 +36,7 @@ export function validate_param_matchers(params, names, file) { * routes: import('types').RouteData[]; * params_path: string | null; * root: string; - * load: (file: string) => Promise>; + * load?: (file: string) => Promise>; * }} opts * @returns {Promise | null>} */ @@ -49,8 +49,8 @@ export async function load_and_validate_params({ routes, params_path, root, load throw new Error(`No matcher found for parameter '${names.values().next().value}'`); } - const file = posixify(path.resolve(root, params_path)); - const module = await load(file); + const file = path.resolve(root, params_path); + const module = load ? await load(file) : await import(pathToFileURL(file).href); if (!module.params || typeof module.params !== 'object') { throw new Error(`${params_path} does not export \`params\` from \`defineParams\``); diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 81af3e3647e5..b0e4da92bf6a 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -1781,7 +1781,7 @@ declare module '@sveltejs/kit' { 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. */ + /** 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; From 4c67f0c19f1ccf80f8b150fc0db57c27489e38bd Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Tue, 28 Jul 2026 23:00:03 +0800 Subject: [PATCH 086/109] split out --- packages/kit/src/runtime/server/index.js | 42 ++++-------------------- 1 file changed, 7 insertions(+), 35 deletions(-) diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js index 8e13057be29a..0b1522391ef0 100644 --- a/packages/kit/src/runtime/server/index.js +++ b/packages/kit/src/runtime/server/index.js @@ -105,24 +105,16 @@ export class Server { this.#options.hooks = { handle: module.handle || (({ event, resolve }) => resolve(event)), - handleError: (args) => { - /** @type {string} */ - let message = ''; - let stack = ''; - - if (!module.handleError) { - if (args.error instanceof SvelteKitError) { + handleError: + module.handleError || + (({ error }) => { + if (error instanceof SvelteKitError) { // don't log stack traces for 404s etc, it's all internal gubbins return; } - let e = args.error; + let e = error; while (e instanceof Error) { - if (__SVELTEKIT_DEV__) { - message ||= e.message; - if (e.stack) stack += e.stack; - } - if (e.stack) { console.error(e.stack); } @@ -130,29 +122,9 @@ export class Server { } if (e) { - const original_message = String(e); - console.error(original_message); - if (__SVELTEKIT_DEV__ && original_message) message = original_message; + console.error(String(e)); } - } else if (args.error instanceof Error) { - message = args.error.message; - stack = args.error.stack ?? ''; - } - - if (__SVELTEKIT_DEV__ && !(args.error instanceof SvelteKitError) && message) { - import.meta.hot?.send('vite:error', { - type: 'error', - err: { - message, - stack: stack.replace(`Error: ${message}\n`, '') - } - }); - } - - if (module.handleError) { - return module.handleError(args); - } - }, + }), handleFetch: module.handleFetch || (({ request, fetch }) => fetch(request)), handleValidationError: module.handleValidationError || From cddfef013714fe1b2c7e08922c74a9b471da0c73 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Tue, 28 Jul 2026 23:05:55 +0800 Subject: [PATCH 087/109] unused --- packages/kit/src/exports/vite/module_ids.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/kit/src/exports/vite/module_ids.js b/packages/kit/src/exports/vite/module_ids.js index 7b8da3024043..10deaf81e0a6 100644 --- a/packages/kit/src/exports/vite/module_ids.js +++ b/packages/kit/src/exports/vite/module_ids.js @@ -11,7 +11,6 @@ export const service_worker = '\0virtual:service-worker'; /** Used to construct `$app/manifest` */ export const sveltekit_manifest_data = '\0virtual:__sveltekit/manifest-data'; -/** The environment variables loaded by Vite, used to initialise the dev server instance */ /** Resolves to the adapter's `customHandler`, falling back to SvelteKit's default handler */ export const sveltekit_dev_handler = '__sveltekit/dev-handler'; From e21944d6e56fda6f5366933e46030019aae20959 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Tue, 28 Jul 2026 23:15:51 +0800 Subject: [PATCH 088/109] fixes --- packages/kit/src/core/postbuild/fallback.js | 6 ++++-- packages/kit/src/core/postbuild/prerender.js | 4 ++-- packages/kit/src/exports/vite/preview/index.js | 6 +++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/kit/src/core/postbuild/fallback.js b/packages/kit/src/core/postbuild/fallback.js index 2405ceb833e7..36cdbfd134c1 100644 --- a/packages/kit/src/core/postbuild/fallback.js +++ b/packages/kit/src/core/postbuild/fallback.js @@ -40,8 +40,10 @@ async function generate_fallback({ manifest_path, env, out_dir, origin, assets, let custom_respond; if (customHandler) { - /** @type {import('@sveltejs/kit').SSRHandler} */ - const init_server = await import(pathToFileURL(`${server_root}/server/index.js`).href); + /** @type {{ default: import('@sveltejs/kit').SSRHandler }} */ + const { default: init_server } = await import( + pathToFileURL(`${server_root}/server/index.js`).href + ); /** @type {InternalServer['respond']} */ custom_respond = await init_server(server, env); } diff --git a/packages/kit/src/core/postbuild/prerender.js b/packages/kit/src/core/postbuild/prerender.js index 97e204a69274..4f4fcc362b20 100644 --- a/packages/kit/src/core/postbuild/prerender.js +++ b/packages/kit/src/core/postbuild/prerender.js @@ -612,8 +612,8 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit let custom_respond; if (config.adapter?.customHandler) { - /** @type {import('@sveltejs/kit').SSRHandler} */ - const init_server = await import(pathToFileURL(`${out}/server/index.js`).href); + /** @type {{ default: import('@sveltejs/kit').SSRHandler }} */ + const { default: init_server } = await import(pathToFileURL(`${out}/server/index.js`).href); /** @type {InternalServer['respond']} */ custom_respond = await init_server(server, env); } diff --git a/packages/kit/src/exports/vite/preview/index.js b/packages/kit/src/exports/vite/preview/index.js index 0c0d7a8caf5c..f57ee925feeb 100644 --- a/packages/kit/src/exports/vite/preview/index.js +++ b/packages/kit/src/exports/vite/preview/index.js @@ -81,9 +81,9 @@ export async function preview(vite, vite_config, svelte_config) { /** @type {import('@sveltejs/kit').Server['respond'] | undefined} */ let custom_respond; - if (svelte_config.adapter?.customHandler) { - /** @type {import('@sveltejs/kit').SSRHandler} */ - const handler = await import(pathToFileURL(`${dir}/server/index.js`).href); + if (svelte_config.kit.adapter?.customHandler) { + /** @type {{ default: import('@sveltejs/kit').SSRHandler }} */ + const { default: handler } = await import(pathToFileURL(`${dir}/index.js`).href); custom_respond = await handler(server, env); } From 498443202127ebaf7b3bfd2801edeeb757dc97d0 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Tue, 28 Jul 2026 23:19:54 +0800 Subject: [PATCH 089/109] add tests --- .../kit/test/apps/basics/adapter-handler.js | 37 +++++++++++++++++++ .../custom-handler/modified/+server.js | 5 +++ .../kit/test/apps/basics/test/server.test.js | 19 +++++++++- packages/kit/test/apps/basics/vite.config.js | 3 +- 4 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 packages/kit/test/apps/basics/adapter-handler.js create mode 100644 packages/kit/test/apps/basics/src/routes/adapter/custom-handler/modified/+server.js diff --git a/packages/kit/test/apps/basics/adapter-handler.js b/packages/kit/test/apps/basics/adapter-handler.js new file mode 100644 index 000000000000..c2115bd18cf0 --- /dev/null +++ b/packages/kit/test/apps/basics/adapter-handler.js @@ -0,0 +1,37 @@ +/** @import { SSRHandler } from '@sveltejs/kit' */ +import { createReadableStream } from '@sveltejs/kit/node'; + +/** + * Custom handler for the test adapter. Intercepts requests to + * `/adapter/custom-handler/intercepted` and adds a header to responses + * for every other route beneath `/adapter/custom-handler`. + * @type {SSRHandler} + */ +export default async function handler(server, env) { + await server.init({ env, read: (file) => createReadableStream(file) }); + + return async (request, options) => { + const { pathname } = new URL(request.url); + + if (pathname === '/adapter/custom-handler/intercepted') { + return new Response('intercepted by the adapter', { + headers: { 'content-type': 'text/plain' } + }); + } + + const response = await server.respond(request, options); + + if (pathname.startsWith('/adapter/custom-handler')) { + const headers = new Headers(response.headers); + headers.set('x-adapter-custom-handler', 'true'); + + return new Response(response.body, { + status: response.status, + statusText: response.statusText, + headers + }); + } + + return response; + }; +} diff --git a/packages/kit/test/apps/basics/src/routes/adapter/custom-handler/modified/+server.js b/packages/kit/test/apps/basics/src/routes/adapter/custom-handler/modified/+server.js new file mode 100644 index 000000000000..4e659060d864 --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/adapter/custom-handler/modified/+server.js @@ -0,0 +1,5 @@ +import { text } from '@sveltejs/kit'; + +export function GET() { + return text('handled by the server'); +} diff --git a/packages/kit/test/apps/basics/test/server.test.js b/packages/kit/test/apps/basics/test/server.test.js index d7e45c1c9ce9..aac739cb16cb 100644 --- a/packages/kit/test/apps/basics/test/server.test.js +++ b/packages/kit/test/apps/basics/test/server.test.js @@ -1558,6 +1558,21 @@ test.describe('Streaming', () => { }); }); -// TODO: test adapter intercepted request +test.describe('Adapter custom handler', () => { + test('returns its own response', async ({ request }) => { + const response = await request.get('/adapter/custom-handler/intercepted'); -// TODO: test adapter modified request + expect(response.status()).toBe(200); + expect(await response.text()).toBe('intercepted by the adapter'); + // the handler returns before `server.respond` is called + expect(response.headers()['x-adapter-custom-handler']).toBeUndefined(); + }); + + test('modifies response returned by server.respond', async ({ request }) => { + const response = await request.get('/adapter/custom-handler/modified'); + + expect(response.status()).toBe(200); + expect(await response.text()).toBe('handled by the server'); + expect(response.headers()['x-adapter-custom-handler']).toBe('true'); + }); +}); diff --git a/packages/kit/test/apps/basics/vite.config.js b/packages/kit/test/apps/basics/vite.config.js index 60fc4bfcc272..33b6b1fd422f 100644 --- a/packages/kit/test/apps/basics/vite.config.js +++ b/packages/kit/test/apps/basics/vite.config.js @@ -37,7 +37,8 @@ export default defineConfig({ supports: { read: () => true, instrumentation: () => true - } + }, + customHandler: import.meta.resolve('./adapter-handler.js') }, compilerOptions: { From d3ac896a6cef734d59a3ec4edaa6c8a1e745c9d0 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Tue, 28 Jul 2026 23:28:30 +0800 Subject: [PATCH 090/109] Delete utils.js --- packages/kit/src/exports/vite/dev/utils.js | 47 ---------------------- 1 file changed, 47 deletions(-) delete mode 100644 packages/kit/src/exports/vite/dev/utils.js diff --git a/packages/kit/src/exports/vite/dev/utils.js b/packages/kit/src/exports/vite/dev/utils.js deleted file mode 100644 index 8b711bfb234c..000000000000 --- a/packages/kit/src/exports/vite/dev/utils.js +++ /dev/null @@ -1,47 +0,0 @@ -import path from 'node:path'; -import { styleText } from 'node:util'; -import { buildErrorMessage } from 'vite'; -import { dev } from './context.js'; -import { to_fs } from '../../../utils/vite.js'; - -/** @param {string} url */ -export async function loud_ssr_load_module(url) { - try { - return await import(/* @vite-ignore */ url); - } catch (/** @type {any} */ err) { - const msg = buildErrorMessage(err, [styleText('red', `Internal server error: ${err.message}`)]); - - if (!dev.vite.config.logger.hasErrorLogged(err)) { - dev.vite.config.logger.error(msg, { error: err }); - } - - // TODO this is inadequate — it doesn't reliably show the overlay on every page load, - // and when it does appear it may immediately vanish. `vite.hot.send` broadcasts - // to all connected clients, even ones that are unaffected by the error. - // we need a more considered approach - dev.vite.hot.send({ - type: 'error', - err: { - ...err, - // these properties are non-enumerable and will - // not be serialized unless we explicitly include them - message: err.message, - stack: err.stack ?? '' - } - }); - - throw err; - } -} - -/** @param {string} id */ -export async function resolve(id) { - const url = id.startsWith('..') ? to_fs(path.resolve(id)) : `file://${id}`; - - const module = await loud_ssr_load_module(url); - - const module_node = await dev.vite.environments.ssr.moduleGraph.getModuleByUrl(url); - if (!module_node) throw new Error(`Could not find node for ${url}`); - - return { module, module_node, url }; -} From 7622a82dd41e2990d2a46c9caec9a22eeb2ff42f Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Tue, 28 Jul 2026 23:29:23 +0800 Subject: [PATCH 091/109] typo --- packages/kit/src/exports/vite/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index cded25341033..360cb1b471f3 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -867,7 +867,7 @@ function kit({ svelte_config }) { /** @type {ViteDevServer} */ let dev_server; - /** @type {Array<{ hash: string, file: string }} */ + /** @type {Array<{ hash: string, file: string }>} */ const remotes = []; /** @type {Map} Maps remote hash -> original module id */ From 040296d4ae29d9becec6c95563f011d6384eb944 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Tue, 28 Jul 2026 23:31:51 +0800 Subject: [PATCH 092/109] keep state.read in preview --- .../kit/src/exports/vite/preview/index.js | 42 ++++++++++--------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/packages/kit/src/exports/vite/preview/index.js b/packages/kit/src/exports/vite/preview/index.js index f57ee925feeb..76b8d513b5c7 100644 --- a/packages/kit/src/exports/vite/preview/index.js +++ b/packages/kit/src/exports/vite/preview/index.js @@ -50,34 +50,25 @@ export async function preview(vite, vite_config, svelte_config) { const server = new Server(manifest); - const env = loadEnv(vite_config.mode, svelte_config.kit.env.dir, ''); - - try { - await server.init({ - env, - read: (file) => createReadableStream(`${dir}/${file}`) - }); - } catch (error) { - // Vite erases the error message when starting the preview server so we store - // it in the stack instead. This ensures errors thrown using `stackless` - // are still readable - if (error instanceof Error) error.stack = error.message; - throw error; - } - - const emulator = await svelte_config.kit.adapter?.emulate?.(); - const original_respond = server.respond.bind(server); + const emulator = await svelte_config.kit.adapter?.emulate?.(); server.respond = async (request, options) => { return await /** @type {InternalServer['respond']} */ (original_respond)(request, { ...options, - // @ts-expect-error this is only needed for prerender - read: undefined, + read: (file) => { + if (file in manifest._.server_assets) { + return fs.readFileSync(join(dir, file)); + } + + return fs.readFileSync(join(svelte_config.kit.files.assets, file)); + }, emulator }); }; + const env = loadEnv(vite_config.mode, svelte_config.kit.env.dir, ''); + /** @type {import('@sveltejs/kit').Server['respond'] | undefined} */ let custom_respond; @@ -85,6 +76,19 @@ export async function preview(vite, vite_config, svelte_config) { /** @type {{ default: import('@sveltejs/kit').SSRHandler }} */ const { default: handler } = await import(pathToFileURL(`${dir}/index.js`).href); custom_respond = await handler(server, env); + } else { + try { + await server.init({ + env, + read: (file) => createReadableStream(`${dir}/${file}`) + }); + } catch (error) { + // Vite erases the error message when starting the preview server so we store + // it in the stack instead. This ensures errors thrown using `stackless` + // are still readable + if (error instanceof Error) error.stack = error.message; + throw error; + } } return () => { From cd62efbddb5f54026c0485d4795796a3a86a78dd Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Tue, 28 Jul 2026 23:34:41 +0800 Subject: [PATCH 093/109] unused type --- packages/kit/src/core/postbuild/fallback.js | 2 -- packages/kit/src/core/postbuild/prerender.js | 2 -- packages/kit/src/exports/vite/preview/index.js | 4 ++-- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/kit/src/core/postbuild/fallback.js b/packages/kit/src/core/postbuild/fallback.js index 36cdbfd134c1..e0391470f4d6 100644 --- a/packages/kit/src/core/postbuild/fallback.js +++ b/packages/kit/src/core/postbuild/fallback.js @@ -1,4 +1,3 @@ -/** @import { InternalServer } from 'types' */ import { readFileSync } from 'node:fs'; import { join } from 'node:path'; import { pathToFileURL } from 'node:url'; @@ -44,7 +43,6 @@ async function generate_fallback({ manifest_path, env, out_dir, origin, assets, const { default: init_server } = await import( pathToFileURL(`${server_root}/server/index.js`).href ); - /** @type {InternalServer['respond']} */ custom_respond = await init_server(server, env); } diff --git a/packages/kit/src/core/postbuild/prerender.js b/packages/kit/src/core/postbuild/prerender.js index 4f4fcc362b20..2a78a709bdbd 100644 --- a/packages/kit/src/core/postbuild/prerender.js +++ b/packages/kit/src/core/postbuild/prerender.js @@ -1,5 +1,4 @@ /** @import { Server } from '@sveltejs/kit' */ -/** @import { InternalServer } from 'types' */ import { existsSync, readFileSync, statSync, writeFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { pathToFileURL } from 'node:url'; @@ -614,7 +613,6 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit if (config.adapter?.customHandler) { /** @type {{ default: import('@sveltejs/kit').SSRHandler }} */ const { default: init_server } = await import(pathToFileURL(`${out}/server/index.js`).href); - /** @type {InternalServer['respond']} */ custom_respond = await init_server(server, env); } diff --git a/packages/kit/src/exports/vite/preview/index.js b/packages/kit/src/exports/vite/preview/index.js index 76b8d513b5c7..194aad93b90b 100644 --- a/packages/kit/src/exports/vite/preview/index.js +++ b/packages/kit/src/exports/vite/preview/index.js @@ -1,6 +1,6 @@ /** @import { NextHandleFunction } from 'connect' */ /** @import { PreviewServer, ResolvedConfig } from 'vite' */ -/** @import { ValidatedConfig, ServerInternalModule, InternalServer, ServerModule } from 'types' */ +/** @import { ValidatedConfig, ServerInternalModule, ServerModule } from 'types' */ import fs from 'node:fs'; import { join } from 'node:path'; import { pathToFileURL } from 'node:url'; @@ -54,7 +54,7 @@ export async function preview(vite, vite_config, svelte_config) { const emulator = await svelte_config.kit.adapter?.emulate?.(); server.respond = async (request, options) => { - return await /** @type {InternalServer['respond']} */ (original_respond)(request, { + return await original_respond(request, { ...options, read: (file) => { if (file in manifest._.server_assets) { From c1756ad9efa7c4d39f2bff006638316b8bb29c4d Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Tue, 28 Jul 2026 23:55:40 +0800 Subject: [PATCH 094/109] revert --- packages/kit/test/apps/basics/vite.config.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/kit/test/apps/basics/vite.config.js b/packages/kit/test/apps/basics/vite.config.js index 33b6b1fd422f..3a5b5b7c0941 100644 --- a/packages/kit/test/apps/basics/vite.config.js +++ b/packages/kit/test/apps/basics/vite.config.js @@ -94,9 +94,6 @@ export default defineConfig({ }, cors: { origin: '*' - }, - hmr: { - overlay: process.env.PLAYWRIGHT_TEST !== '1' } }, test: { From 42de023e9b4a3bd6afe499b25db3b9a02776f74c Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Wed, 29 Jul 2026 00:08:07 +0800 Subject: [PATCH 095/109] clean up and fixes --- packages/kit/src/exports/vite/dev/index.js | 5 ++- packages/kit/src/exports/vite/module_ids.js | 2 +- .../kit/src/exports/vite/preview/index.js | 38 +++++++++---------- packages/kit/src/types/ambient-private.d.ts | 2 +- .../kit/test/apps/basics/adapter-handler.js | 9 ++++- packages/kit/test/apps/basics/tsconfig.json | 9 ++++- 6 files changed, 40 insertions(+), 25 deletions(-) diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 0714848bfafc..c571f1c9f3c3 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -30,6 +30,7 @@ import { SCHEME } from '../../../utils/url.js'; import { check_feature } from '../../../utils/features.js'; import { escape_html } from '../../../utils/escape.js'; import { get_runner } from '../../../runner.js'; +import { sveltekit_dev_handler } from '../module_ids.js'; // vite-specifc queries that we should skip handling for css urls const vite_css_query_regex = /(?:\?|&)(?:raw|url|inline)(?:&|$)/; @@ -590,8 +591,8 @@ export async function dev(vite, vite_config, svelte_config, get_remotes, root, s }); }; - const init_server = /** @type {typeof import('__sveltekit/dev-handler')} */ ( - await runner.import('__sveltekit/dev-handler') + const init_server = /** @type {typeof import('__sveltekit/dev-handler.js')} */ ( + await runner.import(sveltekit_dev_handler) ).default; const respond = await init_server(server, env); diff --git a/packages/kit/src/exports/vite/module_ids.js b/packages/kit/src/exports/vite/module_ids.js index 10deaf81e0a6..4702523807dc 100644 --- a/packages/kit/src/exports/vite/module_ids.js +++ b/packages/kit/src/exports/vite/module_ids.js @@ -13,7 +13,7 @@ export const service_worker = '\0virtual:service-worker'; export const sveltekit_manifest_data = '\0virtual:__sveltekit/manifest-data'; /** Resolves to the adapter's `customHandler`, falling back to SvelteKit's default handler */ -export const sveltekit_dev_handler = '__sveltekit/dev-handler'; +export const sveltekit_dev_handler = '__sveltekit/dev-handler.js'; export const app_server = posixify( fileURLToPath(new URL('../../runtime/app/server/index.js', import.meta.url)) diff --git a/packages/kit/src/exports/vite/preview/index.js b/packages/kit/src/exports/vite/preview/index.js index 194aad93b90b..789a2421b1e3 100644 --- a/packages/kit/src/exports/vite/preview/index.js +++ b/packages/kit/src/exports/vite/preview/index.js @@ -1,3 +1,4 @@ +/** @import { Server, SSRHandler, SSRManifest } from '@sveltejs/kit' */ /** @import { NextHandleFunction } from 'connect' */ /** @import { PreviewServer, ResolvedConfig } from 'vite' */ /** @import { ValidatedConfig, ServerInternalModule, ServerModule } from 'types' */ @@ -43,9 +44,12 @@ export async function preview(vite, vite_config, svelte_config) { /** @type {ServerModule} */ const { Server } = await import(pathToFileURL(join(dir, 'server.js')).href); - /** @type {import('@sveltejs/kit').SSRManifest} */ + /** @type {SSRManifest} */ const manifest = (await import(pathToFileURL(join(dir, 'manifest.js')).href)).manifest; + /** @type {{ default: SSRHandler }} */ + const { default: handler } = await import(pathToFileURL(`${dir}/index.js`).href); + set_assets(assets); const server = new Server(manifest); @@ -67,28 +71,26 @@ export async function preview(vite, vite_config, svelte_config) { }); }; - const env = loadEnv(vite_config.mode, svelte_config.kit.env.dir, ''); - - /** @type {import('@sveltejs/kit').Server['respond'] | undefined} */ + /** @type {Server['respond'] | undefined} */ let custom_respond; - if (svelte_config.kit.adapter?.customHandler) { - /** @type {{ default: import('@sveltejs/kit').SSRHandler }} */ - const { default: handler } = await import(pathToFileURL(`${dir}/index.js`).href); - custom_respond = await handler(server, env); - } else { - try { + try { + const env = loadEnv(vite_config.mode, svelte_config.kit.env.dir, ''); + + if (svelte_config.kit.adapter?.customHandler) { + custom_respond = await handler(server, env); + } else { await server.init({ env, read: (file) => createReadableStream(`${dir}/${file}`) }); - } catch (error) { - // Vite erases the error message when starting the preview server so we store - // it in the stack instead. This ensures errors thrown using `stackless` - // are still readable - if (error instanceof Error) error.stack = error.message; - throw error; } + } catch (error) { + // Vite erases the error message when starting the preview server so we store + // it in the stack instead. This ensures errors thrown using `stackless` + // are still readable + if (error instanceof Error) error.stack = error.message; + throw error; } return () => { @@ -238,9 +240,7 @@ export async function preview(vite, vite_config, svelte_config) { setResponse( res, - await /** @type {import('@sveltejs/kit').Server['respond']} */ ( - custom_respond ?? server.respond - )(request, { + await /** @type {Server['respond']} */ (custom_respond ?? server.respond)(request, { getClientAddress: () => { const { remoteAddress } = req.socket; if (remoteAddress) return remoteAddress; diff --git a/packages/kit/src/types/ambient-private.d.ts b/packages/kit/src/types/ambient-private.d.ts index cbe4f0324a89..cc7931acf04e 100644 --- a/packages/kit/src/types/ambient-private.d.ts +++ b/packages/kit/src/types/ambient-private.d.ts @@ -55,7 +55,7 @@ declare module '__sveltekit/manifest-data' { } /** Resolves to the adapter's `customHandler`, falling back to SvelteKit's default handler */ -declare module '__sveltekit/dev-handler' { +declare module '__sveltekit/dev-handler.js' { // eslint-disable-next-line no-duplicate-imports import { SSRHandler } from '@sveltejs/kit'; diff --git a/packages/kit/test/apps/basics/adapter-handler.js b/packages/kit/test/apps/basics/adapter-handler.js index c2115bd18cf0..c04a3ca31f40 100644 --- a/packages/kit/test/apps/basics/adapter-handler.js +++ b/packages/kit/test/apps/basics/adapter-handler.js @@ -1,5 +1,9 @@ /** @import { SSRHandler } from '@sveltejs/kit' */ import { createReadableStream } from '@sveltejs/kit/node'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const dir = dirname(fileURLToPath(import.meta.url)); /** * Custom handler for the test adapter. Intercepts requests to @@ -8,7 +12,10 @@ import { createReadableStream } from '@sveltejs/kit/node'; * @type {SSRHandler} */ export default async function handler(server, env) { - await server.init({ env, read: (file) => createReadableStream(file) }); + await server.init({ + env, + read: (file) => createReadableStream(resolve(dir, file)) + }); return async (request, options) => { const { pathname } = new URL(request.url); diff --git a/packages/kit/test/apps/basics/tsconfig.json b/packages/kit/test/apps/basics/tsconfig.json index 9c01fc02941d..e46c34fc38fd 100644 --- a/packages/kit/test/apps/basics/tsconfig.json +++ b/packages/kit/test/apps/basics/tsconfig.json @@ -6,5 +6,12 @@ "resolveJsonModule": true }, "extends": "$app/tsconfig", - "include": ["src", "unit-test", "test"] + "include": [ + "src", + "unit-test", + "test", + "playwright.config.js", + "adapter-handler.js", + "vite.config.js" + ] } From c1aa38f048cc2e61c18c45ec7ec6ea2ea59bfdc0 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Wed, 29 Jul 2026 00:13:53 +0800 Subject: [PATCH 096/109] this goes in here --- packages/kit/src/exports/vite/preview/index.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/kit/src/exports/vite/preview/index.js b/packages/kit/src/exports/vite/preview/index.js index 789a2421b1e3..ffce79d23b0f 100644 --- a/packages/kit/src/exports/vite/preview/index.js +++ b/packages/kit/src/exports/vite/preview/index.js @@ -47,9 +47,6 @@ export async function preview(vite, vite_config, svelte_config) { /** @type {SSRManifest} */ const manifest = (await import(pathToFileURL(join(dir, 'manifest.js')).href)).manifest; - /** @type {{ default: SSRHandler }} */ - const { default: handler } = await import(pathToFileURL(`${dir}/index.js`).href); - set_assets(assets); const server = new Server(manifest); @@ -78,6 +75,8 @@ export async function preview(vite, vite_config, svelte_config) { const env = loadEnv(vite_config.mode, svelte_config.kit.env.dir, ''); if (svelte_config.kit.adapter?.customHandler) { + /** @type {{ default: SSRHandler }} */ + const { default: handler } = await import(pathToFileURL(`${dir}/index.js`).href); custom_respond = await handler(server, env); } else { await server.init({ From 1313b6320b647849c092bcf13380168e6f8258b4 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Wed, 29 Jul 2026 00:21:19 +0800 Subject: [PATCH 097/109] separate into its own pr --- packages/kit/src/exports/vite/index.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 360cb1b471f3..54eebea09550 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -435,9 +435,7 @@ function kit({ svelte_config }) { // this does not affect app code, just handling of imported libraries that use $app or $env '$app', '$env' - ], - // avoid Vite dev server reloading the first time a page is requested - include: ['@sveltejs/kit > devalue', '@sveltejs/kit > esm-env'] + ] }, ssr: { noExternal: [ From 8e47611072b30868b4c46d1f2e56f38c6b7cd55f Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Wed, 29 Jul 2026 01:00:42 +0800 Subject: [PATCH 098/109] make server.init optional --- .../docs/25-build-and-deploy/99-writing-adapters.md | 8 +++++++- packages/kit/src/core/postbuild/fallback.js | 8 +++++++- packages/kit/src/core/postbuild/prerender.js | 9 +++++---- packages/kit/src/exports/public.d.ts | 13 +++++++++---- packages/kit/src/exports/vite/dev/index.js | 4 +++- packages/kit/src/exports/vite/preview/index.js | 11 ++++++----- packages/kit/test/apps/basics/adapter-handler.js | 12 +----------- packages/kit/types/index.d.ts | 13 +++++++++---- 8 files changed, 47 insertions(+), 31 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 ef18dda88ff2..fcbf52ab1456 100644 --- a/documentation/docs/25-build-and-deploy/99-writing-adapters.md +++ b/documentation/docs/25-build-and-deploy/99-writing-adapters.md @@ -95,13 +95,17 @@ export default function (options) { }; ``` -The handler file should export a default function that calls `server.init` with an implementation for [`read`]($app-server#read) (if it supports it). It should also return a function which returns a `Response` or calls `server.respond` with any [platform-specific context](types#Platform). +The handler file should export a default function that returns a request handler. The should return a `Response` such as by calling `server.respond` along with any [platform-specific context](types#Platform). Optionally, you can pass a different set of environment variables or `read` implementation to `server.init` if your platform supports it. ```js /// file: src/handler.js // @errors: 2322 /** @type {import('@sveltejs/kit').SSRHandler} */ export default async function handler(server, env) { + // perform setup work here + + // this is optional. Since `server.init` only runs once, the default + // implementation will be used instead if it's not been called already await server.init({ env, read: (file) => { @@ -110,6 +114,8 @@ export default async function handler(server, env) { }); return async (request, options) => { + // custom request/response handling logic goes here + return await server.respond(request, { ...options, platform: { diff --git a/packages/kit/src/core/postbuild/fallback.js b/packages/kit/src/core/postbuild/fallback.js index e0391470f4d6..a8b7a87e5b8d 100644 --- a/packages/kit/src/core/postbuild/fallback.js +++ b/packages/kit/src/core/postbuild/fallback.js @@ -31,7 +31,6 @@ async function generate_fallback({ manifest_path, env, out_dir, origin, assets, set_building(); const server = new Server(manifest); - await server.init({ env }); const original_respond = server.respond.bind(server); @@ -45,6 +44,13 @@ async function generate_fallback({ manifest_path, env, out_dir, origin, assets, ); custom_respond = await init_server(server, env); } + // fallback if init hasn't been called yet + await server.init({ + env, + read: (file) => { + throw new Error(`Cannot call \`read\` for ${file} while prerendering a fallback page`); + } + }); server.respond = (request, options) => { return original_respond(request, { diff --git a/packages/kit/src/core/postbuild/prerender.js b/packages/kit/src/core/postbuild/prerender.js index 2a78a709bdbd..e67b9a6b2a9d 100644 --- a/packages/kit/src/core/postbuild/prerender.js +++ b/packages/kit/src/core/postbuild/prerender.js @@ -600,10 +600,6 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit // only run the server after the `should_prerender` check so that we // don't run the user's init hook unnecessarily const server = new Server(manifest); - await server.init({ - env, - read: (file) => createReadableStream(`${config.outDir}/output/server/${file}`) - }); const original_respond = server.respond.bind(server); @@ -615,6 +611,11 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit const { default: init_server } = await import(pathToFileURL(`${out}/server/index.js`).href); custom_respond = await init_server(server, env); } + // fallback if init hasn't been called yet + await server.init({ + env, + read: (file) => createReadableStream(`${config.outDir}/output/server/${file}`) + }); log.info('Prerendering'); diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index 9e1423fc20f6..a0d177e356c6 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -86,7 +86,7 @@ export interface Adapter { } /** - * The [custom handler](https://svelte.dev/docs/kit/writing-adapters#Custom-request-handler) an adapter can specify to customise request handling. It is expected to call `server.init` with an implementation for [`read`](https://svelte.dev/docs/kit/$app-server#read) (if it supports it) and return a function which returns a `Response` or calls `server.respond` with any [platform-specific context](https://svelte.dev/docs/kit/types#Platform). + * The [custom handler](https://svelte.dev/docs/kit/writing-adapters#Custom-request-handler) an adapter can specify to customise request handling. It is expected to return a function which returns a `Response` or calls `server.respond` with any [platform-specific context](https://svelte.dev/docs/kit/types#Platform). Optionally, it can call `server.init` with a different set of environment variables or an implementation for [`read`](https://svelte.dev/docs/kit/$app-server#read) if the platform supports it. * @since 3.0.0 */ export type SSRHandler = ( @@ -1409,7 +1409,10 @@ export interface NavigationLink extends NavigationBase { } export type Navigation = - NavigationExternal | NavigationFormSubmit | NavigationPopState | NavigationLink; + | NavigationExternal + | NavigationFormSubmit + | NavigationPopState + | NavigationLink; /** * The argument passed to [`beforeNavigate`](https://svelte.dev/docs/kit/$app-navigation#beforeNavigate) callbacks. @@ -1514,7 +1517,8 @@ export type ParamValue = string | number | boolean | bigint; * A param matcher definition passed to [`defineParams`](https://svelte.dev/docs/kit/@sveltejs-kit#defineParams). */ export type ParamDefinition = - ((param: string) => ParamValue | undefined) | StandardSchemaV1; + | ((param: string) => ParamValue | undefined) + | StandardSchemaV1; /** * The return type of [`defineParams`](https://svelte.dev/docs/kit/@sveltejs-kit#defineParams). @@ -1619,7 +1623,8 @@ export type LiveQueryRequestedResult = Iterable< }; export type RequestedResult = - QueryRequestedResult | LiveQueryRequestedResult; + | QueryRequestedResult + | LiveQueryRequestedResult; export interface RequestEvent< Params extends AppLayoutParams<'/'> = AppLayoutParams<'/'>, diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index c571f1c9f3c3..f4caa70dfcae 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -8,7 +8,7 @@ import { AsyncLocalStorage } from 'node:async_hooks'; import { styleText } from 'node:util'; import sirv from 'sirv'; import { isCSSRequest, loadEnv, buildErrorMessage } from 'vite'; -import { getRequest, setResponse } from '../../../exports/node/index.js'; +import { createReadableStream, getRequest, setResponse } from '../../../exports/node/index.js'; import { coalesce_to_error } from '../../../utils/error.js'; import { resolve_entry } from '../../../utils/filesystem.js'; import { load_and_validate_params } from '../../../utils/params.js'; @@ -596,6 +596,8 @@ export async function dev(vite, vite_config, svelte_config, get_remotes, root, s ).default; const respond = await init_server(server, env); + // fallback in case the custom handler didn't run init + await server.init({ env, read: (file) => createReadableStream(file) }); const request = getRequest({ base, diff --git a/packages/kit/src/exports/vite/preview/index.js b/packages/kit/src/exports/vite/preview/index.js index ffce79d23b0f..7ff937648c23 100644 --- a/packages/kit/src/exports/vite/preview/index.js +++ b/packages/kit/src/exports/vite/preview/index.js @@ -78,12 +78,13 @@ export async function preview(vite, vite_config, svelte_config) { /** @type {{ default: SSRHandler }} */ const { default: handler } = await import(pathToFileURL(`${dir}/index.js`).href); custom_respond = await handler(server, env); - } else { - await server.init({ - env, - read: (file) => createReadableStream(`${dir}/${file}`) - }); } + + // fallback if init hasn't been called yet + await server.init({ + env, + read: (file) => createReadableStream(`${dir}/${file}`) + }); } catch (error) { // Vite erases the error message when starting the preview server so we store // it in the stack instead. This ensures errors thrown using `stackless` diff --git a/packages/kit/test/apps/basics/adapter-handler.js b/packages/kit/test/apps/basics/adapter-handler.js index c04a3ca31f40..31ace8f78e26 100644 --- a/packages/kit/test/apps/basics/adapter-handler.js +++ b/packages/kit/test/apps/basics/adapter-handler.js @@ -1,9 +1,4 @@ /** @import { SSRHandler } from '@sveltejs/kit' */ -import { createReadableStream } from '@sveltejs/kit/node'; -import { dirname, resolve } from 'node:path'; -import { fileURLToPath } from 'node:url'; - -const dir = dirname(fileURLToPath(import.meta.url)); /** * Custom handler for the test adapter. Intercepts requests to @@ -11,12 +6,7 @@ const dir = dirname(fileURLToPath(import.meta.url)); * for every other route beneath `/adapter/custom-handler`. * @type {SSRHandler} */ -export default async function handler(server, env) { - await server.init({ - env, - read: (file) => createReadableStream(resolve(dir, file)) - }); - +export default async function handler(server) { return async (request, options) => { const { pathname } = new URL(request.url); diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index d35ee9e782dc..8dc3cde5e32f 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -60,7 +60,7 @@ declare module '@sveltejs/kit' { } /** - * The [custom handler](https://svelte.dev/docs/kit/writing-adapters#Custom-request-handler) an adapter can specify to customise request handling. It is expected to call `server.init` with an implementation for [`read`](https://svelte.dev/docs/kit/$app-server#read) (if it supports it) and return a function which returns a `Response` or calls `server.respond` with any [platform-specific context](https://svelte.dev/docs/kit/types#Platform). + * The [custom handler](https://svelte.dev/docs/kit/writing-adapters#Custom-request-handler) an adapter can specify to customise request handling. It is expected to return a function which returns a `Response` or calls `server.respond` with any [platform-specific context](https://svelte.dev/docs/kit/types#Platform). Optionally, it can call `server.init` with a different set of environment variables or an implementation for [`read`](https://svelte.dev/docs/kit/$app-server#read) if the platform supports it. * @since 3.0.0 */ export type SSRHandler = ( @@ -1381,7 +1381,10 @@ declare module '@sveltejs/kit' { } export type Navigation = - NavigationExternal | NavigationFormSubmit | NavigationPopState | NavigationLink; + | NavigationExternal + | NavigationFormSubmit + | NavigationPopState + | NavigationLink; /** * The argument passed to [`beforeNavigate`](https://svelte.dev/docs/kit/$app-navigation#beforeNavigate) callbacks. @@ -1486,7 +1489,8 @@ declare module '@sveltejs/kit' { * A param matcher definition passed to [`defineParams`](https://svelte.dev/docs/kit/@sveltejs-kit#defineParams). */ export type ParamDefinition = - ((param: string) => ParamValue | undefined) | StandardSchemaV1; + | ((param: string) => ParamValue | undefined) + | StandardSchemaV1; /** * The return type of [`defineParams`](https://svelte.dev/docs/kit/@sveltejs-kit#defineParams). @@ -1589,7 +1593,8 @@ declare module '@sveltejs/kit' { }; export type RequestedResult = - QueryRequestedResult | LiveQueryRequestedResult; + | QueryRequestedResult + | LiveQueryRequestedResult; export interface RequestEvent< Params extends AppLayoutParams<'/'> = AppLayoutParams<'/'>, From c3d773410cfe22bfe073bf7d09d5d7b760d83202 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Wed, 29 Jul 2026 20:59:55 +0800 Subject: [PATCH 099/109] Update documentation/docs/25-build-and-deploy/99-writing-adapters.md Co-authored-by: Rich Harris --- .../docs/25-build-and-deploy/99-writing-adapters.md | 9 --------- 1 file changed, 9 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 fcbf52ab1456..65f85bffde78 100644 --- a/documentation/docs/25-build-and-deploy/99-writing-adapters.md +++ b/documentation/docs/25-build-and-deploy/99-writing-adapters.md @@ -104,15 +104,6 @@ The handler file should export a default function that returns a request handler export default async function handler(server, env) { // perform setup work here - // this is optional. Since `server.init` only runs once, the default - // implementation will be used instead if it's not been called already - await server.init({ - env, - read: (file) => { - // how your platform reads files - } - }); - return async (request, options) => { // custom request/response handling logic goes here From 7d70597d5f4fb0f0d4fcb39129c16d794371bdbb Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Wed, 29 Jul 2026 21:00:08 +0800 Subject: [PATCH 100/109] Update documentation/docs/25-build-and-deploy/99-writing-adapters.md Co-authored-by: Rich Harris --- documentation/docs/25-build-and-deploy/99-writing-adapters.md | 2 +- 1 file changed, 1 insertion(+), 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 65f85bffde78..056130f0a644 100644 --- a/documentation/docs/25-build-and-deploy/99-writing-adapters.md +++ b/documentation/docs/25-build-and-deploy/99-writing-adapters.md @@ -101,7 +101,7 @@ The handler file should export a default function that returns a request handler /// file: src/handler.js // @errors: 2322 /** @type {import('@sveltejs/kit').SSRHandler} */ -export default async function handler(server, env) { +export default async function handler(server) { // perform setup work here return async (request, options) => { From b8f5830ab77542fe8706c613280e77ebea19a473 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Wed, 29 Jul 2026 21:00:26 +0800 Subject: [PATCH 101/109] Update documentation/docs/25-build-and-deploy/99-writing-adapters.md Co-authored-by: Rich Harris --- documentation/docs/25-build-and-deploy/99-writing-adapters.md | 2 +- 1 file changed, 1 insertion(+), 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 056130f0a644..a3172b8b2da0 100644 --- a/documentation/docs/25-build-and-deploy/99-writing-adapters.md +++ b/documentation/docs/25-build-and-deploy/99-writing-adapters.md @@ -104,7 +104,7 @@ The handler file should export a default function that returns a request handler export default async function handler(server) { // perform setup work here - return async (request, options) => { + return async (request) => { // custom request/response handling logic goes here return await server.respond(request, { From df6754744c661c07ec8e2c14fbe6d7e5539206f7 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Wed, 29 Jul 2026 21:01:53 +0800 Subject: [PATCH 102/109] Update documentation/docs/25-build-and-deploy/99-writing-adapters.md Co-authored-by: Rich Harris --- documentation/docs/25-build-and-deploy/99-writing-adapters.md | 1 - 1 file changed, 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 a3172b8b2da0..3761664e0b2f 100644 --- a/documentation/docs/25-build-and-deploy/99-writing-adapters.md +++ b/documentation/docs/25-build-and-deploy/99-writing-adapters.md @@ -108,7 +108,6 @@ export default async function handler(server) { // custom request/response handling logic goes here return await server.respond(request, { - ...options, platform: { // the shape of `App.Platform` } From cf920695e71394dafe1d69b24cbfe86328a90de7 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 29 Jul 2026 13:28:04 -0400 Subject: [PATCH 103/109] feat: simplify custom entrypoint stuff (#16560) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit continuation of #16464. This isn't complete — the types need a little finessing (in the handler, it probably doesn't make sense for `server` to have an `init` method, and the second argument to `server.respond` should be optional and only include `platform`), and `getClientAddress` doesn't currently work. But you get the idea --------- Co-authored-by: Tee Ming Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: Vercel --- .../99-writing-adapters.md | 2 +- packages/kit/src/core/adapt/builder.js | 3 +- packages/kit/src/core/postbuild/fallback.js | 55 +++++-------- packages/kit/src/core/postbuild/prerender.js | 80 ++++++++----------- packages/kit/src/exports/public.d.ts | 9 +-- packages/kit/src/exports/vite/dev/handler.js | 15 ---- packages/kit/src/exports/vite/dev/index.js | 65 +++++++-------- packages/kit/src/exports/vite/index.js | 38 +-------- packages/kit/src/exports/vite/module_ids.js | 3 - .../kit/src/exports/vite/preview/index.js | 65 +++++++-------- .../kit/src/runtime/server/default-handler.js | 6 ++ ...{adapter-handler.js => adapter-handler.ts} | 13 +-- packages/kit/test/apps/basics/tsconfig.json | 2 +- packages/kit/test/apps/basics/vite.config.js | 11 ++- packages/kit/types/index.d.ts | 9 +-- 15 files changed, 151 insertions(+), 225 deletions(-) delete mode 100644 packages/kit/src/exports/vite/dev/handler.js create mode 100644 packages/kit/src/runtime/server/default-handler.js rename packages/kit/test/apps/basics/{adapter-handler.js => adapter-handler.ts} (76%) 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 3761664e0b2f..bd4c80169210 100644 --- a/documentation/docs/25-build-and-deploy/99-writing-adapters.md +++ b/documentation/docs/25-build-and-deploy/99-writing-adapters.md @@ -95,7 +95,7 @@ export default function (options) { }; ``` -The handler file should export a default function that returns a request handler. The should return a `Response` such as by calling `server.respond` along with any [platform-specific context](types#Platform). Optionally, you can pass a different set of environment variables or `read` implementation to `server.init` if your platform supports it. +The handler file should export a default function that returns a request handler. The should return a `Response` such as by calling `server.respond` along with any [platform-specific context](types#Platform). ```js /// file: src/handler.js diff --git a/packages/kit/src/core/adapt/builder.js b/packages/kit/src/core/adapt/builder.js index af87d4e183d9..d871e45ff2c4 100644 --- a/packages/kit/src/core/adapt/builder.js +++ b/packages/kit/src/core/adapt/builder.js @@ -138,8 +138,7 @@ export function create_builder({ env, out_dir: config.kit.outDir, origin: config.kit.paths.origin || 'http://sveltekit-prerender', - assets: config.kit.files.assets, - customHandler: config.adapter?.customHandler + assets: config.kit.files.assets }); if (existsSync(dest)) { diff --git a/packages/kit/src/core/postbuild/fallback.js b/packages/kit/src/core/postbuild/fallback.js index a8b7a87e5b8d..1da31291fb4d 100644 --- a/packages/kit/src/core/postbuild/fallback.js +++ b/packages/kit/src/core/postbuild/fallback.js @@ -1,3 +1,4 @@ +/** @import { SSRHandler } from '@sveltejs/kit' */ import { readFileSync } from 'node:fs'; import { join } from 'node:path'; import { pathToFileURL } from 'node:url'; @@ -13,10 +14,9 @@ export default forked(import.meta.url, generate_fallback); * out_dir: string; * origin: string; * assets: string; - * customHandler: string | undefined; * }} opts */ -async function generate_fallback({ manifest_path, env, out_dir, origin, assets, customHandler }) { +async function generate_fallback({ manifest_path, env, out_dir, origin, assets }) { const server_root = join(out_dir, 'output'); /** @type {import('types').ServerInternalModule} */ @@ -31,20 +31,6 @@ async function generate_fallback({ manifest_path, env, out_dir, origin, assets, set_building(); const server = new Server(manifest); - - const original_respond = server.respond.bind(server); - - /** @type {import('@sveltejs/kit').Server['respond'] | undefined} */ - let custom_respond; - - if (customHandler) { - /** @type {{ default: import('@sveltejs/kit').SSRHandler }} */ - const { default: init_server } = await import( - pathToFileURL(`${server_root}/server/index.js`).href - ); - custom_respond = await init_server(server, env); - } - // fallback if init hasn't been called yet await server.init({ env, read: (file) => { @@ -52,26 +38,29 @@ async function generate_fallback({ manifest_path, env, out_dir, origin, assets, } }); - server.respond = (request, options) => { - return original_respond(request, { - ...options, - prerendering: { - fallback: true, - dependencies: new Map(), - remote_responses: new Map() - }, - read: (file) => readFileSync(join(assets, file)) - }); - }; - - const response = await /** @type {import('@sveltejs/kit').Server['respond']} */ ( - custom_respond ?? server.respond - )(new Request(origin + '/[fallback]'), { - getClientAddress: () => { - throw new Error('Cannot read clientAddress during prerendering'); + /** @type {{ default: SSRHandler }} */ + const { default: init_server } = await import( + pathToFileURL(`${server_root}/server/handler.js`).href + ); + const respond = await init_server({ + respond: (request, options) => { + return server.respond(request, { + ...options, + getClientAddress: () => { + throw new Error('Cannot read clientAddress during prerendering'); + }, + prerendering: { + fallback: true, + dependencies: new Map(), + remote_responses: new Map() + }, + read: (file) => readFileSync(join(assets, file)) + }); } }); + const response = await respond(new Request(origin + '/[fallback]')); + if (response.ok) { return await response.text(); } diff --git a/packages/kit/src/core/postbuild/prerender.js b/packages/kit/src/core/postbuild/prerender.js index e67b9a6b2a9d..b2569e4103b3 100644 --- a/packages/kit/src/core/postbuild/prerender.js +++ b/packages/kit/src/core/postbuild/prerender.js @@ -1,4 +1,4 @@ -/** @import { Server } from '@sveltejs/kit' */ +/** @import { SSRHandler } from '@sveltejs/kit' */ import { existsSync, readFileSync, statSync, writeFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { pathToFileURL } from 'node:url'; @@ -59,6 +59,9 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit /** @type {import('types').ServerModule} */ const { Server } = await import(pathToFileURL(`${out}/server/server.js`).href); + /** @type {{ default: SSRHandler }} */ + const { default: init_server } = await import(pathToFileURL(`${out}/server/handler.js`).href); + const throw_handled = () => { throw new Error('__handled__'); }; @@ -151,8 +154,7 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit env, out_dir: config.outDir, origin: prerender_origin, - assets: config.files.assets, - customHandler: config.adapter?.customHandler + assets: config.files.assets }); const file = output_filename('/', true); @@ -303,38 +305,36 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit const request = new Request(prerender_origin + encoded); - server.respond = (request, options) => { - return original_respond(request, { - ...options, - prerendering: { - dependencies, - remote_responses - }, - read: (file) => { - // stuff we just wrote - const filepath = saved.get(file); - if (filepath) return readFileSync(filepath); - - // Static assets emitted during build - if (file.startsWith(config.appDir)) { - return readFileSync(`${out}/server/${file}`); - } - - // stuff in `static` - return readFileSync(join(config.files.assets, file)); - }, - emulator - }); - }; - - const response = await /** @type {Server['respond']} */ (custom_respond ?? server.respond)( - request, - { - getClientAddress() { - throw new Error('Cannot read clientAddress during prerendering'); - } + const respond = await init_server({ + respond: (request, options) => { + return server.respond(request, { + ...options, + getClientAddress() { + throw new Error('Cannot read clientAddress during prerendering'); + }, + prerendering: { + dependencies, + remote_responses + }, + read: (file) => { + // stuff we just wrote + const filepath = saved.get(file); + if (filepath) return readFileSync(filepath); + + // Static assets emitted during build + if (file.startsWith(config.appDir)) { + return readFileSync(`${out}/server/${file}`); + } + + // stuff in `static` + return readFileSync(join(config.files.assets, file)); + }, + emulator + }); } - ); + }); + + const response = await respond(request); const encoded_id = response.headers.get('x-sveltekit-routeid'); const decoded_id = encoded_id && decode_uri(encoded_id); @@ -600,18 +600,6 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit // only run the server after the `should_prerender` check so that we // don't run the user's init hook unnecessarily const server = new Server(manifest); - - const original_respond = server.respond.bind(server); - - /** @type {Server['respond'] | undefined} */ - let custom_respond; - - if (config.adapter?.customHandler) { - /** @type {{ default: import('@sveltejs/kit').SSRHandler }} */ - const { default: init_server } = await import(pathToFileURL(`${out}/server/index.js`).href); - custom_respond = await init_server(server, env); - } - // fallback if init hasn't been called yet await server.init({ env, read: (file) => createReadableStream(`${config.outDir}/output/server/${file}`) diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index 0a5296dbfea0..2ef00f2dd3fd 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -86,13 +86,12 @@ export interface Adapter { } /** - * The [custom handler](https://svelte.dev/docs/kit/writing-adapters#Custom-request-handler) an adapter can specify to customise request handling. It is expected to return a function which returns a `Response` or calls `server.respond` with any [platform-specific context](https://svelte.dev/docs/kit/types#Platform). Optionally, it can call `server.init` with a different set of environment variables or an implementation for [`read`](https://svelte.dev/docs/kit/$app-server#read) if the platform supports it. + * The [custom handler](https://svelte.dev/docs/kit/writing-adapters#Custom-request-handler) an adapter can specify to customise request handling. It is expected to return a function which returns a `Response` or calls `server.respond` with any [platform-specific context](https://svelte.dev/docs/kit/types#Platform). * @since 3.0.0 */ -export type SSRHandler = ( - server: Server, - env: Record -) => Promise<(request: Request, options: Omit) => Promise>; +export type SSRHandler = (server: { + respond: (request: Request, options?: Pick) => Promise; +}) => MaybePromise<(request: Request) => MaybePromise>; export type LoadProperties | void> = input extends void ? undefined // needs to be undefined, because void will break intellisense diff --git a/packages/kit/src/exports/vite/dev/handler.js b/packages/kit/src/exports/vite/dev/handler.js deleted file mode 100644 index 3a4f13852309..000000000000 --- a/packages/kit/src/exports/vite/dev/handler.js +++ /dev/null @@ -1,15 +0,0 @@ -/** @import { SSRHandler } from '@sveltejs/kit' */ -import { createReadableStream } from '@sveltejs/kit/node'; - -/** - * The handler used when the adapter doesn't provide a `customHandler`. - * @type {SSRHandler} - */ -export default async function (server, env) { - await server.init({ - env, - read: (file) => createReadableStream(file) - }); - - return (request, options) => server.respond(request, options); -} diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index b55c08e7bbb2..92e14355cd2a 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -31,7 +31,6 @@ import { SCHEME } from '../../../utils/url.js'; import { check_feature } from '../../../utils/features.js'; import { escape_html } from '../../../utils/escape.js'; import { get_runner } from '../../../runner.js'; -import { sveltekit_dev_handler } from '../module_ids.js'; // vite-specifc queries that we should skip handling for css urls const vite_css_query_regex = /(?:\?|&)(?:raw|url|inline)(?:&|$)/; @@ -583,34 +582,42 @@ export async function dev(vite, vite_config, svelte_config, get_remotes, root, s set_assets(assets); const server = new Server(manifest); - const original_respond = server.respond.bind(server); - server.respond = (request, options) => { - return original_respond(request, { - ...options, - read: (file) => { - if (file in manifest._.server_assets) { - return fs.readFileSync(from_fs(file)); - } - - return fs.readFileSync(path.join(svelte_config.kit.files.assets, file)); - }, - before_handle: async (event, config, prerender, handle) => { - // we need to use .run because .enterWith() is not supported in Cloudflare Workers - // see https://blog.cloudflare.com/workers-node-js-asynclocalstorage/ - return await async_local_storage.run({ event, config, prerender }, handle); - }, - emulator - }); - }; - const init_server = /** @type {typeof import('__sveltekit/dev-handler.js')} */ ( - await runner.import(sveltekit_dev_handler) - ).default; + const handler = + svelte_config.kit.adapter?.customHandler ?? + `${get_runtime_base(root)}/server/default-handler.js`; - const respond = await init_server(server, env); - // fallback in case the custom handler didn't run init await server.init({ env, read: (file) => createReadableStream(file) }); + /** @type {import('../../../runtime/server/default-handler.js')} */ + const { default: init_server } = await runner.import(handler); + + const respond = await init_server({ + respond: (request, options) => { + return server.respond(request, { + ...options, + getClientAddress: () => { + const { remoteAddress } = req.socket; + if (remoteAddress) return remoteAddress; + throw new Error('Could not determine clientAddress'); + }, + read: (file) => { + if (file in manifest._.server_assets) { + return fs.readFileSync(from_fs(file)); + } + + return fs.readFileSync(path.join(svelte_config.kit.files.assets, file)); + }, + before_handle: async (event, config, prerender, handle) => { + // we need to use .run because .enterWith() is not supported in Cloudflare Workers + // see https://blog.cloudflare.com/workers-node-js-asynclocalstorage/ + return await async_local_storage.run({ event, config, prerender }, handle); + }, + emulator + }); + } + }); + const request = getRequest({ base, request: req @@ -638,13 +645,7 @@ export async function dev(vite, vite_config, svelte_config, get_remotes, root, s return; } - const rendered = await respond(request, { - getClientAddress: () => { - const { remoteAddress } = req.socket; - if (remoteAddress) return remoteAddress; - throw new Error('Could not determine clientAddress'); - } - }); + const rendered = await respond(request); if (rendered.status === 404) { // @ts-expect-error diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index d553826e4dc6..f5a8f5633d50 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -6,7 +6,6 @@ import fs from 'node:fs'; import path from 'node:path'; import process from 'node:process'; -import { fileURLToPath } from 'node:url'; import { styleText } from 'node:util'; import MagicString from 'magic-string'; import { loadEnv } from 'vite'; @@ -56,8 +55,7 @@ import { sveltekit_env_service_worker, sveltekit_manifest_data, sveltekit_env_public_client, - sveltekit_env_public_server, - sveltekit_dev_handler + sveltekit_env_public_server } from './module_ids.js'; import { import_peer } from '../../utils/import.js'; import { compact } from '../../utils/array.js'; @@ -66,8 +64,6 @@ import { process_config, split_config, validate_config } from '../../core/config import { treeshake_prerendered_remotes } from './build/remote.js'; import { get_runner } from '../../runner.js'; -const default_handler = posixify(path.join(import.meta.dirname, 'dev/handler.js')); - /** @type {import('./types.js').EnforcedConfig} */ const enforced_config = { appType: true, @@ -569,32 +565,6 @@ function kit({ svelte_config }) { } }; - /** @type {Plugin} */ - const plugin_dev_ssr = { - name: 'vite-plugin-sveltekit-dev-ssr', - apply: 'serve', - - applyToEnvironment(environment) { - return environment.config.consumer === 'server'; - }, - - resolveId: { - filter: { - id: [exactRegex(sveltekit_dev_handler)] - }, - handler() { - const custom_handler = kit.adapter?.customHandler; - if (!custom_handler) return default_handler; - - const filepath = custom_handler.startsWith('file://') - ? fileURLToPath(custom_handler) - : path.resolve(root, custom_handler); - - return posixify(filepath); - } - } - }; - /** @type {string | null} */ let explicit_env_entry = null; @@ -1273,15 +1243,12 @@ function kit({ svelte_config }) { /** @type {Record} */ const server_input = { server: `${runtime_directory}/server/index.js`, + handler: kit.adapter?.customHandler ?? `${runtime_directory}/server/default-handler.js`, internal: `${out_dir}/generated/server/internal.js`, env: '__sveltekit/env', ['remote-entry']: `${runtime_directory}/app/server/remote/index.js` }; - if (kit.adapter?.customHandler) { - server_input.index = kit.adapter.customHandler; - } - // add entry points for every endpoint... manifest_data.routes.forEach((route) => { if (route.endpoint) { @@ -2047,7 +2014,6 @@ function kit({ svelte_config }) { svelte_config.kit.adapter?.vite?.plugins, plugin_resolve_root, plugin_setup, - plugin_dev_ssr, plugin_remote_guard, plugin_remote, plugin_virtual_modules, diff --git a/packages/kit/src/exports/vite/module_ids.js b/packages/kit/src/exports/vite/module_ids.js index 4702523807dc..09313b1dca96 100644 --- a/packages/kit/src/exports/vite/module_ids.js +++ b/packages/kit/src/exports/vite/module_ids.js @@ -12,9 +12,6 @@ export const service_worker = '\0virtual:service-worker'; /** Used to construct `$app/manifest` */ export const sveltekit_manifest_data = '\0virtual:__sveltekit/manifest-data'; -/** Resolves to the adapter's `customHandler`, falling back to SvelteKit's default handler */ -export const sveltekit_dev_handler = '__sveltekit/dev-handler.js'; - export const app_server = posixify( fileURLToPath(new URL('../../runtime/app/server/index.js', import.meta.url)) ); diff --git a/packages/kit/src/exports/vite/preview/index.js b/packages/kit/src/exports/vite/preview/index.js index 7ff937648c23..ba3a7458bc34 100644 --- a/packages/kit/src/exports/vite/preview/index.js +++ b/packages/kit/src/exports/vite/preview/index.js @@ -1,4 +1,4 @@ -/** @import { Server, SSRHandler, SSRManifest } from '@sveltejs/kit' */ +/** @import { SSRHandler, SSRManifest } from '@sveltejs/kit' */ /** @import { NextHandleFunction } from 'connect' */ /** @import { PreviewServer, ResolvedConfig } from 'vite' */ /** @import { ValidatedConfig, ServerInternalModule, ServerModule } from 'types' */ @@ -51,36 +51,9 @@ export async function preview(vite, vite_config, svelte_config) { const server = new Server(manifest); - const original_respond = server.respond.bind(server); - const emulator = await svelte_config.kit.adapter?.emulate?.(); - - server.respond = async (request, options) => { - return await original_respond(request, { - ...options, - read: (file) => { - if (file in manifest._.server_assets) { - return fs.readFileSync(join(dir, file)); - } - - return fs.readFileSync(join(svelte_config.kit.files.assets, file)); - }, - emulator - }); - }; - - /** @type {Server['respond'] | undefined} */ - let custom_respond; - try { const env = loadEnv(vite_config.mode, svelte_config.kit.env.dir, ''); - if (svelte_config.kit.adapter?.customHandler) { - /** @type {{ default: SSRHandler }} */ - const { default: handler } = await import(pathToFileURL(`${dir}/index.js`).href); - custom_respond = await handler(server, env); - } - - // fallback if init hasn't been called yet await server.init({ env, read: (file) => createReadableStream(`${dir}/${file}`) @@ -93,6 +66,8 @@ export async function preview(vite, vite_config, svelte_config) { throw error; } + const emulator = await svelte_config.kit.adapter?.emulate?.(); + return () => { // Remove the base middleware. It screws with the URL. // It also only lets through requests beginning with the base path, so that requests beginning @@ -238,16 +213,30 @@ export async function preview(vite, vite_config, svelte_config) { request: req }); - setResponse( - res, - await /** @type {Server['respond']} */ (custom_respond ?? server.respond)(request, { - getClientAddress: () => { - const { remoteAddress } = req.socket; - if (remoteAddress) return remoteAddress; - throw new Error('Could not determine clientAddress'); - } - }) - ); + /** @type {{ default: SSRHandler }} */ + const { default: init_server } = await import(pathToFileURL(`${dir}/handler.js`).href); + const respond = await init_server({ + respond: (request, options) => { + return server.respond(request, { + ...options, + getClientAddress: () => { + const { remoteAddress } = req.socket; + if (remoteAddress) return remoteAddress; + throw new Error('Could not determine clientAddress'); + }, + read: (file) => { + if (file in manifest._.server_assets) { + return fs.readFileSync(join(dir, file)); + } + + return fs.readFileSync(join(svelte_config.kit.files.assets, file)); + }, + emulator + }); + } + }); + + setResponse(res, await respond(request)); }); }; } diff --git a/packages/kit/src/runtime/server/default-handler.js b/packages/kit/src/runtime/server/default-handler.js new file mode 100644 index 000000000000..5604e977cc1b --- /dev/null +++ b/packages/kit/src/runtime/server/default-handler.js @@ -0,0 +1,6 @@ +/** @import { SSRHandler } from '@sveltejs/kit' */ + +/** @type {SSRHandler} */ +export default function (server) { + return (request) => server.respond(request); +} diff --git a/packages/kit/test/apps/basics/adapter-handler.js b/packages/kit/test/apps/basics/adapter-handler.ts similarity index 76% rename from packages/kit/test/apps/basics/adapter-handler.js rename to packages/kit/test/apps/basics/adapter-handler.ts index 31ace8f78e26..5586a4847d50 100644 --- a/packages/kit/test/apps/basics/adapter-handler.js +++ b/packages/kit/test/apps/basics/adapter-handler.ts @@ -1,13 +1,12 @@ -/** @import { SSRHandler } from '@sveltejs/kit' */ +import type { SSRHandler } from '@sveltejs/kit'; /** * Custom handler for the test adapter. Intercepts requests to * `/adapter/custom-handler/intercepted` and adds a header to responses * for every other route beneath `/adapter/custom-handler`. - * @type {SSRHandler} */ -export default async function handler(server) { - return async (request, options) => { +const handler: SSRHandler = async (server) => { + return async (request) => { const { pathname } = new URL(request.url); if (pathname === '/adapter/custom-handler/intercepted') { @@ -16,7 +15,7 @@ export default async function handler(server) { }); } - const response = await server.respond(request, options); + const response = await server.respond(request); if (pathname.startsWith('/adapter/custom-handler')) { const headers = new Headers(response.headers); @@ -31,4 +30,6 @@ export default async function handler(server) { return response; }; -} +}; + +export default handler; diff --git a/packages/kit/test/apps/basics/tsconfig.json b/packages/kit/test/apps/basics/tsconfig.json index e46c34fc38fd..8bb0cb292e0e 100644 --- a/packages/kit/test/apps/basics/tsconfig.json +++ b/packages/kit/test/apps/basics/tsconfig.json @@ -11,7 +11,7 @@ "unit-test", "test", "playwright.config.js", - "adapter-handler.js", + "adapter-handler.ts", "vite.config.js" ] } diff --git a/packages/kit/test/apps/basics/vite.config.js b/packages/kit/test/apps/basics/vite.config.js index 5b63f48d72e2..d1da0c3d330d 100644 --- a/packages/kit/test/apps/basics/vite.config.js +++ b/packages/kit/test/apps/basics/vite.config.js @@ -38,7 +38,7 @@ export default defineConfig({ read: () => true, instrumentation: () => true }, - customHandler: import.meta.resolve('./adapter-handler.js') + customHandler: import.meta.resolve('./adapter-handler.ts') }, compilerOptions: { @@ -102,7 +102,14 @@ export default defineConfig({ browser: { enabled: true, provider: playwright(), - instances: [{ browser: process.env.KIT_E2E_BROWSER || 'chromium' }], + instances: [ + { + browser: + /** @type {import('vitest/node').BrowserInstanceOption['browser']} */ ( + process.env.KIT_E2E_BROWSER + ) || 'chromium' + } + ], headless: true }, include: ['unit-test/**/*.spec.js'] diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 62d525651dbc..2e3440aab488 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -60,13 +60,12 @@ declare module '@sveltejs/kit' { } /** - * The [custom handler](https://svelte.dev/docs/kit/writing-adapters#Custom-request-handler) an adapter can specify to customise request handling. It is expected to return a function which returns a `Response` or calls `server.respond` with any [platform-specific context](https://svelte.dev/docs/kit/types#Platform). Optionally, it can call `server.init` with a different set of environment variables or an implementation for [`read`](https://svelte.dev/docs/kit/$app-server#read) if the platform supports it. + * The [custom handler](https://svelte.dev/docs/kit/writing-adapters#Custom-request-handler) an adapter can specify to customise request handling. It is expected to return a function which returns a `Response` or calls `server.respond` with any [platform-specific context](https://svelte.dev/docs/kit/types#Platform). * @since 3.0.0 */ - export type SSRHandler = ( - server: Server, - env: Record - ) => Promise<(request: Request, options: Omit) => Promise>; + export type SSRHandler = (server: { + respond: (request: Request, options?: Pick) => Promise; + }) => MaybePromise<(request: Request) => MaybePromise>; export type LoadProperties | void> = input extends void ? undefined // needs to be undefined, because void will break intellisense From 673ff67ee866090f0c094c6f3d9c8cbfc64344f3 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Thu, 30 Jul 2026 01:32:15 +0800 Subject: [PATCH 104/109] remove --- packages/kit/src/types/ambient-private.d.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/packages/kit/src/types/ambient-private.d.ts b/packages/kit/src/types/ambient-private.d.ts index d16e74ba5e34..db107f1d3dcd 100644 --- a/packages/kit/src/types/ambient-private.d.ts +++ b/packages/kit/src/types/ambient-private.d.ts @@ -53,12 +53,3 @@ declare module '__sveltekit/manifest-data' { export const prerendered: Array<{ path: string }>; export const routes: Array<{ id: string }>; } - -/** Resolves to the adapter's `customHandler`, falling back to SvelteKit's default handler */ -declare module '__sveltekit/dev-handler.js' { - // eslint-disable-next-line no-duplicate-imports - import { SSRHandler } from '@sveltejs/kit'; - - const init_server: SSRHandler; - export default init_server; -} From 6ea7c3585033e730d938fa7493f7ee8178f7e78a Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Thu, 30 Jul 2026 01:32:47 +0800 Subject: [PATCH 105/109] unused --- packages/kit/src/types/global-private.d.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/packages/kit/src/types/global-private.d.ts b/packages/kit/src/types/global-private.d.ts index d4a690af5ed2..e5484bd905d8 100644 --- a/packages/kit/src/types/global-private.d.ts +++ b/packages/kit/src/types/global-private.d.ts @@ -40,15 +40,6 @@ declare global { * Should only be used when bundleStrategy !== 'inline' to avoid SvelteKit runtime changing on every build, preventing cacheability. */ const __SVELTEKIT_PAYLOAD__: SvelteKitPayload; - /** - * The Vite `root` setting used to construct paths to nodes and components - * for the SSR manifest during development - */ - const __SVELTEKIT_ROOT__: string; - const __SVELTEKIT_OUT_DIR__: string; - const __SVELTEKIT_FILES_ASSETS__: string; - /** Path to the internal SvelteKit runtime directory */ - const __SVELTEKIT_RUNTIME__: string; /** * Whether the `experimental.async` flag is applied */ From fe45851f14ccf9d742d1ea61d29f55fcbfb8adbc Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Thu, 30 Jul 2026 01:33:18 +0800 Subject: [PATCH 106/109] revert --- packages/kit/test/apps/async/vite.config.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/kit/test/apps/async/vite.config.js b/packages/kit/test/apps/async/vite.config.js index df80cff60559..ea842ed8c6fe 100644 --- a/packages/kit/test/apps/async/vite.config.js +++ b/packages/kit/test/apps/async/vite.config.js @@ -1,5 +1,4 @@ import * as path from 'node:path'; -import process from 'node:process'; import { sveltekit } from '@sveltejs/kit/vite'; import { defineConfig } from 'vitest/config'; @@ -25,9 +24,6 @@ export default defineConfig({ server: { fs: { allow: [path.resolve('../../../src')] - }, - hmr: { - overlay: process.env.PLAYWRIGHT_TEST !== '1' } }, test: { From 594833089f42aa9b81e935e2ee6af65226d3dbbd Mon Sep 17 00:00:00 2001 From: "vercel[bot]" <35613825+vercel[bot]@users.noreply.github.com> Date: Wed, 29 Jul 2026 22:09:40 +0000 Subject: [PATCH 107/109] Fix: In `vite preview`, the custom/default handler's one-time setup function (`init_server`) runs on every request instead of once at server startup, causing per-request re-initialization of stateful setup (DB pools, workers, listeners). MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit fixes the issue reported at packages/kit/src/exports/vite/preview/index.js:232 ## Bug In `packages/kit/src/exports/vite/preview/index.js`, the SSR middleware imported `handler.js` and called `init_server({ respond })` **inside the per-request middleware callback**: ```js vite.middlewares.use(async (req, res) => { ... const { default: init_server } = await import(pathToFileURL(` ``` The `SSRHandler` contract and the `writing-adapters.md` docs explicitly document the outer function as the place to *"perform setup work here"* — i.e. one-time initialization such as opening a DB connection/pool, spawning a worker, or registering listeners. The returned inner function is the per-request handler. In `preview`, the `Server` instance is created **once** at startup, yet `init_server` was being invoked **on every request**. This means: * The dynamic `import()` of the handler runs per request (cheap after caching, but still wrong). * More importantly, any stateful one-time setup inside the custom handler re-runs on every request, leaking resources (connection pools, workers) or producing incorrect behaviour. **Concrete trigger:** an adapter with a `customHandler` that opens a resource in the setup phase, e.g. ```js export default async function handler(server) { const pool = createPool(); // runs once per contract return (request) => server.respond(request, { platform: { pool } }); } ``` Under `vite preview`, `createPool()` executes for every incoming HTTP request, exhausting connections. The default handler happens to be stateless, so the bug is invisible with the default, which is why it slipped through. The reason the call was inlined is that the `respond` closure needs the per-request `req.socket` for `getClientAddress`. Unlike dev (which recreates a fresh `Server` per request for HMR and therefore legitimately re-inits), preview reuses a single long-lived `Server`, so init must be hoisted. ## Fix Hoisted the handler import and `init_server({ respond })` call to run **once** at preview startup (right after `emulator` is computed, before the returned config function). To preserve the per-request client address inside the now long-lived `respond` closure, I added a `WeakMap` that maps each incoming web `Request` to its originating Node request. The middleware registers the mapping (`request_sockets.set(request, req)`) before calling `respond`, and `getClientAddress` looks the socket up from the map: ```js const respond = await init_server({ respond: (request, options) => { const req = request_sockets.get(request); return server.respond(request, { ...options, getClientAddress: () => { const remoteAddress = req?.socket.remoteAddress; if (remoteAddress) return remoteAddress; throw new Error('Could not determine clientAddress'); }, read: (file) => { ... }, emulator }); } }); ``` Since `getRequest` returns the same `Request` object that flows through the default/custom handler into `server.respond`, the `WeakMap` lookup resolves correctly for the common path, and falls back to the existing "Could not determine clientAddress" error otherwise (same behaviour as before when the socket had no `remoteAddress`). Behaviour is otherwise unchanged; only the setup-once semantics are restored. See notes for the analogous (unfixed, lower-impact) occurrence in `prerender.js`. Co-authored-by: Vercel Co-authored-by: Rich-Harris --- .../kit/src/exports/vite/preview/index.js | 55 +++++++++++-------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/packages/kit/src/exports/vite/preview/index.js b/packages/kit/src/exports/vite/preview/index.js index ba3a7458bc34..b0636dca2ed4 100644 --- a/packages/kit/src/exports/vite/preview/index.js +++ b/packages/kit/src/exports/vite/preview/index.js @@ -68,6 +68,38 @@ export async function preview(vite, vite_config, svelte_config) { const emulator = await svelte_config.kit.adapter?.emulate?.(); + // The custom/default handler's `init_server` function performs one-time setup work + // (see the `SSRHandler` contract), so it must run once at startup rather than per + // request. We map each incoming `Request` to its originating Node request so that the + // long-lived `respond` closure can still resolve the per-request client address. + /** @type {{ default: SSRHandler }} */ + const { default: init_server } = await import(pathToFileURL(`${dir}/handler.js`).href); + + /** @type {WeakMap} */ + const request_sockets = new WeakMap(); + + const respond = await init_server({ + respond: (request, options) => { + const req = request_sockets.get(request); + return server.respond(request, { + ...options, + getClientAddress: () => { + const remoteAddress = req?.socket.remoteAddress; + if (remoteAddress) return remoteAddress; + throw new Error('Could not determine clientAddress'); + }, + read: (file) => { + if (file in manifest._.server_assets) { + return fs.readFileSync(join(dir, file)); + } + + return fs.readFileSync(join(svelte_config.kit.files.assets, file)); + }, + emulator + }); + } + }); + return () => { // Remove the base middleware. It screws with the URL. // It also only lets through requests beginning with the base path, so that requests beginning @@ -213,28 +245,7 @@ export async function preview(vite, vite_config, svelte_config) { request: req }); - /** @type {{ default: SSRHandler }} */ - const { default: init_server } = await import(pathToFileURL(`${dir}/handler.js`).href); - const respond = await init_server({ - respond: (request, options) => { - return server.respond(request, { - ...options, - getClientAddress: () => { - const { remoteAddress } = req.socket; - if (remoteAddress) return remoteAddress; - throw new Error('Could not determine clientAddress'); - }, - read: (file) => { - if (file in manifest._.server_assets) { - return fs.readFileSync(join(dir, file)); - } - - return fs.readFileSync(join(svelte_config.kit.files.assets, file)); - }, - emulator - }); - } - }); + request_sockets.set(request, req); setResponse(res, await respond(request)); }); From cb408b7d04390cd404e6f7a817ced143a9a5c49b Mon Sep 17 00:00:00 2001 From: "vercel[bot]" <35613825+vercel[bot]@users.noreply.github.com> Date: Wed, 29 Jul 2026 22:09:56 +0000 Subject: [PATCH 108/109] Fix: The custom/default `SSRHandler` setup function `init_server` is called once per prerendered path inside `visit()`, re-running handler setup for every page and violating the "setup runs once" contract. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit fixes the issue reported at packages/kit/src/core/postbuild/prerender.js:308 ## Bug `SSRHandler` is typed as: ```ts export type SSRHandler = (server: { respond: (request: Request, options?: Pick) => Promise; }) => MaybePromise<(request: Request) => MaybePromise>; ``` The **outer** function is the one-time setup phase (this is where an adapter's `customHandler` opens a DB pool, spawns a worker, registers listeners, etc.), and it returns the **inner** per-request handler. In `packages/kit/src/core/postbuild/prerender.js`, the setup call ```js const respond = await init_server({ respond: (request, options) => { ... } }); ``` was placed **inside `visit()`**, which runs once per prerendered path (via the concurrency queue `q`). This means the handler's setup phase re-runs for every prerendered route. An adapter `customHandler` that allocates resources in setup would leak one set of resources per prerendered page during the build. `git log -p` confirms this PR introduced the regression: the previous code called `server.respond` directly inline (with `custom_respond` initialized once, outside the loop), whereas the new code moved `init_server(...)` into `visit()`. This is the same class of bug as the confirmed `preview-per-request-init-server` suggestion, but occurring at build time in the prerender loop. ## Fix Hoisted the `init_server({...})` call to run **once**, right after `server.init(...)` and before the prerender queue starts. Since the `respond` closure needs per-visit state — the `dependencies` map created fresh in each `visit()` call — I threaded that state through a `WeakMap>` (`dependencies_by_request`) keyed on the web `Request` object that flows into `server.respond`. * `remote_responses`, `saved`, `emulator`, `config`, `out` are all shared/stable state, so they remain captured directly. * Only `dependencies` was per-visit; in `visit()` we now register `dependencies_by_request.set(request, dependencies)` before calling the shared `respond(request)`, and the hoisted closure looks it up via `dependencies_by_request.get(request) ?? new Map()`. `respond` is defined in the enclosing function scope after `server.init` but before the `enqueue(...)` loops that trigger `visit()` via the queue, so it is always assigned before use. The full `tsc` type check passes with no errors. Co-authored-by: Vercel Co-authored-by: Rich-Harris --- packages/kit/src/core/postbuild/prerender.js | 70 ++++++++++++-------- 1 file changed, 42 insertions(+), 28 deletions(-) diff --git a/packages/kit/src/core/postbuild/prerender.js b/packages/kit/src/core/postbuild/prerender.js index b2569e4103b3..df62b1c01fb7 100644 --- a/packages/kit/src/core/postbuild/prerender.js +++ b/packages/kit/src/core/postbuild/prerender.js @@ -266,6 +266,13 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit /** @type {Map>} */ const remote_responses = new Map(); + // the custom/default handler's setup phase must run only once (per the `SSRHandler` + // contract), so `respond` is created a single time below. Per-visit state (the + // `dependencies` map) is threaded through to the shared handler via this `WeakMap`, + // keyed on the `Request` that flows into `server.respond`. + /** @type {WeakMap>} */ + const dependencies_by_request = new WeakMap(); + /** @type {Map>} */ const expected_hashlinks = new Map(); @@ -305,34 +312,9 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit const request = new Request(prerender_origin + encoded); - const respond = await init_server({ - respond: (request, options) => { - return server.respond(request, { - ...options, - getClientAddress() { - throw new Error('Cannot read clientAddress during prerendering'); - }, - prerendering: { - dependencies, - remote_responses - }, - read: (file) => { - // stuff we just wrote - const filepath = saved.get(file); - if (filepath) return readFileSync(filepath); - - // Static assets emitted during build - if (file.startsWith(config.appDir)) { - return readFileSync(`${out}/server/${file}`); - } - - // stuff in `static` - return readFileSync(join(config.files.assets, file)); - }, - emulator - }); - } - }); + // thread the per-visit `dependencies` map through to the shared `respond` + // handler (created once, after `server.init` below) + dependencies_by_request.set(request, dependencies); const response = await respond(request); @@ -605,6 +587,38 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, vit read: (file) => createReadableStream(`${config.outDir}/output/server/${file}`) }); + // create the custom/default handler once, honouring the `SSRHandler` + // "setup runs once" contract. Per-visit `dependencies` are looked up from + // `dependencies_by_request` using the `Request` passed to `server.respond`. + const respond = await init_server({ + respond: (request, options) => { + return server.respond(request, { + ...options, + getClientAddress() { + throw new Error('Cannot read clientAddress during prerendering'); + }, + prerendering: { + dependencies: dependencies_by_request.get(request) ?? new Map(), + remote_responses + }, + read: (file) => { + // stuff we just wrote + const filepath = saved.get(file); + if (filepath) return readFileSync(filepath); + + // Static assets emitted during build + if (file.startsWith(config.appDir)) { + return readFileSync(`${out}/server/${file}`); + } + + // stuff in `static` + return readFileSync(join(config.files.assets, file)); + }, + emulator + }); + } + }); + log.info('Prerendering'); for (const entry of config.prerender.entries) { From cf9539cd725bc18f1c395b97d6aaa5f55d6bc976 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 29 Jul 2026 19:22:45 -0400 Subject: [PATCH 109/109] Update documentation/docs/25-build-and-deploy/99-writing-adapters.md --- documentation/docs/25-build-and-deploy/99-writing-adapters.md | 3 +++ 1 file changed, 3 insertions(+) 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 bd4c80169210..d4ab9d0d7304 100644 --- a/documentation/docs/25-build-and-deploy/99-writing-adapters.md +++ b/documentation/docs/25-build-and-deploy/99-writing-adapters.md @@ -106,6 +106,9 @@ export default async function handler(server) { return async (request) => { // custom request/response handling logic goes here + if (new URL(request.url).pathname === '/_ping') { + return new Response('pong'); + } return await server.respond(request, { platform: {