From 97eea561b7cb2a2a78f1dbd4e14d4389d4a3d868 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Fri, 20 Mar 2026 17:24:19 +0800 Subject: [PATCH 001/117] wip --- .../adapter-cloudflare/fallback-worker.js | 125 +++++++ packages/adapter-cloudflare/index.d.ts | 4 + packages/adapter-cloudflare/index.js | 180 ++++----- packages/adapter-cloudflare/package.json | 6 +- .../test/apps/workers/config/wrangler.jsonc | 7 +- .../test/apps/workers/package.json | 5 +- .../apps/workers/src/routes/read/+server.js | 6 +- .../test/apps/workers/vite.config.js | 8 +- packages/adapter-cloudflare/tsconfig.json | 5 +- packages/adapter-cloudflare/utils.js | 156 +------- packages/adapter-cloudflare/utils.spec.js | 258 +------------ packages/kit/kit.vitest.config.js | 4 - packages/kit/scripts/generate-dts.js | 4 +- packages/kit/src/core/postbuild/prerender.js | 5 +- packages/kit/src/core/sync/sync.js | 2 +- packages/kit/src/core/sync/write_server.js | 6 +- packages/kit/src/exports/public.d.ts | 14 +- .../kit/src/exports/vite/dev/fetchable.js | 82 +++++ packages/kit/src/exports/vite/dev/index.js | 327 +++++++++-------- packages/kit/src/exports/vite/index.js | 314 +++++++++++++--- packages/kit/src/exports/vite/module_ids.js | 7 +- .../kit/src/exports/vite/preview/index.js | 5 +- .../kit/src/runtime/app/environment/index.js | 3 +- .../src/runtime/app/environment/internal.js | 11 + .../src/runtime/app/paths/internal/server.js | 8 +- packages/kit/src/runtime/app/paths/server.js | 6 +- packages/kit/src/runtime/app/server/index.js | 4 +- .../runtime/app/server/remote/prerender.js | 2 +- .../src/runtime/app/server/remote/query.js | 6 +- .../client/remote-functions/command.svelte.js | 4 +- .../client/remote-functions/form.svelte.js | 8 +- .../remote-functions/prerender.svelte.js | 6 +- .../client/remote-functions/query.svelte.js | 10 +- packages/kit/src/runtime/client/utils.js | 4 +- packages/kit/src/runtime/server/ambient.d.ts | 4 - packages/kit/src/runtime/server/external.js | 27 ++ packages/kit/src/runtime/server/fetch.js | 4 +- packages/kit/src/runtime/server/generated.js | 128 +++++++ packages/kit/src/runtime/server/index.js | 11 +- .../kit/src/runtime/server/page/render.js | 2 +- .../src/runtime/server/page/server_routing.js | 2 +- packages/kit/src/runtime/server/remote.js | 2 +- packages/kit/src/runtime/server/respond.js | 231 ++++++------ packages/kit/src/runtime/server/utils.js | 22 +- packages/kit/src/runtime/shared-server.js | 8 - packages/kit/src/types/ambient-private.d.ts | 19 - packages/kit/src/types/global-private.d.ts | 1 + packages/kit/src/types/internal.d.ts | 22 +- packages/kit/src/types/virtual.d.ts | 14 + packages/kit/src/utils/features.js | 6 +- .../kit/test/apps/basics/svelte.config.js | 7 - packages/kit/test/mocks/path.js | 3 - packages/kit/tsconfig.json | 6 +- packages/kit/types/index.d.ts | 264 ++++++++++++- pnpm-lock.yaml | 346 +++++------------- pnpm-workspace.yaml | 2 +- 56 files changed, 1496 insertions(+), 1237 deletions(-) create mode 100644 packages/adapter-cloudflare/fallback-worker.js create mode 100644 packages/kit/src/exports/vite/dev/fetchable.js create mode 100644 packages/kit/src/runtime/app/environment/internal.js delete mode 100644 packages/kit/src/runtime/server/ambient.d.ts create mode 100644 packages/kit/src/runtime/server/external.js create mode 100644 packages/kit/src/runtime/server/generated.js create mode 100644 packages/kit/src/types/virtual.d.ts delete mode 100644 packages/kit/test/mocks/path.js diff --git a/packages/adapter-cloudflare/fallback-worker.js b/packages/adapter-cloudflare/fallback-worker.js new file mode 100644 index 000000000000..26499611189a --- /dev/null +++ b/packages/adapter-cloudflare/fallback-worker.js @@ -0,0 +1,125 @@ +import { Server } from '__sveltekit/dev'; +import { manifest, prerendered, base_path } from '__sveltekit/ssr-manifest'; +import { env } from 'cloudflare:workers'; +import { DEV } from 'esm-env'; +import * as Cache from 'worktop/cfw.cache'; + +const server = new Server(manifest); + +const app_path = `/${manifest.appPath}`; + +const immutable = `${app_path}/immutable/`; +const version_file = `${app_path}/version.json`; + +/** + * We don't know the origin until we receive a request, but + * that's guaranteed to happen before we call `read` + * @type {string} + */ +let origin; + +const initialized = server.init({ + // @ts-expect-error env contains the environment variables we need but also bindings + env, + read: async (file) => { + const url = DEV ? `${origin}${immutable}${file}` : `${origin}/${file}`; + const response = await /** @type {{ ASSETS: { fetch: typeof fetch } }} */ (env).ASSETS.fetch( + url + ); + + if (!response.ok) { + throw new Error( + `read(...) failed: could not fetch ${url} (${response.status} ${response.statusText})` + ); + } + + return response.body; + } +}); + +export default { + /** + * @param {Request} req + * @param {{ ASSETS: { fetch: typeof fetch } }} env + * @param {ExecutionContext} ctx + * @returns {Promise} + */ + async fetch(req, env, ctx) { + if (!origin) { + origin = new URL(req.url).origin; + } + + // always await initialization to prevent race condition with concurrent requests + await initialized; + + // skip cache if "cache-control: no-cache" in request + let pragma = req.headers.get('cache-control') || ''; + let res = !pragma.includes('no-cache') && (await Cache.lookup(req)); + if (res) return res; + + let { pathname, search } = new URL(req.url); + try { + pathname = decodeURIComponent(pathname); + } catch { + // ignore invalid URI + } + + const stripped_pathname = pathname.replace(/\/$/, ''); + + // files in /static, the service worker, and Vite imported server assets + let is_static_asset = false; + const filename = stripped_pathname.slice(base_path.length + 1); + if (filename) { + is_static_asset = + manifest.assets.has(filename) || + manifest.assets.has(filename + '/index.html') || + filename in manifest._.server_assets || + filename + '/index.html' in manifest._.server_assets; + } + + let location = pathname.at(-1) === '/' ? stripped_pathname : pathname + '/'; + + if ( + is_static_asset || + prerendered.has(pathname) || + pathname === version_file || + pathname.startsWith(immutable) + ) { + res = await env.ASSETS.fetch(req); + } else if (location && prerendered.has(location)) { + // trailing slash redirect for prerendered pages + if (search) location += search; + res = new Response('', { + status: 308, + headers: { + location + } + }); + } else { + // dynamically-generated pages + res = await server.respond(req, { + platform: { + env, + ctx, + // @ts-expect-error webworker types from worktop are not compatible with Cloudflare Workers types + caches, + // @ts-expect-error the type is correct but ts is confused because platform.cf uses the type from index.ts while req.cf uses the type from index.d.ts + cf: req.cf + }, + getClientAddress() { + return /** @type {string} */ (req.headers.get('cf-connecting-ip')); + } + }); + } + + // write to `Cache` only if response is not an error, + // let `Cache.save` handle the Cache-Control and Vary headers + pragma = res.headers.get('cache-control') || ''; + return pragma && res.status < 400 ? Cache.save(req, res, ctx) : res; + } +}; + +// Without this, server file changes will invalidate the entire server module graph +if (import.meta.hot) { + import.meta.hot.accept(); +} diff --git a/packages/adapter-cloudflare/index.d.ts b/packages/adapter-cloudflare/index.d.ts index b9062094bd08..940302b92d19 100644 --- a/packages/adapter-cloudflare/index.d.ts +++ b/packages/adapter-cloudflare/index.d.ts @@ -1,3 +1,4 @@ +import { PluginConfig } from '@cloudflare/vite-plugin'; import { Adapter } from '@sveltejs/kit'; import './ambient.js'; import { GetPlatformProxyOptions } from 'wrangler'; @@ -63,8 +64,11 @@ export interface AdapterOptions { /** * Config object passed to [`getPlatformProxy`](https://developers.cloudflare.com/workers/wrangler/api/#getplatformproxy) * during development and preview. + * @deprecated removed in 8.0.0 */ platformProxy?: GetPlatformProxyOptions; + + vitePluginOptions?: PluginConfig; } /** diff --git a/packages/adapter-cloudflare/index.js b/packages/adapter-cloudflare/index.js index 66c8a2da246e..33c93acc4905 100644 --- a/packages/adapter-cloudflare/index.js +++ b/packages/adapter-cloudflare/index.js @@ -1,19 +1,18 @@ -import { copyFileSync, existsSync, readFileSync, writeFileSync } from 'node:fs'; +import { copyFileSync, existsSync, writeFileSync } from 'node:fs'; import path from 'node:path'; import process from 'node:process'; import { fileURLToPath } from 'node:url'; -import { getPlatformProxy, unstable_readConfig } from 'wrangler'; -import { - is_building_for_cloudflare_pages, - validate_worker_settings, - get_routes_json, - parse_redirects -} from './utils.js'; +import { unstable_readConfig } from 'wrangler'; +import { cloudflare } from '@cloudflare/vite-plugin'; +import { validate_worker_settings } from './utils.js'; +import { DEV } from 'esm-env'; const name = '@sveltejs/adapter-cloudflare'; /** @type {import('./index.js').default} */ export default function (options = {}) { + const { wrangler_config } = validate_wrangler_config(options.config); + return { name, async adapt(builder) { @@ -38,34 +37,23 @@ export default function (options = {}) { ); } - const { wrangler_config, building_for_cloudflare_pages } = validate_wrangler_config( - options.config - ); - let dest = builder.getBuildDirectory('cloudflare'); let worker_dest = `${dest}/_worker.js`; let assets_binding = 'ASSETS'; - if (building_for_cloudflare_pages) { - if (wrangler_config.pages_build_output_dir) { - dest = wrangler_config.pages_build_output_dir; - worker_dest = `${dest}/_worker.js`; - } - } else { - if (wrangler_config.main) { - worker_dest = wrangler_config.main; - } - if (wrangler_config.assets?.directory) { - // wrangler doesn't resolve `assets.directory` to an absolute path unlike - // `main` and `pages_build_output_dir` so we need to do it ourselves here - const parent_dir = wrangler_config.configPath - ? path.dirname(path.resolve(wrangler_config.configPath)) - : process.cwd(); - dest = path.resolve(parent_dir, wrangler_config.assets.directory); - } - if (wrangler_config.assets?.binding) { - assets_binding = wrangler_config.assets.binding; - } + if (wrangler_config.main) { + worker_dest = wrangler_config.main; + } + if (wrangler_config.assets?.directory) { + // wrangler doesn't resolve `assets.directory` to an absolute path unlike + // `main` and `pages_build_output_dir` so we need to do it ourselves here + const parent_dir = wrangler_config.configPath + ? path.dirname(path.resolve(wrangler_config.configPath)) + : process.cwd(); + dest = path.resolve(parent_dir, wrangler_config.assets.directory); + } + if (wrangler_config.assets?.binding) { + assets_binding = wrangler_config.assets.binding; } const files = fileURLToPath(new URL('./files', import.meta.url).href); @@ -80,10 +68,7 @@ export default function (options = {}) { // client assets and prerendered pages const assets_dest = `${dest}${builder.config.kit.paths.base}`; builder.mkdirp(assets_dest); - if ( - building_for_cloudflare_pages || - wrangler_config.assets?.not_found_handling === '404-page' - ) { + if (wrangler_config.assets?.not_found_handling === '404-page') { // generate plaintext 404.html first which can then be overridden by prerendering, if the user defined such a page. // This file is served when a request fails to match an asset. // If we're building for Cloudflare Pages, it's only served when a request matches an entry in `routes.exclude` @@ -94,12 +79,9 @@ export default function (options = {}) { writeFileSync(fallback, 'Not Found'); } } - const client_assets = builder.writeClient(assets_dest); + builder.writeClient(assets_dest); builder.writePrerendered(assets_dest); - if ( - !building_for_cloudflare_pages && - wrangler_config.assets?.not_found_handling === 'single-page-application' - ) { + if (wrangler_config.assets?.not_found_handling === 'single-page-application') { await builder.generateFallback(path.join(assets_dest, 'index.html')); } @@ -150,70 +132,52 @@ export default function (options = {}) { }); } - if (building_for_cloudflare_pages) { - // _routes.json - - // we need to add the source paths found in the `_redirects` file to the - // `_routes.json` file so that Cloudflare knows it shouldn't invoke the - // Worker but instead let the rules in the `_redirects` file take over. - /** @type {string[]} */ - let redirects = []; - if (existsSync(redirects_dest)) { - const redirect_rules = readFileSync(redirects_dest, 'utf8'); - redirects = parse_redirects(redirect_rules); - } - - writeFileSync( - `${dest}/_routes.json`, - JSON.stringify( - get_routes_json(builder, client_assets, redirects, options.routes ?? {}), - null, - '\t' - ) - ); - } else { - writeFileSync(`${dest}/.assetsignore`, generate_assetsignore(), { flag: 'a' }); - } - }, - emulate() { - // we want to invoke `getPlatformProxy` only once, but await it only when it is accessed. - // If we would await it here, it would hang indefinitely because the platform proxy only resolves once a request happens - const get_emulated = async () => { - const proxy = await getPlatformProxy(options.platformProxy); - const platform = /** @type {App.Platform} */ ({ - env: proxy.env, - ctx: proxy.ctx, - context: proxy.ctx, // deprecated in favor of ctx - caches: proxy.caches, - cf: proxy.cf - }); - /** @type {Record} */ - const env = {}; - const prerender_platform = /** @type {App.Platform} */ (/** @type {unknown} */ ({ env })); - for (const key in proxy.env) { - Object.defineProperty(env, key, { - get: () => { - throw new Error(`Cannot access platform.env.${key} in a prerenderable route`); - } - }); - } - return { platform, prerender_platform }; - }; - - /** @type {{ platform: App.Platform, prerender_platform: App.Platform }} */ - let emulated; - - return { - platform: async ({ prerender }) => { - emulated ??= await get_emulated(); - return prerender ? emulated.prerender_platform : emulated.platform; - } - }; + writeFileSync(`${dest}/.assetsignore`, generate_assetsignore(), { flag: 'a' }); }, supports: { read: () => true, instrumentation: () => true - } + }, + vitePlugins: [ + cloudflare({ + ...options.vitePluginOptions, + configPath: options.vitePluginOptions?.configPath ?? options.config, + viteEnvironment: { + name: options.vitePluginOptions?.viteEnvironment?.name ?? 'ssr', + childEnvironments: options.vitePluginOptions?.viteEnvironment?.childEnvironments + }, + config: (user_config) => { + // user programmatic config + if (typeof options.vitePluginOptions?.config === 'function') { + options.vitePluginOptions?.config(user_config); + } else { + Object.assign(user_config, options.vitePluginOptions?.config); + } + + if (DEV) { + if (!user_config.assets?.binding) { + user_config.assets = { + binding: 'ASSETS' + }; + } + + if (!user_config.main) { + user_config.main = path.resolve(import.meta.dirname, 'fallback-worker.js'); + } + } else { + // TODO: if `main` or `assets.binding` is configured, ensure `main`, `assets.directory` and `assets.binding` are populated + } + + if ( + !user_config.compatibility_flags.find( + (flag) => flag === 'nodejs_als' || flag === 'nodejs_compat' + ) + ) { + user_config.compatibility_flags.push('nodejs_als'); + } + } + }) + ] }; } @@ -267,24 +231,16 @@ _redirects /** * @param {string | undefined} config_file * @returns {{ - * wrangler_config: import('wrangler').Unstable_Config, - * building_for_cloudflare_pages: boolean + * wrangler_config: import('wrangler').Unstable_Config * }} */ function validate_wrangler_config(config_file = undefined) { const wrangler_config = unstable_readConfig({ config: config_file }); - const building_for_cloudflare_pages = is_building_for_cloudflare_pages(wrangler_config); - - // we don't need to validate the config if we're building for Cloudflare Pages - // because the `main` and `assets` values cannot be changed there - if (!building_for_cloudflare_pages) { - validate_worker_settings(wrangler_config); - } + validate_worker_settings(wrangler_config); return { - wrangler_config, - building_for_cloudflare_pages + wrangler_config }; } diff --git a/packages/adapter-cloudflare/package.json b/packages/adapter-cloudflare/package.json index 8b185d760303..2f5c592b86de 100644 --- a/packages/adapter-cloudflare/package.json +++ b/packages/adapter-cloudflare/package.json @@ -44,7 +44,8 @@ "prepublishOnly": "pnpm build" }, "dependencies": { - "@cloudflare/workers-types": "^4.20260219.0", + "@cloudflare/workers-types": "^4.20260312.0", + "esm-env": "^1.2.2", "worktop": "0.8.0-next.18" }, "devDependencies": { @@ -58,6 +59,7 @@ }, "peerDependencies": { "@sveltejs/kit": "^3.0.0", - "wrangler": "^4.67.0" + "wrangler": "^4.74.0", + "@cloudflare/vite-plugin": "^1.29.0" } } diff --git a/packages/adapter-cloudflare/test/apps/workers/config/wrangler.jsonc b/packages/adapter-cloudflare/test/apps/workers/config/wrangler.jsonc index 0bf8a9db8c3a..b3975e5485e1 100644 --- a/packages/adapter-cloudflare/test/apps/workers/config/wrangler.jsonc +++ b/packages/adapter-cloudflare/test/apps/workers/config/wrangler.jsonc @@ -1,10 +1,5 @@ // we've moved the wrangler config away from the root of the project // to test that the adapter still resolves the paths correctly { - "$schema": "../node_modules/wrangler/config-schema.json", - "main": "../dist/index.js", - "assets": { - "directory": "../dist/public", - "binding": "ASSETS" - } + "$schema": "../../../../node_modules/wrangler/config-schema.json", } diff --git a/packages/adapter-cloudflare/test/apps/workers/package.json b/packages/adapter-cloudflare/test/apps/workers/package.json index 4032805d1ec6..3790a9b3a242 100644 --- a/packages/adapter-cloudflare/test/apps/workers/package.json +++ b/packages/adapter-cloudflare/test/apps/workers/package.json @@ -5,7 +5,7 @@ "scripts": { "dev": "vite dev", "build": "vite build", - "preview": "wrangler dev dist/index.js", + "preview": "vite preview -p 8787", "prepare": "svelte-kit sync || echo ''", "test:dev": "DEV=true playwright test", "test:build": "playwright test", @@ -16,8 +16,7 @@ "@sveltejs/vite-plugin-svelte": "catalog:", "server-side-dep": "file:server-side-dep", "svelte": "catalog:", - "vite": "catalog:", - "wrangler": "catalog:" + "vite": "catalog:" }, "type": "module" } diff --git a/packages/adapter-cloudflare/test/apps/workers/src/routes/read/+server.js b/packages/adapter-cloudflare/test/apps/workers/src/routes/read/+server.js index 47717c4e0511..06a7b16f7cac 100644 --- a/packages/adapter-cloudflare/test/apps/workers/src/routes/read/+server.js +++ b/packages/adapter-cloudflare/test/apps/workers/src/routes/read/+server.js @@ -1,6 +1,8 @@ import { read } from '$app/server'; import file from './file.txt?url'; -export function GET() { - return read(file); +export async function GET() { + const asset = read(file); + const text = await asset.text(); + return new Response(text, asset); } diff --git a/packages/adapter-cloudflare/test/apps/workers/vite.config.js b/packages/adapter-cloudflare/test/apps/workers/vite.config.js index 29ad08debe6a..b80757c292ee 100644 --- a/packages/adapter-cloudflare/test/apps/workers/vite.config.js +++ b/packages/adapter-cloudflare/test/apps/workers/vite.config.js @@ -1,11 +1,17 @@ import { sveltekit } from '@sveltejs/kit/vite'; +import adapter from '../../../index.js'; /** @type {import('vite').UserConfig} */ const config = { build: { minify: false }, - plugins: [sveltekit()] + plugins: [sveltekit({ adapter: adapter() })], + server: { + fs: { + allow: ['../../../../kit'] + } + } }; export default config; diff --git a/packages/adapter-cloudflare/tsconfig.json b/packages/adapter-cloudflare/tsconfig.json index 8e8b455d2fd2..d8e4d8e14dc5 100644 --- a/packages/adapter-cloudflare/tsconfig.json +++ b/packages/adapter-cloudflare/tsconfig.json @@ -13,16 +13,17 @@ // taken from the Cloudflare Workers TypeScript template https://github.com/cloudflare/workers-sdk/blob/main/packages/create-cloudflare/templates/hello-world/ts/tsconfig.json "target": "es2024", "lib": ["es2024"], - "types": ["@cloudflare/workers-types"] + "types": ["@cloudflare/workers-types", "../kit/src/types/virtual.d.ts"] }, "include": [ "index.js", + "fallback-worker.js", "utils.js", "utils.spec.js", "rolldown.config.js", "vitest.config.js", - "test/utils.js", "internal.d.ts", + "test/utils.js", "src/worker.js" ] } diff --git a/packages/adapter-cloudflare/utils.js b/packages/adapter-cloudflare/utils.js index 2b1c7c74b567..ea4a11f5481f 100644 --- a/packages/adapter-cloudflare/utils.js +++ b/packages/adapter-cloudflare/utils.js @@ -1,24 +1,4 @@ -import process from 'node:process'; - -/** - * @param {import('wrangler').Unstable_Config} wrangler_config - * @returns {boolean} - */ -export function is_building_for_cloudflare_pages(wrangler_config) { - if (process.env.CF_PAGES || wrangler_config.pages_build_output_dir) { - return true; - } - - if (!!process.env.WORKERS_CI || wrangler_config.main || wrangler_config.assets) { - return false; - } - - return true; -} - -/** - * @param {import('wrangler').Unstable_Config} wrangler_config - */ +/** @param {import('wrangler').Unstable_Config} wrangler_config */ export function validate_worker_settings(wrangler_config) { const config_path = wrangler_config.configPath || 'your wrangler.jsonc file'; @@ -29,137 +9,5 @@ export function validate_worker_settings(wrangler_config) { ); } - // we need the `assets.directory` key so that the static assets are deployed - if ((wrangler_config.main || wrangler_config.assets) && !wrangler_config.assets?.directory) { - throw new Error( - `You must specify the \`assets.directory\` key in ${config_path}. Consult https://developers.cloudflare.com/workers/static-assets/binding/#directory` - ); - } - - // we need the `assets.binding` key so that the Worker can access the static assets - if (wrangler_config.main && !wrangler_config.assets?.binding) { - throw new Error( - `You must specify the \`assets.binding\` key in ${config_path} before deploying your Worker. Consult https://developers.cloudflare.com/workers/static-assets/binding/#binding` - ); - } - - // the user might have forgot the `main` key or should remove the `assets.binding` - // key to deploy static assets without a Worker - if (!wrangler_config.main && wrangler_config.assets?.binding) { - throw new Error( - `You must specify the \`main\` key in ${config_path} if you want to deploy a Worker alongside your static assets. Otherwise, remove the \`assets.binding\` key if you only want to deploy static assets.` - ); - } -} - -/** - * Extracts the redirect source from each line of a [_redirects](https://developers.cloudflare.com/pages/configuration/redirects/) - * file so we can exclude them in [_routes.json](https://developers.cloudflare.com/pages/functions/routing/#create-a-_routesjson-file) - * to ensure the redirect is invoked instead of the Cloudflare Worker. - * @param {string} file_contents - * @returns {string[]} - */ -export function parse_redirects(file_contents) { - /** @type {string[]} */ - const redirects = []; - - for (const line of file_contents.split('\n')) { - const content = line.trim(); - if (!content || content.startsWith('#')) continue; - - const [pathname] = line.split(' '); - // pathnames with placeholders are not supported - if (!pathname || pathname.includes('/:')) { - throw new Error(`The following _redirects rule cannot be excluded by _routes.json: ${line}`); - } - redirects.push(pathname); - } - - return redirects; -} - -/** - * Generates the [_routes.json](https://developers.cloudflare.com/pages/functions/routing/#create-a-_routesjson-file) - * file that dictates which routes invoke the Cloudflare Worker. - * @param {import('@sveltejs/kit').Builder} builder - * @param {string[]} client_assets - * @param {string[]} redirects - * @param {import('./index.js').AdapterOptions['routes']} routes - * @returns {import('./index.js').RoutesJSONSpec} - */ -export function get_routes_json(builder, client_assets, redirects, routes) { - const include = routes?.include ?? ['/*']; - let exclude = routes?.exclude ?? ['']; - - if (!Array.isArray(include) || !Array.isArray(exclude)) { - throw new Error('routes.include and routes.exclude must be arrays'); - } - - if (include?.length === 0) { - throw new Error('routes.include must contain at least one route'); - } - - if (include?.length > 100) { - throw new Error('routes.include must contain 100 or fewer routes'); - } - - /** @type {Set} */ - const transformed_rules = new Set(); - for (const rule of exclude) { - if (rule === '') { - transformed_rules.add(''); - transformed_rules.add(''); - transformed_rules.add(''); - transformed_rules.add(''); - } else { - transformed_rules.add(rule); - } - } - - /** @type {Set} */ - const excluded_routes = new Set(); - for (const rule of transformed_rules) { - if (rule === '') { - const app_path = builder.getAppPath(); - excluded_routes.add(`/${app_path}/version.json`); - excluded_routes.add(`/${app_path}/immutable/*`); - continue; - } - - if (rule === '') { - for (const file of client_assets) { - if (file.startsWith(`${builder.config.kit.appDir}/`)) continue; - excluded_routes.add(`${builder.config.kit.paths.base}/${file}`); - } - continue; - } - - if (rule === '') { - builder.prerendered.paths.forEach((path) => excluded_routes.add(path)); - continue; - } - - if (rule === '') { - redirects.forEach((path) => excluded_routes.add(path)); - continue; - } - - excluded_routes.add(rule); - } - exclude = Array.from(excluded_routes); - - const excess = include.length + exclude.length - 100; - if (excess > 0) { - builder.log.warn( - `Cloudflare Pages Functions' includes/excludes exceeds _routes.json limits (see https://developers.cloudflare.com/pages/platform/functions/routing/#limits). Dropping ${excess} exclude rules — this will cause unnecessary function invocations.` - ); - exclude.length -= excess; - } - - return { - version: 1, - description: 'Generated by @sveltejs/adapter-cloudflare', - include, - exclude - }; + // TODO: error on cloudflare pages configurations? } diff --git a/packages/adapter-cloudflare/utils.spec.js b/packages/adapter-cloudflare/utils.spec.js index 7d3755559376..229a0fbef60a 100644 --- a/packages/adapter-cloudflare/utils.spec.js +++ b/packages/adapter-cloudflare/utils.spec.js @@ -1,84 +1,8 @@ -import { describe, test, vi, expect } from 'vitest'; +import { describe, test, expect } from 'vitest'; import { - is_building_for_cloudflare_pages, validate_worker_settings, - get_routes_json, - parse_redirects } from './utils.js'; -describe('detects Cloudflare Pages project', () => { - test('by default', () => { - expect( - is_building_for_cloudflare_pages(/** @type {import('wrangler').Unstable_Config} */ ({})) - ).toBe(true); - }); - - test('CF_PAGES environment variable', () => { - vi.stubEnv('CF_PAGES', '1'); - const result = is_building_for_cloudflare_pages( - /** @type {import('wrangler').Unstable_Config} */ ({}) - ); - vi.unstubAllEnvs(); - expect(result).toBe(true); - }); - - test('empty Wrangler configuration file', () => { - expect( - is_building_for_cloudflare_pages( - /** @type {import('wrangler').Unstable_Config} */ ({ - configPath: 'wrangler.jsonc' - }) - ) - ).toBe(true); - }); - - test('pages_build_output_dir config key', () => { - expect( - is_building_for_cloudflare_pages( - /** @type {import('wrangler').Unstable_Config} */ ({ - configPath: 'wrangler.jsonc', - pages_build_output_dir: 'dist' - }) - ) - ).toBe(true); - }); -}); - -describe('detects Cloudflare Workers project', () => { - test('main config key', () => { - expect( - is_building_for_cloudflare_pages( - /** @type {import('wrangler').Unstable_Config} */ ({ - configPath: 'wrangler.jsonc', - main: 'dist/index.js' - }) - ) - ).toBe(false); - }); - - test('assets config key', () => { - expect( - is_building_for_cloudflare_pages( - /** @type {import('wrangler').Unstable_Config} */ ({ - configPath: 'wrangler.jsonc', - assets: { - directory: 'dist/assets' - } - }) - ) - ).toBe(false); - }); - - test('WORKERS_CI environment variable', () => { - vi.stubEnv('WORKERS_CI', '1'); - const result = is_building_for_cloudflare_pages( - /** @type {import('wrangler').Unstable_Config} */ ({}) - ); - vi.unstubAllEnvs(); - expect(result).toBe(false); - }); -}); - describe('validates Wrangler config', () => { test('Worker and static assets', () => { expect(() => @@ -156,183 +80,3 @@ describe('validates Wrangler config', () => { ); }); }); - -test('ignores comments in _redirects file', () => { - const redirects = parse_redirects( - ` -# This is a comment -/home301 / 301 - # Indented comment -/blog/* https://blog.my.domain/:splat -`.trim() - ); - - expect(redirects).toEqual(['/home301', '/blog/*']); -}); - -test('parses _redirects file', () => { - const redirects = parse_redirects( - ` -/home301 / 301 -/notrailing/ /nottrailing 301 - -/blog/* https://blog.my.domain/:splat -`.trim() - ); - - expect(redirects).toEqual(['/home301', '/notrailing/', '/blog/*']); -}); - -test('generates a _routes.json file', () => { - const routes = get_routes_json( - { - getAppPath: () => 'base-path/_app', - config: { - kit: { - appDir: '_app', - paths: { - base: '/base-path', - assets: '', - relative: true - }, - alias: {}, - csrf: { - checkOrigin: true, - trustedOrigins: [] - }, - embedded: false, - files: { - src: 'src', - assets: 'static', - hooks: { - client: 'src/hooks.client.js', - server: 'src/hooks.server.js', - universal: 'src/hooks.js' - }, - lib: 'src/lib', - params: 'src/params', - routes: 'src/routes', - serviceWorker: 'src/service-worker.js', - appTemplate: 'src/app.html', - errorTemplate: 'src/error.html' - }, - inlineStyleThreshold: 0, - moduleExtensions: ['.js', '.ts'], - csp: { - mode: 'auto', - // @ts-ignore - directives: {}, - // @ts-ignore - reportOnly: {} - }, - env: { - dir: '.', - publicPrefix: 'PUBLIC_', - privatePrefix: '' - }, - outDir: '.svelte-kit' - } - }, - prerendered: { - paths: ['/base-path/prerendered'], - pages: new Map(), - assets: new Map(), - redirects: new Map() - } - }, - ['_app/immutable/this-should-not-be-excluded.js', 'robots.txt'], - ['/base-path/redirect'], - undefined - ); - - expect(routes).toEqual({ - version: 1, - description: 'Generated by @sveltejs/adapter-cloudflare', - include: ['/*'], - exclude: [ - '/base-path/_app/version.json', - '/base-path/_app/immutable/*', - '/base-path/robots.txt', - '/base-path/prerendered', - '/base-path/redirect' - ] - }); -}); - -test('truncates excess _routes.json exclude rules', () => { - const routes = get_routes_json( - { - // @ts-ignore - log: { - warn: console.warn - }, - getAppPath: () => 'base-path/_app', - config: { - kit: { - appDir: '_app', - paths: { - base: '/base-path', - assets: '', - relative: true - }, - alias: {}, - csrf: { - checkOrigin: true, - trustedOrigins: [] - }, - embedded: false, - files: { - src: 'src', - assets: 'static', - hooks: { - client: 'src/hooks.client.js', - server: 'src/hooks.server.js', - universal: 'src/hooks.js' - }, - lib: 'src/lib', - params: 'src/params', - routes: 'src/routes', - serviceWorker: 'src/service-worker.js', - appTemplate: 'src/app.html', - errorTemplate: 'src/error.html' - }, - inlineStyleThreshold: 0, - moduleExtensions: ['.js', '.ts'], - csp: { - mode: 'auto', - // @ts-ignore - directives: {}, - // @ts-ignore - reportOnly: {} - }, - env: { - dir: '.', - publicPrefix: 'PUBLIC_', - privatePrefix: '' - }, - outDir: '.svelte-kit' - } - }, - prerendered: { - paths: Array.from({ length: 100 }, (_, i) => `/base-path/blog/post/${i + 1}`), - pages: new Map(), - assets: new Map(), - redirects: new Map() - } - }, - ['_app/immutable/this-should-not-be-excluded.js', 'robots.txt'], - [], - undefined - ); - - expect(routes).toEqual({ - version: 1, - description: 'Generated by @sveltejs/adapter-cloudflare', - include: ['/*'], - exclude: [ - '/base-path/_app/version.json', - '/base-path/_app/immutable/*', - '/base-path/robots.txt' - ].concat(Array.from({ length: 96 }, (_, i) => `/base-path/blog/post/${i + 1}`)) - }); -}); diff --git a/packages/kit/kit.vitest.config.js b/packages/kit/kit.vitest.config.js index 6683a04297c4..641cbf0c8327 100644 --- a/packages/kit/kit.vitest.config.js +++ b/packages/kit/kit.vitest.config.js @@ -1,4 +1,3 @@ -import { fileURLToPath } from 'node:url'; import { defineConfig } from 'vitest/config'; // this file needs a custom name so that the numerous test subprojects don't all pick it up @@ -12,9 +11,6 @@ export default defineConfig({ } }, test: { - alias: { - '__sveltekit/paths': fileURLToPath(new URL('./test/mocks/path.js', import.meta.url)) - }, pool: 'threads', maxWorkers: 1, include: ['src/**/*.spec.js'], diff --git a/packages/kit/scripts/generate-dts.js b/packages/kit/scripts/generate-dts.js index 60c7fe7d103f..f036f3be28a7 100644 --- a/packages/kit/scripts/generate-dts.js +++ b/packages/kit/scripts/generate-dts.js @@ -8,6 +8,8 @@ await createBundle({ '@sveltejs/kit/hooks': 'src/exports/hooks/index.js', '@sveltejs/kit/node': 'src/exports/node/index.js', '@sveltejs/kit/vite': 'src/exports/vite/index.js', + '@sveltejs/kit/internal': 'src/exports/internal/index.js', + '@sveltejs/kit/internal/server': 'src/exports/internal/server.js', '$app/environment': 'src/runtime/app/environment/types.d.ts', '$app/forms': 'src/runtime/app/forms.js', '$app/navigation': 'src/runtime/app/navigation.js', @@ -23,7 +25,7 @@ await createBundle({ const types = readFileSync('./types/index.d.ts', 'utf-8'); if (types.includes('__sveltekit/')) { throw new Error( - 'Found __sveltekit/ in types/index.d.ts - make sure to hide internal modules by not just reexporting them. Contents:\n\n' + + 'Found __sveltekit/ in types/index.d.ts - make sure to hide internal modules by not just re-exporting them. Contents:\n\n' + types ); } diff --git a/packages/kit/src/core/postbuild/prerender.js b/packages/kit/src/core/postbuild/prerender.js index f8d8603bdb32..bcee921eb2b1 100644 --- a/packages/kit/src/core/postbuild/prerender.js +++ b/packages/kit/src/core/postbuild/prerender.js @@ -120,8 +120,6 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, roo return { prerendered, prerender_map }; } - const emulator = await config.adapter?.emulate?.(); - /** @type {import('types').Logger} */ const log = logger({ verbose }); @@ -258,8 +256,7 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, roo // stuff in `static` return readFileSync(join(config.files.assets, file)); - }, - emulator + } }); const encoded_id = response.headers.get('x-sveltekit-routeid'); diff --git a/packages/kit/src/core/sync/sync.js b/packages/kit/src/core/sync/sync.js index 839e877716d0..1e233f2f9f24 100644 --- a/packages/kit/src/core/sync/sync.js +++ b/packages/kit/src/core/sync/sync.js @@ -94,7 +94,7 @@ export function all_types(config, mode) { } /** - * Regenerate __SERVER__/internal.js in response to src/{app.html,error.html,service-worker.js} changing + * Regenerate `${output}/server/internal.js` in response to `src/{app.html,error.html,service-worker.js}` changing * @param {import('types').ValidatedConfig} config * @param {string} root The project root directory */ diff --git a/packages/kit/src/core/sync/write_server.js b/packages/kit/src/core/sync/write_server.js index ebc1db336184..8b5e69e21408 100644 --- a/packages/kit/src/core/sync/write_server.js +++ b/packages/kit/src/core/sync/write_server.js @@ -29,9 +29,9 @@ const server_template = ({ error_page }) => ` import root from '../root.js'; -import { set_building, set_prerendering } from '__sveltekit/environment'; -import { set_assets } from '$app/paths/internal/server'; -import { set_manifest, set_read_implementation } from '__sveltekit/server'; +import { set_building, set_prerendering } from '${runtime_directory}/app/environment/internal.js'; +import { set_assets } from '${runtime_directory}/app/paths/internal/server.js'; +import { set_manifest, set_read_implementation } from '${runtime_directory}/server/external.js'; import { set_private_env, set_public_env } from '${runtime_directory}/shared-server.js'; export const options = { diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index 30fcb4ca3102..4d51510e2ddd 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -2,6 +2,10 @@ import 'svelte'; // pick up `declare module "*.svelte"` import 'vite/client'; // pick up `declare module "*.jpg"`, etc. import '../types/ambient.js'; +import { SvelteConfig } from '@sveltejs/vite-plugin-svelte'; +import { StandardSchemaV1 } from '@standard-schema/spec'; +import { PluginOption } from 'vite'; + import { AdapterEntry, CspDirectives, @@ -20,8 +24,6 @@ import { IsAny } from '../types/private.js'; import { BuildData, SSRNodeLoader, SSRRoute, ValidatedConfig } from 'types'; -import { SvelteConfig } from '@sveltejs/vite-plugin-svelte'; -import { StandardSchemaV1 } from '@standard-schema/spec'; import { RouteId as AppRouteId, LayoutParams as AppLayoutParams, @@ -65,8 +67,13 @@ export interface Adapter { /** * Creates an `Emulator`, which allows the adapter to influence the environment * during dev, build and prerendering. + * @deprecated removed in 3.0.0 */ emulate?: () => MaybePromise; + /** + * @since 3.0.0 + */ + vitePlugins?: PluginOption; } export type LoadProperties | void> = input extends void @@ -1584,6 +1591,9 @@ export interface ServerInitOptions { read?: (file: string) => MaybePromise; } +/** + * Powers the server + */ export interface SSRManifest { appDir: string; appPath: string; diff --git a/packages/kit/src/exports/vite/dev/fetchable.js b/packages/kit/src/exports/vite/dev/fetchable.js new file mode 100644 index 000000000000..171588286fab --- /dev/null +++ b/packages/kit/src/exports/vite/dev/fetchable.js @@ -0,0 +1,82 @@ +// import { buildErrorMessage, createServer } from 'vite'; + +// `posixify` and `to_fs` are duplicated from utils/filesystem.js to avoid +// imports from `node:*` which aren't available in Cloudflare's workerd runtime + +/** @param {string} str */ +function posixify(str) { + return str.replace(/\\/g, '/'); +} + +/** + * Prepend given path with `/@fs` prefix + * @param {string} str + */ +function to_fs(str) { + str = posixify(str); + return `/@fs${ + // Windows/Linux separation - Windows starts with a drive letter, we need a / in front there + str.startsWith('/') ? '' : '/' + }${str}`; +} + +/** + * Removes `/@fs` prefix from given path and posixifies it + * @param {string} str + */ +export function from_fs(str) { + str = posixify(str); + if (!str.startsWith('/@fs')) return str; + + str = str.slice(4); + // Windows/Linux separation - Windows starts with a drive letter, we need to strip the additional / here + return str[2] === ':' && /[A-Z]/.test(str[1]) ? str.slice(1) : str; +} + +// const server = await createServer({}); +// const ssr_environment = server.environments.ssr; + +/** @param {string} id */ +export async function resolve(id) { + // TODO: doesn't work for files symlinked to kit package workspace + const url = id.startsWith('..') ? to_fs(id) : `${id}`; + + const module = await loud_ssr_load_module(url); + + // const module_node = await ssr_environment.moduleGraph.getModuleByUrl(url); + // if (!module_node) throw new Error(`Could not find node for ${url}`); + + return { module, module_node: '', url }; +} + +/** + * @param {string} url + */ +export async function loud_ssr_load_module(url) { + // TODO: properly implement this for fetchable environments + // eslint-disable-next-line no-useless-catch + try { + // return await server.ssrLoadModule(url, { fixStacktrace: true }); + return await import(/* @vite-ignore */ url); + } catch (/** @type {any} */ err) { + // const msg = buildErrorMessage(err, [styleText('red', `Internal server error: ${err.message}`)]); + // const msg = buildErrorMessage(err, [`Internal server error: ${err.message}`]); + + // if (!server.config.logger.hasErrorLogged(err)) { + // server.config.logger.error(msg, { error: err }); + // } + + // server.ws.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; + } +} diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 52923f56e880..344463670748 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -1,10 +1,11 @@ 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 { coalesce_to_error } from '../../../utils/error.js'; import { from_fs, posixify, resolve_entry, to_fs } from '../../../utils/filesystem.js'; @@ -14,9 +15,8 @@ import * as sync from '../../../core/sync/sync.js'; import { get_mime_lookup, get_runtime_base } from '../../../core/utils.js'; import { compact } from '../../../utils/array.js'; import { not_found } from '../utils.js'; -import { SCHEME } from '../../../utils/url.js'; -import { check_feature } from '../../../utils/features.js'; import { escape_html } from '../../../utils/escape.js'; +import { sveltekit_ssr_manifest } from '../module_ids.js'; // vite-specifc queries that we should skip handling for css urls const vite_css_query_regex = /(?:\?|&)(?:raw|url|inline)(?:&|$)/; @@ -27,29 +27,10 @@ const vite_css_query_regex = /(?:\?|&)(?:raw|url|inline)(?:&|$)/; * @param {import('types').ValidatedConfig} svelte_config * @param {() => Array<{ hash: string, file: string }>} get_remotes * @param {string} root The project root directory - * @return {Promise void>>} + * @param {import('types').DevEnvironment} dev_environment + * @return {() => void} */ -export async function dev(vite, vite_config, svelte_config, get_remotes, root) { - const async_local_storage = new AsyncLocalStorage(); - - 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, 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, get_remotes, root, dev_environment) { sync.init(svelte_config, vite_config.mode, root); /** @type {import('types').ManifestData} */ @@ -94,7 +75,7 @@ export async function dev(vite, vite_config, svelte_config, get_remotes, root) { 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 }; @@ -103,6 +84,7 @@ export async function dev(vite, vite_config, svelte_config, get_remotes, root) { function update_manifest() { try { ({ manifest_data } = sync.create(svelte_config, root)); + dev_environment.manifest_data = manifest_data; if (manifest_error) { manifest_error = null; @@ -185,7 +167,7 @@ export async function dev(vite, vite_config, svelte_config, get_remotes, root) { result.stylesheets = []; result.fonts = []; - /** @type {import('vite').ModuleNode[]} */ + /** @type {import('vite').EnvironmentModuleNode[]} */ const module_nodes = []; if (node.component) { @@ -301,17 +283,8 @@ export async function dev(vite, vite_config, svelte_config, get_remotes, root) { } } }; - } - - /** @param {Error} error */ - function fix_stack_trace(error) { - 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 - } - return error.stack; + dev_environment.manifest = manifest; + invalidate_module(vite, sveltekit_ssr_manifest); } update_manifest(); @@ -430,7 +403,7 @@ export async function dev(vite, vite_config, svelte_config, get_remotes, root) { }); const env = loadEnv(vite_config.mode, svelte_config.kit.env.dir, ''); - const emulator = await svelte_config.kit.adapter?.emulate?.(); + dev_environment.env = env; return () => { const serve_static_middleware = vite.middlewares.stack.find( @@ -442,141 +415,180 @@ export async function dev(vite, vite_config, svelte_config, get_remotes, root) { // serving routes with those names. See https://github.com/vitejs/vite/issues/7363 remove_static_middlewares(vite.middlewares); - vite.middlewares.use(async (req, res) => { - // Vite's base middleware strips out the base path. Restore it - const original_url = req.url; - req.url = req.originalUrl; - try { - const base = `${vite.config.server.https ? 'https' : 'http'}://${ - req.headers[':authority'] || req.headers.host - }`; - - const decoded = decodeURI(new URL(base + req.url).pathname); - const file = posixify( - path.resolve(root, decoded.slice(svelte_config.kit.paths.base.length + 1)) - ); - const is_file = fs.existsSync(file) && !fs.statSync(file).isDirectory(); - const allowed = - !vite_config.server.fs.strict || - vite_config.server.fs.allow.some((dir) => file.startsWith(dir)); - - if (is_file && allowed) { - req.url = original_url; - // @ts-expect-error - serve_static_middleware.handle(req, res); - return; - } - - if (!decoded.startsWith(svelte_config.kit.paths.base)) { - return not_found(req, res, svelte_config.kit.paths.base); - } + if (svelte_config.kit.adapter?.vitePlugins) { + vite.middlewares.use((req, res, next) => { + // Vite's base middleware strips out the base path. Restore it + let original_url = req.url; + req.url = req.originalUrl; + try { + const base = `${vite.config.server.https ? 'https' : 'http'}://${ + req.headers[':authority'] || req.headers.host + }`; + + let decoded = decodeURI(new URL(base + req.url).pathname); + + // requests to _app/immutable during development are fetchable dev + // environments trying to read the filesystem + const immutable = `/${manifest.appPath}/immutable`; + if (decoded.startsWith(immutable)) { + decoded = decoded.slice(immutable.length); + original_url = original_url?.slice(immutable.length); + } - if (decoded === svelte_config.kit.paths.base + '/service-worker.js') { - const resolved = resolve_entry(svelte_config.kit.files.serviceWorker); + const file = posixify( + path.resolve(root, decoded.slice(svelte_config.kit.paths.base.length + 1)) + ); + const is_file = fs.existsSync(file) && !fs.statSync(file).isDirectory(); + const allowed = + !vite_config.server.fs.strict || + vite_config.server.fs.allow.some((dir) => file.startsWith(dir)); + + if (is_file && allowed) { + req.url = original_url; + // @ts-expect-error + serve_static_middleware.handle(req, res); + return; + } - if (resolved) { - res.writeHead(200, { - 'content-type': 'application/javascript' - }); - res.end(`import '${svelte_config.kit.paths.base}${to_fs(resolved)}';`); - } else { - res.writeHead(404); - res.end('not found'); + if (!decoded.startsWith(svelte_config.kit.paths.base)) { + return not_found(req, res, svelte_config.kit.paths.base); } - return; + next(); + } catch (e) { + const error = coalesce_to_error(e); + res.statusCode = 500; + res.end(error); } + }); + } else { + vite.middlewares.use(async (req, res) => { + // Vite's base middleware strips out the base path. Restore it + const original_url = req.url; + req.url = req.originalUrl; + try { + const base = `${vite.config.server.https ? 'https' : 'http'}://${ + req.headers[':authority'] || req.headers.host + }`; + + const decoded = decodeURI(new URL(base + req.url).pathname); + const file = posixify( + path.resolve(root, decoded.slice(svelte_config.kit.paths.base.length + 1)) + ); + const is_file = fs.existsSync(file) && !fs.statSync(file).isDirectory(); + const allowed = + !vite_config.server.fs.strict || + vite_config.server.fs.allow.some((dir) => file.startsWith(dir)); + + if (is_file && allowed) { + req.url = original_url; + // @ts-expect-error + serve_static_middleware.handle(req, res); + return; + } - const resolved_instrumentation = resolve_entry( - path.join(svelte_config.kit.files.src, 'instrumentation.server') - ); - - if (resolved_instrumentation) { - await vite.ssrLoadModule(resolved_instrumentation); - } + if (!decoded.startsWith(svelte_config.kit.paths.base)) { + return not_found(req, res, svelte_config.kit.paths.base); + } - // 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 - }) - ); + if (decoded === svelte_config.kit.paths.base + '/service-worker.js') { + const resolved = resolve_entry(svelte_config.kit.files.serviceWorker); - const { set_fix_stack_trace } = await vite.ssrLoadModule( - `${get_runtime_base(root)}/shared-server.js` - ); - set_fix_stack_trace(fix_stack_trace); + if (resolved) { + res.writeHead(200, { + 'content-type': 'application/javascript' + }); + res.end(`import '${svelte_config.kit.paths.base}${to_fs(resolved)}';`); + } else { + res.writeHead(404); + res.end('not found'); + } - const { set_assets } = await vite.ssrLoadModule('$app/paths/internal/server'); - set_assets(assets); + return; + } - const server = new Server(manifest); + const resolved_instrumentation = resolve_entry( + path.join(svelte_config.kit.files.src, 'instrumentation.server') + ); - await server.init({ - env, - read: (file) => createReadableStream(from_fs(file)) - }); + if (resolved_instrumentation) { + await vite.ssrLoadModule(resolved_instrumentation); + } - const request = await getRequest({ - base, - request: req - }); + // 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 + }) + ); - if (manifest_error) { - console.error(styleText(['bold', 'red'], manifest_error.message)); + const { set_assets } = await vite.ssrLoadModule('$app/paths/internal/server'); + set_assets(assets); - const error_page = load_error_page(svelte_config); + const server = new Server(manifest); - /** @param {{ status: number; message: string }} opts */ - const error_template = ({ status, message }) => { - return error_page - .replace(/%sveltekit\.status%/g, String(status)) - .replace(/%sveltekit\.error\.message%/g, escape_html(message)); - }; + await server.init({ + env, + read: (file) => createReadableStream(from_fs(file)) + }); - res.writeHead(500, { - 'Content-Type': 'text/html; charset=utf-8' + const request = await getRequest({ + base, + request: req }); - res.end( - error_template({ status: 500, message: manifest_error.message ?? 'Invalid routes' }) - ); - return; - } + if (manifest_error) { + console.error(styleText(['bold', 'red'], manifest_error.message)); + + const error_page = load_error_page(svelte_config); + + /** @param {{ status: number; message: string }} opts */ + const error_template = ({ status, message }) => { + return error_page + .replace(/%sveltekit\.status%/g, String(status)) + .replace(/%sveltekit\.error\.message%/g, escape_html(message)); + }; + + res.writeHead(500, { + 'Content-Type': 'text/html; charset=utf-8' + }); + res.end( + error_template({ status: 500, message: manifest_error.message ?? 'Invalid routes' }) + ); + + 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)); + } - 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)); } + }); - return fs.readFileSync(path.join(svelte_config.kit.files.assets, file)); - }, - before_handle: (event, config, prerender) => { - async_local_storage.enterWith({ event, config, prerender }); - }, - emulator - }); - - if (rendered.status === 404) { - // @ts-expect-error - serve_static_middleware.handle(req, res, () => { + if (rendered.status === 404) { + // @ts-expect-error + serve_static_middleware.handle(req, res, () => { + void setResponse(res, rendered); + }); + } else { void setResponse(res, rendered); - }); - } else { - void setResponse(res, rendered); + } + } catch (e) { + const error = coalesce_to_error(e); + res.statusCode = 500; + res.end(error); } - } catch (e) { - const error = coalesce_to_error(e); - res.statusCode = 500; - res.end(fix_stack_trace(error)); - } - }); + }); + } }; } @@ -669,3 +681,16 @@ function has_correct_case(file, assets) { return false; } + +/** + * @param {import('vite').ViteDevServer} vite + * @param {string} id + */ +export function invalidate_module(vite, id) { + for (const environment in vite.environments) { + const module = vite.environments[environment].moduleGraph.getModuleById(id); + if (module) { + vite.environments[environment].moduleGraph.invalidateModule(module); + } + } +} diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index fba0feb60640..05112a9530bc 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -4,16 +4,17 @@ import process from 'node:process'; import { styleText } from 'node:util'; import { exactRegex, prefixRegex } from 'rolldown/filter'; +import * as devalue from 'devalue'; import { copy, mkdirp, posixify, read, resolve_entry, rimraf } from '../../utils/filesystem.js'; import { create_static_module, create_dynamic_module } from '../../core/env.js'; import * as sync from '../../core/sync/sync.js'; import { create_assets } from '../../core/sync/create_manifest_data/index.js'; -import { runtime_directory, logger } 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 { assets_base, 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, @@ -36,8 +37,9 @@ import { env_static_private, env_static_public, service_worker, - sveltekit_environment, - sveltekit_server + sveltekit_dev, + sveltekit_server_assets, + sveltekit_ssr_manifest } from './module_ids.js'; import { import_peer } from '../../utils/import.js'; import { compact } from '../../utils/array.js'; @@ -49,6 +51,8 @@ const cwd = process.cwd(); /** @type {string} */ let root; +const dev_environment = /** @type {import('types').DevEnvironment} */ ({}); + /** @type {import('./types.js').EnforcedConfig} */ const enforced_config = { appType: true, @@ -136,9 +140,12 @@ let vite_plugin_svelte; /** * Returns the SvelteKit Vite plugins. - * @returns {Promise} + * @param {{ + * adapter?: import('@sveltejs/kit').Adapter; + * }=} options + * @returns {Promise} */ -export async function sveltekit() { +export async function sveltekit(options = {}) { // the config options will be set only after the Vite `config` hook runs // because we need to find `svelte.config.js` relative to `vite.config.root` const svelte_config = /** @type {import('types').ValidatedConfig} */ ({}); @@ -154,8 +161,9 @@ export async function sveltekit() { return [ plugin_svelte_config({ vite_plugin_svelte_options, svelte_config }), - ...vite_plugin_svelte.svelte(vite_plugin_svelte_options), - ...kit({ svelte_config }) + vite_plugin_svelte.svelte(vite_plugin_svelte_options), + kit({ svelte_config, vite_adapter: options.adapter }), + options.adapter ]; } @@ -214,6 +222,16 @@ function plugin_svelte_config({ vite_plugin_svelte_options, svelte_config }) { }; } +/** + * @param {unknown} value + * @returns {string | undefined} + */ +function revive_functions(value) { + if (value instanceof Function) { + return value.toString(); + } +} + /** * Returns the SvelteKit Vite plugin. Vite executes Rolldown hooks as well as some of its own. * Background reading is available at: @@ -224,10 +242,13 @@ function plugin_svelte_config({ vite_plugin_svelte_options, svelte_config }) { * - https://rolldown.rs/apis/plugin-api#build-hooks * - https://rolldown.rs/apis/plugin-api#output-generation-hooks * - * @param {{ svelte_config: import('types').ValidatedConfig }} options - * @return {import('vite').Plugin[]} + * @param {{ + * svelte_config: import('types').ValidatedConfig; + * vite_adapter?: import('@sveltejs/kit').Adapter + * }} options + * @return {import('vite').PluginOption[]} */ -function kit({ svelte_config }) { +function kit({ svelte_config, vite_adapter }) { /** @type {typeof import('vite')} */ let vite; @@ -302,6 +323,13 @@ function kit({ svelte_config }) { ({ kit } = svelte_config); out = `${kit.outDir}/output`; + if (kit.adapter?.vitePlugins && !vite_adapter) { + // TODO: error if adapter vite plugins are not passed through the Vite config + throw new Error( + `${kit.adapter.name} requires \`adapter.vitePlugins\` to be passed into the \`sveltekit\` Vite plugin in vite.config.js. See ...` + ); + } + version_hash = hash(kit.version.name); env = get_env(kit.env, vite_config_env.mode); @@ -336,7 +364,10 @@ function kit({ svelte_config }) { const new_config = { resolve: { alias: [ - { find: '__SERVER__', replacement: `${generated}/server` }, + { + find: '@sveltejs/kit/src/runtime/server/generated.js', + replacement: `${generated}/server.js` + }, { find: '$app', replacement: `${runtime_directory}/app` }, ...get_config_aliases(kit, root) ] @@ -401,7 +432,7 @@ function kit({ svelte_config }) { // because they for example use rolldown.build with `platform: 'browser'` 'esm-env', // This forces `$app/*` modules to be bundled, since they depend on - // virtual modules like `__sveltekit/environment` (this isn't a valid bare + // virtual modules like `__sveltekit/remote` (this isn't a valid bare // import, but it works with vite-node's externalization logic, which // uses basic concatenation) '@sveltejs/kit/src/runtime' @@ -411,6 +442,7 @@ function kit({ svelte_config }) { const define = { __SVELTEKIT_APP_DIR__: s(kit.appDir), + __SVELTEKIT_APP_VERSION__: s(kit.version.name), __SVELTEKIT_EMBEDDED__: s(kit.embedded), __SVELTEKIT_EXPERIMENTAL__REMOTE_FUNCTIONS__: s(kit.experimental.remoteFunctions), __SVELTEKIT_FORK_PRELOADS__: s(kit.experimental.forkPreloads), @@ -446,11 +478,13 @@ function kit({ svelte_config }) { // @ts-ignore this prevents a reference error if `client.js` is imported on the server globalThis.__sveltekit_dev = {}; - // These Kit dependencies are packaged as CommonJS, which means they must always be externalized. - // Without this, the tests will still pass but `pnpm dev` will fail in projects that link `@sveltejs/kit`. - /** @type {NonNullable} */ (new_config.ssr).external = [ - 'cookie' - ]; + new_config.environments = { + ssr: { + dev: { + // TODO: + } + } + }; } warn_overridden_config(config, new_config); @@ -466,6 +500,9 @@ function kit({ svelte_config }) { } }; + /** @type {Record} */ + let server_assets = {}; + /** @type {import('vite').Plugin} */ const plugin_virtual_modules = { name: 'vite-plugin-sveltekit-virtual-modules', @@ -523,8 +560,9 @@ function kit({ svelte_config }) { exactRegex(env_dynamic_private), exactRegex(env_dynamic_public), exactRegex(service_worker), - exactRegex(sveltekit_environment), - exactRegex(sveltekit_server) + exactRegex(sveltekit_server_assets), + exactRegex(sveltekit_ssr_manifest), + exactRegex(sveltekit_dev) ] }, handler(id) { @@ -561,38 +599,191 @@ function kit({ svelte_config }) { case service_worker: return create_service_worker_module(svelte_config); - case sveltekit_environment: { - const { version } = svelte_config.kit; + case sveltekit_server_assets: { + return dedent` + export const server_assets = { + ${Object.entries(server_assets) + .map(([filepath, size]) => `${s(filepath)}: ${size}`) + .join(',\n')} + }; + + if (import.meta.hot) { + import.meta.hot.on('sveltekit:server-asset', (data) => { + Object.assign(server_assets, data); + }); + } + `; + } + + case sveltekit_ssr_manifest: { + const { manifest, manifest_data, env } = dev_environment; return dedent` - export const version = ${s(version.name)}; - export let building = false; - export let prerendering = false; + import { server_assets } from '__sveltekit/server-assets'; + import { resolve, loud_ssr_load_module } from '${get_runtime_base(root)}/../exports/vite/dev/fetchable.js'; + + export const base_path = ${s(kit.paths.base)}; + export const prerendered = new Set(); + export const env = ${s(env)}; + + export const manifest = { + appDir: ${s(manifest.appDir)}, + appPath: ${s(manifest.appPath)}, + assets: ${devalue.uneval(manifest.assets)}, + mimeTypes: ${s(manifest.mimeTypes)}, + _: { + client: ${devalue.uneval(manifest._.client, revive_functions)}, + server_assets, + nodes: [${manifest_data.nodes + .map((node, index) => { + return dedent` + async () => { + const node = ${devalue.uneval(node, revive_functions)}; + + 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 = []; + + const module_nodes = []; + + ${ + node.component + ? dedent` + result.component = async () => { + const { module_node, module } = await resolve(${s(path.resolve(root, node.component))}); + + module_nodes.push(module_node); + + return module.default; + } + ` + : '' + } - export function set_building() { - building = true; - } + ${ + node.universal + ? dedent` + 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(${s(path.resolve(root, node.universal))}); + module_nodes.push(module_node); + result.universal = module; + } + ` + : '' + } - export function set_prerendering() { - prerendering = true; - } - `; + ${ + node.server + ? dedent` + const { module } = await resolve(${s(path.resolve(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 + + // TODO: result.inline_styles + + return result; + } + `; + }) + .join(',\n')}], + prerendered_routes: new Set(), + get remotes() { + // TODO: fetchable remotes in dev + return {}; + }, + routes: [${compact( + manifest_data.routes.map((route) => { + if (!route.page && !route.endpoint) return null; + + const endpoint = route.endpoint; + + return dedent` + { + id: ${s(route.id)}, + pattern: ${devalue.uneval(route.pattern)}, + params: ${devalue.uneval(route.params)}, + page: ${devalue.uneval(route.page)}, + endpoint: ${ + endpoint + ? dedent` + async () => { + const url = ${s(path.resolve(root, endpoint.file))}; + return await loud_ssr_load_module(url); + } + ` + : null + }, + endpoint_id: ${s(endpoint?.file)} + } + `; + }) + ).join(',\n')}], + matchers: async () => { + // TODO: fetchable param matchers in dev + return {}; + } + } + }; + `; } - case sveltekit_server: { + case sveltekit_dev: { + const runtime_base = get_runtime_base(root); + const adapter = svelte_config.kit.adapter; + return dedent` - export let read_implementation = null; + import { AsyncLocalStorage } from 'node:async_hooks'; + import { check_feature } from '${runtime_base}/../utils/features.js'; + import { SCHEME } from '${runtime_base}/../utils/url.js'; + import { Server as InternalServer } from '${runtime_base}/server/index.js'; - export let manifest = null; + const async_local_storage = new AsyncLocalStorage(); - export function set_read_implementation(fn) { - read_implementation = fn; - } + const adapter = ${adapter ? devalue.uneval({ name: adapter.name, supports: adapter.supports }, revive_functions) : null}; - export function set_manifest(_) { - manifest = _; - } - `; + 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, 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 class Server extends InternalServer { + async respond(request, options) { + options.before_handle = async (event, config, prerender, handle) => { + // als.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); + }; + return super.respond(request, options); + } + } + `; } } } @@ -1274,8 +1465,9 @@ 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(vite) { - return await dev(vite, vite_config, svelte_config, () => remotes, root); + configureServer(vite) { + dev_environment.vite = vite; + return dev(vite, vite_config, svelte_config, () => remotes, root, dev_environment); }, /** @@ -1625,9 +1817,43 @@ function kit({ svelte_config }) { } }; + /** @type {import('vite').Plugin} */ + const plugin_server_filesystem = { + name: 'vite-plugin-sveltekit-server-filesystem', + applyToEnvironment(environment) { + return environment.name !== 'client' && environment.name !== 'serviceWorker'; + }, + configureServer() { + server_assets = {}; + }, + load: { + order: 'pre', + handler(id) { + const { pathname, searchParams } = new URL(id, 'file://'); + if ( + (searchParams.has('url') || vite_config.assetsInclude(pathname)) && + fs.existsSync(pathname) + ) { + const filepath = path.relative(root, pathname); + const size = fs.statSync(pathname).size; + + // update it immediately + dev_environment.vite.environments.ssr.hot.send('sveltekit:server-asset', { + [filepath]: size + }); + + // persist changes in case of server reload + server_assets[filepath] = size; + invalidate_module(dev_environment.vite, sveltekit_server_assets); + } + } + } + }; + return [ plugin_setup, plugin_remote, + plugin_server_filesystem, 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 69325f3edf41..86b64f659878 100644 --- a/packages/kit/src/exports/vite/module_ids.js +++ b/packages/kit/src/exports/vite/module_ids.js @@ -8,8 +8,11 @@ export const env_dynamic_public = '\0virtual:env/dynamic/public'; export const service_worker = '\0virtual:service-worker'; -export const sveltekit_environment = '\0virtual:__sveltekit/environment'; -export const sveltekit_server = '\0virtual:__sveltekit/server'; +export const sveltekit_dev = '\0virtual:__sveltekit/dev'; + +export const sveltekit_ssr_manifest = '\0virtual:__sveltekit/ssr-manifest'; + +export const sveltekit_server_assets = '\0virtual:__sveltekit/server-assets'; 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 54c856418d63..9cb6b9ca3e51 100644 --- a/packages/kit/src/exports/vite/preview/index.js +++ b/packages/kit/src/exports/vite/preview/index.js @@ -53,8 +53,6 @@ export async function preview(vite, vite_config, svelte_config) { read: (file) => createReadableStream(`${dir}/${file}`) }); - 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 @@ -210,8 +208,7 @@ export async function preview(vite, vite_config, svelte_config) { } return fs.readFileSync(join(svelte_config.kit.files.assets, file)); - }, - emulator + } }) ); }); diff --git a/packages/kit/src/runtime/app/environment/index.js b/packages/kit/src/runtime/app/environment/index.js index 10fa912a6c56..87a7b3521a10 100644 --- a/packages/kit/src/runtime/app/environment/index.js +++ b/packages/kit/src/runtime/app/environment/index.js @@ -1,3 +1,2 @@ export { BROWSER as browser, DEV as dev } from 'esm-env'; -// TODO: write these to disk -export { building, version } from '__sveltekit/environment'; +export { building, version } from './internal.js'; diff --git a/packages/kit/src/runtime/app/environment/internal.js b/packages/kit/src/runtime/app/environment/internal.js new file mode 100644 index 000000000000..ec9e7a780989 --- /dev/null +++ b/packages/kit/src/runtime/app/environment/internal.js @@ -0,0 +1,11 @@ +export const version = typeof __SVELTEKIT_APP_VERSION__ === 'string' ? __SVELTEKIT_APP_VERSION__ : 'unknown'; +export let building = false; +export let prerendering = false; + +export function set_building() { + building = true; +} + +export function set_prerendering() { + prerendering = true; +} diff --git a/packages/kit/src/runtime/app/paths/internal/server.js b/packages/kit/src/runtime/app/paths/internal/server.js index cd71e77db073..22077ab991c5 100644 --- a/packages/kit/src/runtime/app/paths/internal/server.js +++ b/packages/kit/src/runtime/app/paths/internal/server.js @@ -1,7 +1,7 @@ -export let base = __SVELTEKIT_PATHS_BASE__; -export let assets = __SVELTEKIT_PATHS_ASSETS__ || base; -export const app_dir = __SVELTEKIT_APP_DIR__; -export const relative = __SVELTEKIT_PATHS_RELATIVE__; +export let base = typeof __SVELTEKIT_PATHS_BASE__ !== 'undefined' ? __SVELTEKIT_PATHS_BASE__ : ''; +export let assets = typeof __SVELTEKIT_PATHS_ASSETS__ !== 'undefined' ? __SVELTEKIT_PATHS_ASSETS__ : base; +export const app_dir = typeof __SVELTEKIT_APP_DIR__ !== 'undefined' ? __SVELTEKIT_APP_DIR__ : '_app'; +export const relative = typeof __SVELTEKIT_PATHS_RELATIVE__ !== 'undefined' ? __SVELTEKIT_PATHS_RELATIVE__ : true; const initial = { base, assets }; diff --git a/packages/kit/src/runtime/app/paths/server.js b/packages/kit/src/runtime/app/paths/server.js index 2338f4b53f9d..c9301de0bee4 100644 --- a/packages/kit/src/runtime/app/paths/server.js +++ b/packages/kit/src/runtime/app/paths/server.js @@ -1,9 +1,9 @@ +import { try_get_request_store } from '@sveltejs/kit/internal/server'; import { base, assets, relative, initial_base } from './internal/server.js'; import { resolve_route, find_route } from '../../../utils/routing.js'; import { decode_pathname } from '../../../utils/url.js'; -import { try_get_request_store } from '@sveltejs/kit/internal/server'; -import { manifest } from '__sveltekit/server'; -import { get_hooks } from '__SERVER__/internal.js'; +import { manifest } from '../../server/external.js'; +import { get_hooks } from '../../server/generated.js'; /** @type {import('./client.js').asset} */ export function asset(file) { diff --git a/packages/kit/src/runtime/app/server/index.js b/packages/kit/src/runtime/app/server/index.js index efdc1bbf8307..2df6d1c02c9e 100644 --- a/packages/kit/src/runtime/app/server/index.js +++ b/packages/kit/src/runtime/app/server/index.js @@ -1,6 +1,6 @@ -import { read_implementation, manifest } from '__sveltekit/server'; -import { assets } from '$app/paths/internal/server'; import { DEV } from 'esm-env'; +import { assets } from '../paths/internal/server.js'; +import { read_implementation, manifest } from '../../server/external.js'; import { base64_decode } from '../../utils.js'; /** diff --git a/packages/kit/src/runtime/app/server/remote/prerender.js b/packages/kit/src/runtime/app/server/remote/prerender.js index f9729f7c7752..8585247d7d28 100644 --- a/packages/kit/src/runtime/app/server/remote/prerender.js +++ b/packages/kit/src/runtime/app/server/remote/prerender.js @@ -5,7 +5,7 @@ import { error, json } from '@sveltejs/kit'; import { DEV } from 'esm-env'; import { get_request_store } from '@sveltejs/kit/internal/server'; import { stringify, stringify_remote_arg } from '../../../shared.js'; -import { app_dir, base } from '$app/paths/internal/server'; +import { app_dir, base } from '../../paths/internal/server.js'; import { create_validator, get_cache, diff --git a/packages/kit/src/runtime/app/server/remote/query.js b/packages/kit/src/runtime/app/server/remote/query.js index 4b21603ae132..a2d42776e7ba 100644 --- a/packages/kit/src/runtime/app/server/remote/query.js +++ b/packages/kit/src/runtime/app/server/remote/query.js @@ -1,12 +1,12 @@ /** @import { RemoteQuery, RemoteQueryFunction } from '@sveltejs/kit' */ /** @import { RemoteInfo, MaybePromise } from 'types' */ /** @import { StandardSchemaV1 } from '@standard-schema/spec' */ +import { HttpError, SvelteKitError } from '@sveltejs/kit/internal'; import { get_request_store } from '@sveltejs/kit/internal/server'; -import { create_remote_key, stringify_remote_arg } from '../../../shared.js'; -import { prerendering } from '__sveltekit/environment'; import { create_validator, get_cache, get_response, run_remote_function } from './shared.js'; +import { prerendering } from '../../environment/internal.js'; +import { create_remote_key, stringify_remote_arg } from '../../../shared.js'; import { handle_error_and_jsonify } from '../../../server/utils.js'; -import { HttpError, SvelteKitError } from '@sveltejs/kit/internal'; /** * Creates a remote query. When called from the browser, the function will be invoked on the server via a `fetch` call. diff --git a/packages/kit/src/runtime/client/remote-functions/command.svelte.js b/packages/kit/src/runtime/client/remote-functions/command.svelte.js index 5838f406e88f..7932aa0ba904 100644 --- a/packages/kit/src/runtime/client/remote-functions/command.svelte.js +++ b/packages/kit/src/runtime/client/remote-functions/command.svelte.js @@ -1,12 +1,12 @@ /** @import { RemoteCommand, RemoteQueryOverride } from '@sveltejs/kit' */ /** @import { RemoteFunctionResponse } from 'types' */ /** @import { Query } from './query.svelte.js' */ -import { app_dir, base } from '$app/paths/internal/client'; import * as devalue from 'devalue'; import { HttpError } from '@sveltejs/kit/internal'; +import { get_remote_request_headers, refresh_queries, release_overrides } from './shared.svelte.js'; import { app } from '../client.js'; +import { app_dir, base } from '../../app/paths/internal/client.js'; import { stringify_remote_arg } from '../../shared.js'; -import { get_remote_request_headers, refresh_queries, release_overrides } from './shared.svelte.js'; /** * Client-version of the `command` function from `$app/server`. diff --git a/packages/kit/src/runtime/client/remote-functions/form.svelte.js b/packages/kit/src/runtime/client/remote-functions/form.svelte.js index 3ee951f42aad..6efd3ffc7982 100644 --- a/packages/kit/src/runtime/client/remote-functions/form.svelte.js +++ b/packages/kit/src/runtime/client/remote-functions/form.svelte.js @@ -2,14 +2,14 @@ /** @import { RemoteFormInput, RemoteForm, RemoteQueryOverride } from '@sveltejs/kit' */ /** @import { InternalRemoteFormIssue, RemoteFunctionResponse } from 'types' */ /** @import { Query } from './query.svelte.js' */ -import { app_dir, base } from '$app/paths/internal/client'; import * as devalue from 'devalue'; import { DEV } from 'esm-env'; -import { HttpError } from '@sveltejs/kit/internal'; -import { app, remote_responses, _goto, set_nearest_error_page, invalidateAll } from '../client.js'; import { tick } from 'svelte'; -import { refresh_queries, release_overrides } from './shared.svelte.js'; import { createAttachmentKey } from 'svelte/attachments'; +import { HttpError } from '@sveltejs/kit/internal'; +import { refresh_queries, release_overrides } from './shared.svelte.js'; +import { app, remote_responses, _goto, set_nearest_error_page, invalidateAll } from '../client.js'; +import { app_dir, base } from '../../app/paths/internal/client.js'; import { convert_formdata, flatten_issues, diff --git a/packages/kit/src/runtime/client/remote-functions/prerender.svelte.js b/packages/kit/src/runtime/client/remote-functions/prerender.svelte.js index 322ae0e11950..9b0c54a1aa04 100644 --- a/packages/kit/src/runtime/client/remote-functions/prerender.svelte.js +++ b/packages/kit/src/runtime/client/remote-functions/prerender.svelte.js @@ -1,13 +1,13 @@ -import { app_dir, base } from '$app/paths/internal/client'; -import { version } from '__sveltekit/environment'; import * as devalue from 'devalue'; import { DEV } from 'esm-env'; -import { app, remote_responses } from '../client.js'; import { create_remote_function, get_remote_request_headers, remote_request } from './shared.svelte.js'; +import { app, remote_responses } from '../client.js'; +import { version } from '../../app/environment/index.js'; +import { app_dir, base } from '../../app/paths/internal/client.js'; // Initialize Cache API for prerender functions const CACHE_NAME = DEV ? `sveltekit:${Date.now()}` : `sveltekit:${version}`; diff --git a/packages/kit/src/runtime/client/remote-functions/query.svelte.js b/packages/kit/src/runtime/client/remote-functions/query.svelte.js index 6ca5db50ed86..1bf85d94b740 100644 --- a/packages/kit/src/runtime/client/remote-functions/query.svelte.js +++ b/packages/kit/src/runtime/client/remote-functions/query.svelte.js @@ -1,16 +1,16 @@ /** @import { RemoteQueryFunction } from '@sveltejs/kit' */ /** @import { RemoteFunctionResponse } from 'types' */ -import { app_dir, base } from '$app/paths/internal/client'; -import { app, goto, query_map, remote_responses } from '../client.js'; +import * as devalue from 'devalue'; +import { DEV } from 'esm-env'; import { tick } from 'svelte'; +import { HttpError, Redirect } from '@sveltejs/kit/internal'; import { create_remote_function, get_remote_request_headers, remote_request } from './shared.svelte.js'; -import * as devalue from 'devalue'; -import { HttpError, Redirect } from '@sveltejs/kit/internal'; -import { DEV } from 'esm-env'; +import { app, goto, query_map, remote_responses } from '../client.js'; +import { app_dir, base } from '../../app/paths/internal/client.js'; /** * @param {string} id diff --git a/packages/kit/src/runtime/client/utils.js b/packages/kit/src/runtime/client/utils.js index 5bfe038747ce..a4e355fc4d30 100644 --- a/packages/kit/src/runtime/client/utils.js +++ b/packages/kit/src/runtime/client/utils.js @@ -1,11 +1,9 @@ import { BROWSER, DEV } from 'esm-env'; import { writable } from 'svelte/store'; import { assets } from '$app/paths'; -import { version } from '__sveltekit/environment'; +import { version } from '../app/environment/internal.js'; import { PRELOAD_PRIORITIES } from './constants.js'; -/* global __SVELTEKIT_APP_VERSION_FILE__, __SVELTEKIT_APP_VERSION_POLL_INTERVAL__ */ - export const origin = BROWSER ? location.origin : ''; /** @param {string | URL} url */ diff --git a/packages/kit/src/runtime/server/ambient.d.ts b/packages/kit/src/runtime/server/ambient.d.ts deleted file mode 100644 index 2f38261123dc..000000000000 --- a/packages/kit/src/runtime/server/ambient.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -declare module '__SERVER__/internal.js' { - export const options: import('types').SSROptions; - export const get_hooks: () => Promise>; -} diff --git a/packages/kit/src/runtime/server/external.js b/packages/kit/src/runtime/server/external.js new file mode 100644 index 000000000000..dba092dbce65 --- /dev/null +++ b/packages/kit/src/runtime/server/external.js @@ -0,0 +1,27 @@ +/** @import { SSRManifest } from '@sveltejs/kit' */ + +/** @typedef {(path: string) => ReadableStream} ReadImplementation */ + +/** + * @type {ReadImplementation | null} + */ +export let read_implementation = null; + +/** + * @type {SSRManifest | null} + */ +export let manifest = null; + +/** + * @param {ReadImplementation} fn + */ +export function set_read_implementation(fn) { + read_implementation = fn; +} + +/** + * @param {SSRManifest} _ + */ +export function set_manifest(_) { + manifest = _; +} diff --git a/packages/kit/src/runtime/server/fetch.js b/packages/kit/src/runtime/server/fetch.js index e05f9c1dfd69..9c74a0646c4d 100644 --- a/packages/kit/src/runtime/server/fetch.js +++ b/packages/kit/src/runtime/server/fetch.js @@ -1,8 +1,8 @@ import { parseSetCookie } from 'cookie'; import { respond } from './respond.js'; -import * as paths from '$app/paths/internal/server'; -import { read_implementation } from '__sveltekit/server'; +import { read_implementation } from './external.js'; import { has_prerendered_path } from './utils.js'; +import * as paths from '../app/paths/internal/server.js'; /** * @param {{ diff --git a/packages/kit/src/runtime/server/generated.js b/packages/kit/src/runtime/server/generated.js new file mode 100644 index 000000000000..8d07cff00c77 --- /dev/null +++ b/packages/kit/src/runtime/server/generated.js @@ -0,0 +1,128 @@ +// This module is a stub and will be overridden once Vite takes over module loading + +import { set_building, set_prerendering } from '../app/environment/internal.js'; +import { set_assets } from '../app/paths/internal/server.js'; +import { set_manifest, set_read_implementation } from './external.js'; +import { set_private_env, set_public_env } from '../shared-server.js'; + +/** @type {import('types').SSROptions} */ +export const options = { + app_template_contains_nonce: false, + async: false, + csp: { + mode: 'auto', + directives: { + 'child-src': [], + 'default-src': [], + 'frame-src': [], + 'worker-src': [], + 'connect-src': [], + 'font-src': [], + 'img-src': [], + 'manifest-src': [], + 'media-src': [], + 'object-src': [], + 'prefetch-src': [], + 'script-src': [], + 'script-src-elem': [], + 'script-src-attr': [], + 'style-src': [], + 'style-src-elem': [], + 'style-src-attr': [], + 'base-uri': [], + sandbox: [], + 'form-action': [], + 'frame-ancestors': [], + 'navigate-to': [], + 'report-uri': [], + 'report-to': [], + 'require-trusted-types-for': [], + 'trusted-types': [], + 'upgrade-insecure-requests': false, + 'require-sri-for': [], + 'block-all-mixed-content': false, + 'plugin-types': [], + referrer: [] + }, + reportOnly: { + 'child-src': [], + 'default-src': [], + 'frame-src': [], + 'worker-src': [], + 'connect-src': [], + 'font-src': [], + 'img-src': [], + 'manifest-src': [], + 'media-src': [], + 'object-src': [], + 'prefetch-src': [], + 'script-src': [], + 'script-src-elem': [], + 'script-src-attr': [], + 'style-src': [], + 'style-src-elem': [], + 'style-src-attr': [], + 'base-uri': [], + sandbox: [], + 'form-action': [], + 'frame-ancestors': [], + 'navigate-to': [], + 'report-uri': [], + 'report-to': [], + 'require-trusted-types-for': [], + 'trusted-types': [], + 'upgrade-insecure-requests': false, + 'require-sri-for': [], + 'block-all-mixed-content': false, + 'plugin-types': [], + referrer: [] + } + }, + csrf_check_origin: true, + csrf_trusted_origins: [], + embedded: false, + env_public_prefix: 'PUBLIC_', + env_private_prefix: '', + hash_routing: false, + // @ts-expect-error + hooks: null, // added lazily, via \`get_hooks\`, + root: { + render: () => { + return { css: { code: '', map: '' }, head: '', html: '', assets: '' }; + } + }, + service_worker: false, + service_worker_options: {}, + server_error_boundaries: false, + templates: { + app: () => '', + error: () => '' + }, + version_hash: '' +}; + +/** + * @returns {Promise>} + */ +// eslint-disable-next-line @typescript-eslint/require-await +export async function get_hooks() { + return { + handle: undefined, + handleFetch: undefined, + handleError: undefined, + handleValidationError: undefined, + init: undefined, + reroute: undefined, + transport: undefined + }; +} + +export { + set_assets, + set_building, + set_manifest, + set_prerendering, + set_private_env, + set_public_env, + set_read_implementation +}; diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js index 0dbeecb2d1d5..d516893dae33 100644 --- a/packages/kit/src/runtime/server/index.js +++ b/packages/kit/src/runtime/server/index.js @@ -1,12 +1,12 @@ +import { DEV } from 'esm-env'; +import { set_app } from './app.js'; import { IN_WEBCONTAINER } from './constants.js'; +import { set_read_implementation, set_manifest } from './external.js'; +import { options, get_hooks } from './generated.js'; import { respond } from './respond.js'; +import { format_server_error } from './utils.js'; import { set_private_env, set_public_env } from '../shared-server.js'; -import { options, get_hooks } from '__SERVER__/internal.js'; -import { DEV } from 'esm-env'; import { filter_env } from '../../utils/env.js'; -import { format_server_error } from './utils.js'; -import { set_read_implementation, set_manifest } from '__sveltekit/server'; -import { set_app } from './app.js'; /** @type {Promise} */ let init_promise; @@ -91,6 +91,7 @@ export class Server { controller.close(); } catch (error) { + // TODO: it only throws if the user tries to read the body. Otherwise, the response is a 200 controller.error(error); } } diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index 7da86fca713d..a057709276b8 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -2,7 +2,7 @@ import * as devalue from 'devalue'; import { readable, writable } from 'svelte/store'; import { DEV } from 'esm-env'; import { text } from '@sveltejs/kit'; -import * as paths from '$app/paths/internal/server'; +import * as paths from '../../app/paths/internal/server.js'; import { hash } from '../../../utils/hash.js'; import { serialize_data } from './serialize_data.js'; import { s } from '../../../utils/misc.js'; diff --git a/packages/kit/src/runtime/server/page/server_routing.js b/packages/kit/src/runtime/server/page/server_routing.js index c01d1ca808b4..d224e0d242a6 100644 --- a/packages/kit/src/runtime/server/page/server_routing.js +++ b/packages/kit/src/runtime/server/page/server_routing.js @@ -1,4 +1,4 @@ -import { base, assets, relative } from '$app/paths/internal/server'; +import { base, assets, relative } from '../../app/paths/internal/server.js'; import { text } from '@sveltejs/kit'; import { s } from '../../../utils/misc.js'; import { find_route } from '../../../utils/routing.js'; diff --git a/packages/kit/src/runtime/server/remote.js b/packages/kit/src/runtime/server/remote.js index 8c1db8109d0a..f6a242be65b8 100644 --- a/packages/kit/src/runtime/server/remote.js +++ b/packages/kit/src/runtime/server/remote.js @@ -4,7 +4,7 @@ import { json, error } from '@sveltejs/kit'; import { HttpError, Redirect, SvelteKitError } from '@sveltejs/kit/internal'; import { with_request_store, merge_tracing } from '@sveltejs/kit/internal/server'; -import { app_dir, base } from '$app/paths/internal/server'; +import { app_dir, base } from '../app/paths/internal/server.js'; import { is_form_content_type } from '../../utils/http.js'; import { parse_remote_arg, stringify } from '../shared.js'; import { handle_error_and_jsonify } from './utils.js'; diff --git a/packages/kit/src/runtime/server/respond.js b/packages/kit/src/runtime/server/respond.js index 23ed2a3d0f64..976145f9ed9b 100644 --- a/packages/kit/src/runtime/server/respond.js +++ b/packages/kit/src/runtime/server/respond.js @@ -3,7 +3,7 @@ import { DEV } from 'esm-env'; import { json, text } from '@sveltejs/kit'; import { Redirect, SvelteKitError } from '@sveltejs/kit/internal'; import { merge_tracing, with_request_store } from '@sveltejs/kit/internal/server'; -import { base, app_dir } from '$app/paths/internal/server'; +import { base, app_dir } from '../../runtime/app/paths/internal/server.js'; import { is_endpoint_request, render_endpoint } from './endpoint.js'; import { render_page } from './page/index.js'; import { render_response } from './page/render.js'; @@ -41,8 +41,6 @@ import { record_span } from '../telemetry/record_span.js'; import { otel } from '../telemetry/otel.js'; import { MUTATIVE_METHODS } from '../../constants.js'; -/* global __SVELTEKIT_ADAPTER_NAME__ */ - /** @type {import('types').RequiredResolveOptions['transformPageChunk']} */ const default_transform = ({ html }) => html; @@ -214,13 +212,6 @@ export async function internal_respond(request, options, manifest, state) { set_internal }); - if (state.emulator?.platform) { - event.platform = await state.emulator.platform({ - config: {}, - prerender: !!state.prerendering?.fallback - }); - } - let resolved_path = url.pathname; if (!remote_id) { @@ -371,7 +362,7 @@ export async function internal_respond(request, options, manifest, state) { } } - if (state.before_handle || state.emulator?.platform) { + if (state.before_handle) { let config = {}; /** @type {import('types').PrerenderOption} */ @@ -387,130 +378,136 @@ export async function internal_respond(request, options, manifest, state) { } if (state.before_handle) { - state.before_handle(event, config, prerender); - } - - if (state.emulator?.platform) { - event.platform = await state.emulator.platform({ config, prerender }); + return await state.before_handle(event, config, prerender, handle); } } } - set_trailing_slash(trailing_slash); - - if (state.prerendering && !state.prerendering.fallback && !state.prerendering.inside_reroute) { - disable_search(url); - } + async function handle() { + set_trailing_slash(trailing_slash); - const response = await record_span({ - name: 'sveltekit.handle.root', - attributes: { - 'http.route': event.route.id || 'unknown', - 'http.method': event.request.method, - 'http.url': event.url.href, - 'sveltekit.is_data_request': is_data_request, - 'sveltekit.is_sub_request': event.isSubRequest - }, - fn: async (root_span) => { - const traced_event = { - ...event, - tracing: { - enabled: __SVELTEKIT_SERVER_TRACING_ENABLED__, - root: root_span, - current: root_span - } - }; - event_state.allows_commands = MUTATIVE_METHODS.includes(request.method); - return await with_request_store({ event: traced_event, state: event_state }, () => - options.hooks.handle({ - event: traced_event, - resolve: (event, opts) => { - return record_span({ - name: 'sveltekit.resolve', - attributes: { - 'http.route': event.route.id || 'unknown' - }, - fn: (resolve_span) => { - // counter-intuitively, we need to clear the event, so that it's not - // e.g. accessible when loading modules needed to handle the request - return with_request_store(null, () => - resolve(merge_tracing(event, resolve_span), page_nodes, opts).then( - (response) => { - // add headers/cookies here, rather than inside `resolve`, so that we - // can do it once for all responses instead of once per `return` - for (const key in headers) { - const value = headers[key]; - response.headers.set(key, /** @type {string} */ (value)); - } - - add_cookies_to_headers(response.headers, new_cookies.values()); + if ( + state.prerendering && + !state.prerendering.fallback && + !state.prerendering.inside_reroute + ) { + disable_search(url); + } - if (state.prerendering && event.route.id !== null) { - response.headers.set('x-sveltekit-routeid', encodeURI(event.route.id)); + const response = await record_span({ + name: 'sveltekit.handle.root', + attributes: { + 'http.route': event.route.id || 'unknown', + 'http.method': event.request.method, + 'http.url': event.url.href, + 'sveltekit.is_data_request': is_data_request, + 'sveltekit.is_sub_request': event.isSubRequest + }, + fn: async (root_span) => { + const traced_event = { + ...event, + tracing: { + enabled: __SVELTEKIT_SERVER_TRACING_ENABLED__, + root: root_span, + current: root_span + } + }; + event_state.allows_commands = MUTATIVE_METHODS.includes(request.method); + return await with_request_store({ event: traced_event, state: event_state }, () => + options.hooks.handle({ + event: traced_event, + resolve: (event, opts) => { + return record_span({ + name: 'sveltekit.resolve', + attributes: { + 'http.route': event.route.id || 'unknown' + }, + fn: (resolve_span) => { + // counter-intuitively, we need to clear the event, so that it's not + // e.g. accessible when loading modules needed to handle the request + return with_request_store(null, () => + resolve(merge_tracing(event, resolve_span), page_nodes, opts).then( + (response) => { + // add headers/cookies here, rather than inside `resolve`, so that we + // can do it once for all responses instead of once per `return` + for (const key in headers) { + const value = headers[key]; + response.headers.set(key, /** @type {string} */ (value)); + } + + add_cookies_to_headers(response.headers, new_cookies.values()); + + if (state.prerendering && event.route.id !== null) { + response.headers.set('x-sveltekit-routeid', encodeURI(event.route.id)); + } + + resolve_span.setAttributes({ + 'http.response.status_code': response.status, + 'http.response.body.size': + response.headers.get('content-length') || 'unknown' + }); + + return response; } + ) + ); + } + }); + } + }) + ); + } + }); - resolve_span.setAttributes({ - 'http.response.status_code': response.status, - 'http.response.body.size': - response.headers.get('content-length') || 'unknown' - }); + // respond with 304 if etag matches + if (response.status === 200 && response.headers.has('etag')) { + let if_none_match_value = request.headers.get('if-none-match'); - return response; - } - ) - ); - } - }); - } - }) - ); - } - }); + // ignore W/ prefix https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match#directives + if (if_none_match_value?.startsWith('W/"')) { + if_none_match_value = if_none_match_value.substring(2); + } - // respond with 304 if etag matches - if (response.status === 200 && response.headers.has('etag')) { - let if_none_match_value = request.headers.get('if-none-match'); + const etag = /** @type {string} */ (response.headers.get('etag')); + + if (if_none_match_value === etag) { + const headers = new Headers({ etag }); + + // https://datatracker.ietf.org/doc/html/rfc7232#section-4.1 + set-cookie + for (const key of [ + 'cache-control', + 'content-location', + 'date', + 'expires', + 'vary', + 'set-cookie' + ]) { + const value = response.headers.get(key); + if (value) headers.set(key, value); + } - // ignore W/ prefix https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match#directives - if (if_none_match_value?.startsWith('W/"')) { - if_none_match_value = if_none_match_value.substring(2); + return new Response(undefined, { + status: 304, + headers + }); + } } - const etag = /** @type {string} */ (response.headers.get('etag')); - - if (if_none_match_value === etag) { - const headers = new Headers({ etag }); - - // https://datatracker.ietf.org/doc/html/rfc7232#section-4.1 + set-cookie - for (const key of [ - 'cache-control', - 'content-location', - 'date', - 'expires', - 'vary', - 'set-cookie' - ]) { - const value = response.headers.get(key); - if (value) headers.set(key, value); + // Edge case: If user does `return Response(30x)` in handle hook while processing a data request, + // we need to transform the redirect response to a corresponding JSON response. + if (is_data_request && response.status >= 300 && response.status <= 308) { + const location = response.headers.get('location'); + if (location) { + return redirect_json_response( + new Redirect(/** @type {any} */ (response.status), location) + ); } - - return new Response(undefined, { - status: 304, - headers - }); } - } - // Edge case: If user does `return Response(30x)` in handle hook while processing a data request, - // we need to transform the redirect response to a corresponding JSON response. - if (is_data_request && response.status >= 300 && response.status <= 308) { - const location = response.headers.get('location'); - if (location) { - return redirect_json_response(new Redirect(/** @type {any} */ (response.status), location)); - } + return response; } - return response; + return await handle(); } catch (e) { if (e instanceof Redirect) { const response = diff --git a/packages/kit/src/runtime/server/utils.js b/packages/kit/src/runtime/server/utils.js index 2257328df8d4..d9fd818268ff 100644 --- a/packages/kit/src/runtime/server/utils.js +++ b/packages/kit/src/runtime/server/utils.js @@ -4,7 +4,6 @@ import { HttpError } from '@sveltejs/kit/internal'; import { with_request_store } from '@sveltejs/kit/internal/server'; import { coalesce_to_error, get_message, get_status } from '../../utils/error.js'; import { negotiate } from '../../utils/http.js'; -import { fix_stack_trace } from '../shared-server.js'; import { ENDPOINT_METHODS } from '../../constants.js'; import { escape_html } from '../../utils/escape.js'; @@ -115,10 +114,6 @@ export async function handle_error_and_jsonify(event, state, options, error) { return { message: 'Unknown Error', ...error.body }; } - if (DEV && typeof error == 'object') { - fix_stack_trace(error); - } - const status = get_status(error); const message = get_message(error); @@ -220,15 +215,16 @@ export function format_server_error(status, error, event) { */ let relative = (file) => file; -if (DEV) { - try { - const path = await import('node:path'); +// if (DEV) { +// try { +// // TODO: node:path not available in workerd +// const path = await import('node:path'); - relative = (file) => path.relative('.', file); - } catch { - // do nothing - } -} +// relative = (file) => path.relative('.', file); +// } catch { +// // do nothing +// } +// } /** * Provides a refined stack trace by excluding lines following the last occurrence of a line containing +page. +layout. or +server. diff --git a/packages/kit/src/runtime/shared-server.js b/packages/kit/src/runtime/shared-server.js index 5a535448449f..4f89379a54f0 100644 --- a/packages/kit/src/runtime/shared-server.js +++ b/packages/kit/src/runtime/shared-server.js @@ -10,9 +10,6 @@ export let private_env = {}; */ export let public_env = {}; -/** @param {any} error */ -export let fix_stack_trace = (error) => error?.stack; - /** @type {(environment: Record) => void} */ export function set_private_env(environment) { private_env = environment; @@ -22,8 +19,3 @@ export function set_private_env(environment) { export function set_public_env(environment) { public_env = environment; } - -/** @param {(error: Error) => string} value */ -export function set_fix_stack_trace(value) { - fix_stack_trace = value; -} diff --git a/packages/kit/src/types/ambient-private.d.ts b/packages/kit/src/types/ambient-private.d.ts index c98af8cb0062..5d3b4d470854 100644 --- a/packages/kit/src/types/ambient-private.d.ts +++ b/packages/kit/src/types/ambient-private.d.ts @@ -1,12 +1,3 @@ -/** Internal version of $app/environment */ -declare module '__sveltekit/environment' { - export const building: boolean; - export const prerendering: boolean; - export const version: string; - export function set_building(): void; - export function set_prerendering(): void; -} - /** Internal version of $app/paths */ declare module '__sveltekit/paths' { export let base: '' | `/${string}`; @@ -17,13 +8,3 @@ declare module '__sveltekit/paths' { export function override(paths: { base: string; assets: string }): void; export function set_assets(path: string): void; } - -/** Internal version of $app/server */ -declare module '__sveltekit/server' { - import { SSRManifest } from '@sveltejs/kit'; - - export let manifest: SSRManifest; - export function read_implementation(path: string): ReadableStream; - export function set_manifest(manifest: SSRManifest): void; - export function set_read_implementation(fn: (path: string) => ReadableStream): void; -} diff --git a/packages/kit/src/types/global-private.d.ts b/packages/kit/src/types/global-private.d.ts index c08d43b3de64..08dc1e3660d4 100644 --- a/packages/kit/src/types/global-private.d.ts +++ b/packages/kit/src/types/global-private.d.ts @@ -1,6 +1,7 @@ declare global { const __SVELTEKIT_ADAPTER_NAME__: string; const __SVELTEKIT_APP_DIR__: string; + const __SVELTEKIT_APP_VERSION__: string; const __SVELTEKIT_APP_VERSION_FILE__: string; const __SVELTEKIT_APP_VERSION_POLL_INTERVAL__: number; const __SVELTEKIT_EMBEDDED__: boolean; diff --git a/packages/kit/src/types/internal.d.ts b/packages/kit/src/types/internal.d.ts index da3119626c68..85ba7d70e0fe 100644 --- a/packages/kit/src/types/internal.d.ts +++ b/packages/kit/src/types/internal.d.ts @@ -16,7 +16,6 @@ import { Reroute, RequestEvent, SSRManifest, - Emulator, Adapter, ServerInit, ClientInit, @@ -33,6 +32,7 @@ import { } from './private.js'; import { Span } from '@opentelemetry/api'; import type { PageOptions } from '../exports/vite/static_analysis/index.js'; +import type { ViteDevServer } from 'vite'; export interface ServerModule { Server: typeof InternalServer; @@ -47,7 +47,6 @@ export interface ServerInternalModule { set_public_env(environment: Record): void; set_read_implementation(implementation: (path: string) => ReadableStream): void; set_version(version: string): void; - set_fix_stack_trace(fix_stack_trace: (error: unknown) => string): void; get_hooks: () => Promise>; } @@ -180,11 +179,13 @@ export class InternalServer extends Server { read: (file: string) => NonSharedBuffer; /** A hook called before `handle` during dev, so that `AsyncLocalStorage` can be populated. */ before_handle?: (event: RequestEvent, config: any, prerender: PrerenderOption) => void; - emulator?: Emulator; } ): Promise; } +/** + * Used to construct the SSR manifest + */ export interface ManifestData { /** Static files from `kit.config.files.assets`. */ assets: Asset[]; @@ -539,8 +540,12 @@ export interface SSRState { * Used to set up `__SVELTEKIT_TRACK__` which checks if a used feature is supported. * E.g. if `read` from `$app/server` is used, it checks whether the route's config is compatible. */ - before_handle?: (event: RequestEvent, config: any, prerender: PrerenderOption) => void; - emulator?: Emulator; + before_handle?: ( + event: RequestEvent, + config: any, + prerender: PrerenderOption, + handle: () => Promise + ) => Promise; } export type StrictBody = string | ArrayBufferView; @@ -638,5 +643,12 @@ export interface RequestStore { state: RequestState; } +export interface DevEnvironment { + vite: ViteDevServer; + manifest_data: ManifestData; + manifest: SSRManifest; + env: Record; +} + export * from '../exports/index.js'; export * from './private.js'; diff --git a/packages/kit/src/types/virtual.d.ts b/packages/kit/src/types/virtual.d.ts new file mode 100644 index 000000000000..bc1744855c47 --- /dev/null +++ b/packages/kit/src/types/virtual.d.ts @@ -0,0 +1,14 @@ +declare module '__sveltekit/ssr-manifest' { + // eslint-disable-next-line no-duplicate-imports + import { SSRManifest } from '@sveltejs/kit'; + + export const manifest: SSRManifest; + export const env: Record; + export const remote_address: string | undefined; + export const base_path: string; + export const prerendered: Set; +} + +declare module '__sveltekit/dev' { + export { Server } from '@sveltejs/kit'; +} diff --git a/packages/kit/src/utils/features.js b/packages/kit/src/utils/features.js index 4a8530d22bbb..7ca2f2abe679 100644 --- a/packages/kit/src/utils/features.js +++ b/packages/kit/src/utils/features.js @@ -1,8 +1,10 @@ +/** @import { Adapter } from '@sveltejs/kit'; */ + /** * @param {string} route_id - * @param {any} config + * @param {unknown} config * @param {string} feature - * @param {import('@sveltejs/kit').Adapter | undefined} adapter + * @param {Pick | undefined} adapter */ export function check_feature(route_id, config, feature, adapter) { if (!adapter) return; diff --git a/packages/kit/test/apps/basics/svelte.config.js b/packages/kit/test/apps/basics/svelte.config.js index 61af6dff4fd5..5c573e4d3d58 100644 --- a/packages/kit/test/apps/basics/svelte.config.js +++ b/packages/kit/test/apps/basics/svelte.config.js @@ -14,13 +14,6 @@ const config = { } }); }, - emulate() { - return { - platform({ config, prerender }) { - return { config, prerender }; - } - }; - }, supports: { read: () => true, instrumentation: () => true diff --git a/packages/kit/test/mocks/path.js b/packages/kit/test/mocks/path.js deleted file mode 100644 index 5919601b2122..000000000000 --- a/packages/kit/test/mocks/path.js +++ /dev/null @@ -1,3 +0,0 @@ -export const base = ''; -export const assets = ''; -export const app_dir = '_app'; diff --git a/packages/kit/tsconfig.json b/packages/kit/tsconfig.json index 533431738447..6502aee43ad6 100644 --- a/packages/kit/tsconfig.json +++ b/packages/kit/tsconfig.json @@ -11,13 +11,11 @@ "paths": { "@sveltejs/kit": ["./src/exports/public.d.ts"], "@sveltejs/kit/node": ["./src/exports/node/index.js"], - "@sveltejs/kit/internal": ["./src/exports/internal/index.js"], - "@sveltejs/kit/internal/server": ["./src/exports/internal/server.js"], "$app/paths": ["./src/runtime/app/paths/public.d.ts"], - "$app/paths/internal/client": ["./src/runtime/app/paths/internal/client.js"], - "$app/paths/internal/server": ["./src/runtime/app/paths/internal/server.js"], "$app/server": ["./src/runtime/app/server/index.js"], // internal use only + "@sveltejs/kit/internal": ["./src/exports/internal/index.js"], + "@sveltejs/kit/internal/server": ["./src/exports/internal/server.js"], "types": ["./src/types/internal.d.ts"] }, "noUnusedLocals": true, diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 40bd03c1803f..4cf6eb538a6a 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -4,6 +4,7 @@ declare module '@sveltejs/kit' { import type { SvelteConfig } from '@sveltejs/vite-plugin-svelte'; import type { StandardSchemaV1 } from '@standard-schema/spec'; + import type { PluginOption } from 'vite'; import type { RouteId as AppRouteId, LayoutParams as AppLayoutParams, ResolvedPathname } from '$app/types'; // @ts-ignore this is an optional peer dependency so could be missing. Written like this so dts-buddy preserves the ts-ignore type Span = import('@opentelemetry/api').Span; @@ -40,8 +41,13 @@ declare module '@sveltejs/kit' { /** * Creates an `Emulator`, which allows the adapter to influence the environment * during dev, build and prerendering. + * @deprecated removed in 3.0.0 */ emulate?: () => MaybePromise; + /** + * @since 3.0.0 + */ + vitePlugins?: PluginOption; } export type LoadProperties | void> = input extends void @@ -1558,6 +1564,9 @@ declare module '@sveltejs/kit' { read?: (file: string) => MaybePromise; } + /** + * Powers the server + */ export interface SSRManifest { appDir: string; appPath: string; @@ -2416,6 +2425,9 @@ declare module '@sveltejs/kit' { server_manifest: import('vite').Manifest; } + /** + * Used to construct the SSR manifest + */ interface ManifestData { /** Static files from `kit.config.files.assets`. */ assets: Asset[]; @@ -2846,7 +2858,246 @@ declare module '@sveltejs/kit/vite' { /** * Returns the SvelteKit Vite plugins. * */ - export function sveltekit(): Promise; + export function sveltekit(options?: { + adapter?: import("@sveltejs/kit").Adapter; + } | undefined): Promise; + + export {}; +} + +declare module '@sveltejs/kit/internal' { + import type { StandardSchemaV1 } from '@standard-schema/spec'; + export class HttpError { + + constructor(status: number, body: { + message: string; + } extends App.Error ? (App.Error | string | undefined) : App.Error); + status: number; + body: App.Error; + toString(): string; + } + export class Redirect { + + constructor(status: 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308, location: string); + status: 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308; + location: string; + } + /** + * An error that was thrown from within the SvelteKit runtime that is not fatal and doesn't result in a 500, such as a 404. + * `SvelteKitError` goes through `handleError`. + * */ + export class SvelteKitError extends Error { + + constructor(status: number, text: string, message: string); + status: number; + text: string; + } + + export class ActionFailure { + + constructor(status: number, data: T); + status: number; + data: T; + } + /** + * Error thrown when form validation fails imperatively + */ + export class ValidationError extends Error { + + constructor(issues: StandardSchemaV1.Issue[]); + issues: StandardSchemaV1.Issue[]; + } + export function init_remote_functions(module: Record, file: string, hash: string): void; + + export {}; +} + +declare module '@sveltejs/kit/internal/server' { + import type { RequestEvent, Config, Handle, HandleServerError, KitConfig, HandleFetch, Reroute, Adapter, ServerInit, Transport, HandleValidationError } from '@sveltejs/kit'; + import type { Span } from '@opentelemetry/api'; + export function merge_tracing(event_like: T, current: import("@opentelemetry/api").Span): T; + /** + * Returns the current `RequestEvent`. Can be used inside server hooks, server `load` functions, actions, and endpoints (and functions called by them). + * + * In environments without [`AsyncLocalStorage`](https://nodejs.org/api/async_context.html#class-asynclocalstorage), this must be called synchronously (i.e. not after an `await`). + * @since 2.20.0 + * + * */ + export function getRequestEvent(): RequestEvent; + export function get_request_store(): RequestStore; + export function try_get_request_store(): RequestStore | null; + + export function with_request_store(store: RequestStore | null, fn: () => T): T; + interface ServerHooks { + handleFetch: HandleFetch; + handle: Handle; + handleError: HandleServerError; + handleValidationError: HandleValidationError; + reroute: Reroute; + transport: Transport; + init?: ServerInit; + } + + interface PrerenderDependency { + response: Response; + body: null | string | Uint8Array; + } + + interface PrerenderOptions { + cache?: string; // including this here is a bit of a hack, but it makes it easy to add + fallback?: boolean; + dependencies: Map; + /** + * For each key the (possibly still pending) result of a prerendered remote function. + * Used to deduplicate requests to the same remote function with the same arguments. + */ + remote_responses: Map>; + /** True for the duration of a call to the `reroute` hook */ + inside_reroute?: boolean; + } + + type RecursiveRequired = { + // Recursive implementation of TypeScript's Required utility type. + // Will recursively continue until it reaches a primitive or Function + // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type + [K in keyof T]-?: Extract extends never // If it does not have a Function type + ? RecursiveRequired // recursively continue through. + : T[K]; // Use the exact type for everything else + }; + + interface SSRComponent { + default: { + render( + props: Record, + opts: { context: Map; csp?: { nonce?: string; hash?: boolean } } + ): { + html: string; + head: string; + css: { + code: string; + map: any; // TODO + }; + /** Until we require all Svelte versions that support hashes, this might not be defined */ + hashes?: { + script: Array<`sha256-${string}`>; + }; + }; + }; + } + + interface SSROptions { + app_template_contains_nonce: boolean; + async: boolean; + csp: ValidatedConfig['kit']['csp']; + csrf_check_origin: boolean; + csrf_trusted_origins: string[]; + embedded: boolean; + env_public_prefix: string; + env_private_prefix: string; + hash_routing: boolean; + hooks: ServerHooks; + root: SSRComponent['default']; + service_worker: boolean; + service_worker_options: RegistrationOptions; + server_error_boundaries: boolean; + templates: { + app(values: { + head: string; + body: string; + assets: string; + nonce: string; + env: Record; + }): string; + error(values: { message: string; status: number }): string; + }; + version_hash: string; + } + type RemotePrerenderInputsGenerator = () => MaybePromise; + + type ValidatedConfig = Config & { + kit: ValidatedKitConfig; + extensions: string[]; + }; + + type ValidatedKitConfig = Omit, 'adapter'> & { + adapter?: Adapter; + }; + + type BinaryFormMeta = { + remote_refreshes?: string[]; + validate_only?: boolean; + }; + + type RemoteInfo = + | { + type: 'query' | 'command'; + id: string; + name: string; + } + | { + /** + * Corresponds to the name of the client-side exports (that's why we use underscores and not dots) + */ + type: 'query_batch'; + id: string; + name: string; + /** Direct access to the function, for remote functions called from the client */ + run: (args: any[], options: SSROptions) => Promise; + } + | { + type: 'form'; + id: string; + name: string; + fn: ( + body: Record, + meta: BinaryFormMeta, + form_data: FormData | null + ) => Promise; + } + | { + type: 'prerender'; + id: string; + name: string; + has_arg: boolean; + dynamic?: boolean; + inputs?: RemotePrerenderInputsGenerator; + }; + + type RecordSpan = (options: { + name: string; + attributes: Record; + fn: (current: Span) => Promise; + }) => Promise; + + /** + * Internal state associated with the current `RequestEvent`, + * used for tracking things like remote function calls + */ + interface RequestState { + prerendering: PrerenderOptions | undefined; + transport: ServerHooks['transport']; + handleValidationError: ServerHooks['handleValidationError']; + tracing: { + record_span: RecordSpan; + }; + is_in_remote_function: boolean; + form_instances?: Map; + remote_data?: Map>>; + refreshes?: Record>; + allows_commands?: boolean; + } + + interface RequestStore { + event: RequestEvent; + state: RequestState; + } + type MaybePromise = T | Promise; export {}; } @@ -3162,8 +3413,9 @@ declare module '$app/paths' { } declare module '$app/server' { - import type { RequestEvent, RemoteCommand, RemoteForm, RemoteFormInput, InvalidField, RemotePrerenderFunction, RemoteQueryFunction } from '@sveltejs/kit'; + import type { RemoteCommand, RemoteForm, RemoteFormInput, InvalidField, RemotePrerenderFunction, RemoteQueryFunction } from '@sveltejs/kit'; import type { StandardSchemaV1 } from '@standard-schema/spec'; + export { getRequestEvent } from '@sveltejs/kit/internal/server'; /** * Read the contents of an imported asset from the filesystem * @example @@ -3177,14 +3429,6 @@ declare module '$app/server' { * @since 2.4.0 */ export function read(asset: string): Response; - /** - * Returns the current `RequestEvent`. Can be used inside server hooks, server `load` functions, actions, and endpoints (and functions called by them). - * - * In environments without [`AsyncLocalStorage`](https://nodejs.org/api/async_context.html#class-asynclocalstorage), this must be called synchronously (i.e. not after an `await`). - * @since 2.20.0 - * - * */ - export function getRequestEvent(): RequestEvent; /** * Creates a remote command. When called from the browser, the function will be invoked on the server via a `fetch` call. * diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ebca078acd7b..a6c5953ecf93 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -80,7 +80,7 @@ catalogs: version: 0.3.0 rolldown: specifier: ^1.0.0-rc.6 - version: 1.0.0-rc.6 + version: 1.0.0-rc.9 sirv-cli: specifier: ^3.0.0 version: 3.0.0 @@ -106,8 +106,8 @@ catalogs: specifier: ^4.1.0 version: 4.1.0 wrangler: - specifier: ^4.67.0 - version: 4.67.0 + specifier: ^4.74.0 + version: 4.74.0 importers: @@ -161,15 +161,21 @@ importers: packages/adapter-cloudflare: dependencies: + '@cloudflare/vite-plugin': + specifier: ^1.29.0 + version: 1.29.0(vite@8.0.0(@types/node@24.10.13)(esbuild@0.27.3)(jiti@2.4.2)(yaml@2.8.0))(workerd@1.20260312.1)(wrangler@4.74.0(@cloudflare/workers-types@4.20260317.1)) '@cloudflare/workers-types': - specifier: ^4.20260219.0 - version: 4.20260227.0 + specifier: ^4.20260312.0 + version: 4.20260317.1 + esm-env: + specifier: ^1.2.2 + version: 1.2.2 worktop: specifier: 0.8.0-next.18 version: 0.8.0-next.18 wrangler: - specifier: ^4.67.0 - version: 4.67.0(@cloudflare/workers-types@4.20260227.0) + specifier: ^4.74.0 + version: 4.74.0(@cloudflare/workers-types@4.20260317.1) devDependencies: '@playwright/test': specifier: 'catalog:' @@ -182,7 +188,7 @@ importers: version: 24.10.13 rolldown: specifier: 'catalog:' - version: 1.0.0-rc.6 + version: 1.0.0-rc.9 typescript: specifier: 'catalog:' version: 5.9.3 @@ -212,7 +218,7 @@ importers: version: 8.0.0(@types/node@24.10.13)(esbuild@0.27.3)(jiti@2.4.2)(yaml@2.8.0) wrangler: specifier: 'catalog:' - version: 4.67.0(@cloudflare/workers-types@4.20260227.0) + version: 4.74.0(@cloudflare/workers-types@4.20260317.1) packages/adapter-cloudflare/test/apps/workers: devDependencies: @@ -231,9 +237,6 @@ importers: vite: specifier: 'catalog:' version: 8.0.0(@types/node@24.10.13)(esbuild@0.27.3)(jiti@2.4.2)(yaml@2.8.0) - wrangler: - specifier: 'catalog:' - version: 4.67.0(@cloudflare/workers-types@4.20260227.0) packages/adapter-netlify: dependencies: @@ -242,7 +245,7 @@ importers: version: 2.2.5 rolldown: specifier: ^1.0.0-rc.6 - version: 1.0.0-rc.6 + version: 1.0.0-rc.9 devDependencies: '@netlify/dev': specifier: 'catalog:' @@ -342,7 +345,7 @@ importers: dependencies: rolldown: specifier: ^1.0.0-rc.6 - version: 1.0.0-rc.6 + version: 1.0.0-rc.9 devDependencies: '@polka/url': specifier: 'catalog:' @@ -442,7 +445,7 @@ importers: version: 1.3.2(rollup@4.59.0) rolldown: specifier: ^1.0.0-rc.6 - version: 1.0.0-rc.6 + version: 1.0.0-rc.9 devDependencies: '@sveltejs/kit': specifier: workspace:^ @@ -519,7 +522,7 @@ importers: version: 24.10.13 rolldown: specifier: 'catalog:' - version: 1.0.0-rc.6 + version: 1.0.0-rc.9 svelte: specifier: 'catalog:' version: 5.53.5 @@ -601,7 +604,7 @@ importers: version: 0.7.0(typescript@5.9.3) rolldown: specifier: 'catalog:' - version: 1.0.0-rc.6 + version: 1.0.0-rc.9 svelte: specifier: 'catalog:' version: 5.53.5 @@ -1549,47 +1552,53 @@ packages: resolution: {integrity: sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ==} engines: {node: '>=18.0.0'} - '@cloudflare/unenv-preset@2.14.0': - resolution: {integrity: sha512-XKAkWhi1nBdNsSEoNG9nkcbyvfUrSjSf+VYVPfOto3gLTZVc3F4g6RASCMh6IixBKCG2yDgZKQIHGKtjcnLnKg==} + '@cloudflare/unenv-preset@2.15.0': + resolution: {integrity: sha512-EGYmJaGZKWl+X8tXxcnx4v2bOZSjQeNI5dWFeXivgX9+YCT69AkzHHwlNbVpqtEUTbew8eQurpyOpeN8fg00nw==} peerDependencies: unenv: 2.0.0-rc.24 - workerd: ^1.20260218.0 + workerd: 1.20260301.1 || ~1.20260302.1 || ~1.20260303.1 || ~1.20260304.1 || >1.20260305.0 <2.0.0-0 peerDependenciesMeta: workerd: optional: true - '@cloudflare/workerd-darwin-64@1.20260219.0': - resolution: {integrity: sha512-k+xM+swQBQnkrvwobjRPxyeYwjLSludJusR0PqeHe+h6X9QIRGgw3s1AO38lXQsqzMSgG5709oOXSF19NKVVaQ==} + '@cloudflare/vite-plugin@1.29.0': + resolution: {integrity: sha512-pWaL3vdZadOKTEB15g72YTczq1LT/Pccz3o4k9zGii7eG0yfnRfwVKSBZj7hEuouQz4Cj9JFZK01yydLu56T4A==} + peerDependencies: + vite: ^6.1.0 || ^7.0.0 || ^8.0.0 + wrangler: ^4.74.0 + + '@cloudflare/workerd-darwin-64@1.20260312.1': + resolution: {integrity: sha512-HUAtDWaqUduS6yasV6+NgsK7qBpP1qGU49ow/Wb117IHjYp+PZPUGReDYocpB4GOMRoQlvdd4L487iFxzdARpw==} engines: {node: '>=16'} cpu: [x64] os: [darwin] - '@cloudflare/workerd-darwin-arm64@1.20260219.0': - resolution: {integrity: sha512-EyfQdsG1KcIVAf4qndT00LZly7sLFm1VxMWHBvOFB/EVYF2sE5HZ0dPbe+yrax5p3eS0oLZthR8ynhz4UulMUQ==} + '@cloudflare/workerd-darwin-arm64@1.20260312.1': + resolution: {integrity: sha512-DOn7TPTHSxJYfi4m4NYga/j32wOTqvJf/pY4Txz5SDKWIZHSTXFyGz2K4B+thoPWLop/KZxGoyTv7db0mk/qyw==} engines: {node: '>=16'} cpu: [arm64] os: [darwin] - '@cloudflare/workerd-linux-64@1.20260219.0': - resolution: {integrity: sha512-N0UHXILYYa6htFO/uC92uAqusvynbSbOcHcrVXMKqP9Jy7eqXGMovyKIrNgzYnKIszNB+0lfUYdGI3Wci07LuA==} + '@cloudflare/workerd-linux-64@1.20260312.1': + resolution: {integrity: sha512-TdkIh3WzPXYHuvz7phAtFEEvAxvFd30tHrm4gsgpw0R0F5b8PtoM3hfL2uY7EcBBWVYUBtkY2ahDYFfufnXw/g==} engines: {node: '>=16'} cpu: [x64] os: [linux] - '@cloudflare/workerd-linux-arm64@1.20260219.0': - resolution: {integrity: sha512-835pjQ9uuAtwPBOAkPf+oxH3mNE5mqWuE3H7hJsul7WZsRD2FDcariyoT2AW6xyOePILrn4uMnmG1KGc9m/8Pg==} + '@cloudflare/workerd-linux-arm64@1.20260312.1': + resolution: {integrity: sha512-kNauZhL569Iy94t844OMwa1zP6zKFiL3xiJ4tGLS+TFTEfZ3pZsRH6lWWOtkXkjTyCmBEOog0HSEKjIV4oAffw==} engines: {node: '>=16'} cpu: [arm64] os: [linux] - '@cloudflare/workerd-windows-64@1.20260219.0': - resolution: {integrity: sha512-i7qcuOsuAxqqn1n5Ar3Rh1dHUL9vNmpF9FcdMTT84jIrdm5UNrPZz5grJthPmpB9LTcreT9iiP6qKbzGjnCwPA==} + '@cloudflare/workerd-windows-64@1.20260312.1': + resolution: {integrity: sha512-5dBrlSK+nMsZy5bYQpj8t9iiQNvCRlkm9GGvswJa9vVU/1BNO4BhJMlqOLWT24EmFyApZ+kaBiPJMV8847NDTg==} engines: {node: '>=16'} cpu: [x64] os: [win32] - '@cloudflare/workers-types@4.20260227.0': - resolution: {integrity: sha512-e/Lfx2LGmmTds9Soorj96ER+xzJZ/dfNcSd+odlRDv0HBYA4Ts7m01A1VwCPGvuy3/kQo7FYZEQdF6vnR0y73A==} + '@cloudflare/workers-types@4.20260317.1': + resolution: {integrity: sha512-+G4eVwyCpm8Au1ex8vQBCuA9wnwqetz4tPNRoB/53qvktERWBRMQnrtvC1k584yRE3emMThtuY0gWshvSJ++PQ==} '@colors/colors@1.6.0': resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} @@ -2741,73 +2750,36 @@ packages: resolution: {integrity: sha512-NvV5jPAQIMCoHvaJ0ZhfouBJ2woFYYf+o6B7dCHGh/tLKSPVoxhjffi35xPuMHgOv65aTOKUzML5XwQF9EkDAA==} engines: {node: '>=18'} - '@rolldown/binding-android-arm64@1.0.0-rc.6': - resolution: {integrity: sha512-kvjTSWGcrv+BaR2vge57rsKiYdVR8V8CoS0vgKrc570qRBfty4bT+1X0z3j2TaVV+kAYzA0PjeB9+mdZyqUZlg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [android] - '@rolldown/binding-android-arm64@1.0.0-rc.9': resolution: {integrity: sha512-lcJL0bN5hpgJfSIz/8PIf02irmyL43P+j1pTCfbD1DbLkmGRuFIA4DD3B3ZOvGqG0XiVvRznbKtN0COQVaKUTg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.0.0-rc.6': - resolution: {integrity: sha512-+tJhD21KvGNtUrpLXrZQlT+j5HZKiEwR2qtcZb3vNOUpvoT9QjEykr75ZW/Kr0W89gose/HVXU6351uVZD8Qvw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [darwin] - '@rolldown/binding-darwin-arm64@1.0.0-rc.9': resolution: {integrity: sha512-J7Zk3kLYFsLtuH6U+F4pS2sYVzac0qkjcO5QxHS7OS7yZu2LRs+IXo+uvJ/mvpyUljDJ3LROZPoQfgBIpCMhdQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.0.0-rc.6': - resolution: {integrity: sha512-DKNhjMk38FAWaHwUt1dFR3rA/qRAvn2NUvSG2UGvxvlMxSmN/qqww/j4ABAbXhNRXtGQNmrAINMXRuwHl16ZHg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [darwin] - '@rolldown/binding-darwin-x64@1.0.0-rc.9': resolution: {integrity: sha512-iwtmmghy8nhfRGeNAIltcNXzD0QMNaaA5U/NyZc1Ia4bxrzFByNMDoppoC+hl7cDiUq5/1CnFthpT9n+UtfFyg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.0.0-rc.6': - resolution: {integrity: sha512-8TThsRkCPAnfyMBShxrGdtoOE6h36QepqRQI97iFaQSCRbHFWHcDHppcojZnzXoruuhPnjMEygzaykvPVJsMRg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [freebsd] - '@rolldown/binding-freebsd-x64@1.0.0-rc.9': resolution: {integrity: sha512-DLFYI78SCiZr5VvdEplsVC2Vx53lnA4/Ga5C65iyldMVaErr86aiqCoNBLl92PXPfDtUYjUh+xFFor40ueNs4Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.6': - resolution: {integrity: sha512-ZfmFoOwPUZCWtGOVC9/qbQzfc0249FrRUOzV2XabSMUV60Crp211OWLQN1zmQAsRIVWRcEwhJ46Z1mXGo/L/nQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.9': resolution: {integrity: sha512-CsjTmTwd0Hri6iTw/DRMK7kOZ7FwAkrO4h8YWKoX/kcj833e4coqo2wzIFywtch/8Eb5enQ/lwLM7w6JX1W5RQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.6': - resolution: {integrity: sha512-ZsGzbNETxPodGlLTYHaCSGVhNN/rvkMDCJYHdT7PZr5jFJRmBfmDi2awhF64Dt2vxrJqY6VeeYSgOzEbHRsb7Q==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [glibc] - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.9': resolution: {integrity: sha512-2x9O2JbSPxpxMDhP9Z74mahAStibTlrBMW0520+epJH5sac7/LwZW5Bmg/E6CXuEF53JJFW509uP+lSedaUNxg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2815,13 +2787,6 @@ packages: os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.6': - resolution: {integrity: sha512-elPpdevtCdUOqziemR86C4CSCr/5sUxalzDrf/CJdMT+kZt2C556as++qHikNOz0vuFf52h+GJNXZM08eWgGPQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [musl] - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.9': resolution: {integrity: sha512-JA1QRW31ogheAIRhIg9tjMfsYbglXXYGNPLdPEYrwFxdbkQCAzvpSCSHCDWNl4hTtrol8WeboCSEpjdZK8qrCg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2843,13 +2808,6 @@ packages: os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.6': - resolution: {integrity: sha512-IBwXsf56o3xhzAyaZxdM1CX8UFiBEUFCjiVUgny67Q8vPIqkjzJj0YKhd3TbBHanuxThgBa59f6Pgutg2OGk5A==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.9': resolution: {integrity: sha512-cVEl1vZtBsBZna3YMjGXNvnYYrOJ7RzuWvZU0ffvJUexWkukMaDuGhUXn0rjnV0ptzGVkvc+vW9Yqy6h8YX4pg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2857,13 +2815,6 @@ packages: os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.0.0-rc.6': - resolution: {integrity: sha512-vOk7G8V9Zm+8a6PL6JTpCea61q491oYlGtO6CvnsbhNLlKdf0bbCPytFzGQhYmCKZDKkEbmnkcIprTEGCURnwg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [musl] - '@rolldown/binding-linux-x64-musl@1.0.0-rc.9': resolution: {integrity: sha512-UzYnKCIIc4heAKgI4PZ3dfBGUZefGCJ1TPDuLHoCzgrMYPb5Rv6TLFuYtyM4rWyHM7hymNdsg5ik2C+UD9VDbA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2871,55 +2822,29 @@ packages: os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.0.0-rc.6': - resolution: {integrity: sha512-ASjEDI4MRv7XCQb2JVaBzfEYO98JKCGrAgoW6M03fJzH/ilCnC43Mb3ptB9q/lzsaahoJyIBoAGKAYEjUvpyvQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [openharmony] - '@rolldown/binding-openharmony-arm64@1.0.0-rc.9': resolution: {integrity: sha512-+6zoiF+RRyf5cdlFQP7nm58mq7+/2PFaY2DNQeD4B87N36JzfF/l9mdBkkmTvSYcYPE8tMh/o3cRlsx1ldLfog==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-wasm32-wasi@1.0.0-rc.6': - resolution: {integrity: sha512-mYa1+h2l6Zc0LvmwUh0oXKKYihnw/1WC73vTqw+IgtfEtv47A+rWzzcWwVDkW73+UDr0d/Ie/HRXoaOY22pQDw==} - engines: {node: '>=14.0.0'} - cpu: [wasm32] - '@rolldown/binding-wasm32-wasi@1.0.0-rc.9': resolution: {integrity: sha512-rgFN6sA/dyebil3YTlL2evvi/M+ivhfnyxec7AccTpRPccno/rPoNlqybEZQBkcbZu8Hy+eqNJCqfBR8P7Pg8g==} engines: {node: '>=14.0.0'} cpu: [wasm32] - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.6': - resolution: {integrity: sha512-e2ABskbNH3MRUBMjgxaMjYIw11DSwjLJxBII3UgpF6WClGLIh8A20kamc+FKH5vIaFVnYQInmcLYSUVpqMPLow==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [win32] - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.9': resolution: {integrity: sha512-lHVNUG/8nlF1IQk1C0Ci574qKYyty2goMiPlRqkC5R+3LkXDkL5Dhx8ytbxq35m+pkHVIvIxviD+TWLdfeuadA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.6': - resolution: {integrity: sha512-dJVc3ifhaRXxIEh1xowLohzFrlQXkJ66LepHm+CmSprTWgVrPa8Fx3OL57xwIqDEH9hufcKkDX2v65rS3NZyRA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [win32] - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.9': resolution: {integrity: sha512-G0oA4+w1iY5AGi5HcDTxWsoxF509hrFIPB2rduV5aDqS9FtDg1CAfa7V34qImbjfhIcA8C+RekocJZA96EarwQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@rolldown/pluginutils@1.0.0-rc.6': - resolution: {integrity: sha512-Y0+JT8Mi1mmW08K6HieG315XNRu4L0rkfCpA364HtytjgiqYnMYRdFPcxRl+BQQqNXzecL2S9nii+RUpO93XIA==} - '@rolldown/pluginutils@1.0.0-rc.9': resolution: {integrity: sha512-w6oiRWgEBl04QkFZgmW+jnU1EC9b57Oihi2ot3HNWIQRqgHp5PnYDia5iZ5FF7rpa4EQdiqMDXjlqKGXBhsoXw==} @@ -4689,8 +4614,8 @@ packages: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} - miniflare@4.20260219.0: - resolution: {integrity: sha512-EIb5wXbWUnnC60XU2aiFOPNd4fgTXzECkwRSOXZ1vdcY9WZaEE9rVf+h+Apw+WkOHRkp3Dr9/ZhQ5y1R+9iZ4Q==} + miniflare@4.20260312.1: + resolution: {integrity: sha512-YSWxec9ssisqkQgaCgcIQxZlB41E9hMiq1nxUgxXHRrE9NsfyC6ptSt8yfgBobsKIseAVKLTB/iEDpMumBv8oA==} engines: {node: '>=18.0.0'} hasBin: true @@ -5047,10 +4972,6 @@ packages: peerDependencies: postcss: ^8.2.9 - postcss@8.5.6: - resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} - engines: {node: ^10 || ^12 || >=14} - postcss@8.5.8: resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} engines: {node: ^10 || ^12 || >=14} @@ -5198,11 +5119,6 @@ packages: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rolldown@1.0.0-rc.6: - resolution: {integrity: sha512-B8vFPV1ADyegoYfhg+E7RAucYKv0xdVlwYYsIJgfPNeiSxZGWNxts9RqhyGzC11ULK/VaeXyKezGCwpMiH8Ktw==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - rolldown@1.0.0-rc.9: resolution: {integrity: sha512-9EbgWge7ZH+yqb4d2EnELAntgPTWbfL8ajiTW+SyhJEC4qhBbkCKbqFV4Ge4zmu5ziQuVbWxb/XwLZ+RIO7E8Q==} engines: {node: ^20.19.0 || >=22.12.0} @@ -5839,8 +5755,8 @@ packages: resolution: {integrity: sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==} engines: {node: '>= 12.0.0'} - workerd@1.20260219.0: - resolution: {integrity: sha512-l4U4iT5H8jNV6+EK23ExnUV2z6JvqQtQPrT8XCm4G8RpwC9EPpYTOO9s/ImMPJKe1WSbQUQoJ4k8Nd83fz8skQ==} + workerd@1.20260312.1: + resolution: {integrity: sha512-nNpPkw9jaqo79B+iBCOiksx+N62xC+ETIfyzofUEdY3cSOHJg6oNnVSHm7vHevzVblfV76c8Gr0cXHEapYMBEg==} engines: {node: '>=16'} hasBin: true @@ -5848,12 +5764,12 @@ packages: resolution: {integrity: sha512-+TvsA6VAVoMC3XDKR5MoC/qlLqDixEfOBysDEKnPIPou/NvoPWCAuXHXMsswwlvmEuvX56lQjvELLyLuzTKvRw==} engines: {node: '>=12'} - wrangler@4.67.0: - resolution: {integrity: sha512-58OoVth7bqm0nqsRgcI67gHbpp0IfR1JIBqDY0XR1FzRu9Qkjn6v2iJAdFf82QcVBFhaMBYQi88WqYGswq5wlQ==} + wrangler@4.74.0: + resolution: {integrity: sha512-3qprbhgdUyqYGHZ+Y1k0gsyHLMOlLrKL/HU0LDqLlCkbsKPprUA0/ThE4IZsxD84xAAXY6pv5JUuxS2+OnMa3A==} engines: {node: '>=20.0.0'} hasBin: true peerDependencies: - '@cloudflare/workers-types': ^4.20260219.0 + '@cloudflare/workers-types': ^4.20260312.1 peerDependenciesMeta: '@cloudflare/workers-types': optional: true @@ -6141,28 +6057,41 @@ snapshots: '@cloudflare/kv-asset-handler@0.4.2': {} - '@cloudflare/unenv-preset@2.14.0(unenv@2.0.0-rc.24)(workerd@1.20260219.0)': + '@cloudflare/unenv-preset@2.15.0(unenv@2.0.0-rc.24)(workerd@1.20260312.1)': dependencies: unenv: 2.0.0-rc.24 optionalDependencies: - workerd: 1.20260219.0 + workerd: 1.20260312.1 - '@cloudflare/workerd-darwin-64@1.20260219.0': + '@cloudflare/vite-plugin@1.29.0(vite@8.0.0(@types/node@24.10.13)(esbuild@0.27.3)(jiti@2.4.2)(yaml@2.8.0))(workerd@1.20260312.1)(wrangler@4.74.0(@cloudflare/workers-types@4.20260317.1))': + dependencies: + '@cloudflare/unenv-preset': 2.15.0(unenv@2.0.0-rc.24)(workerd@1.20260312.1) + miniflare: 4.20260312.1 + unenv: 2.0.0-rc.24 + vite: 8.0.0(@types/node@24.10.13)(esbuild@0.27.3)(jiti@2.4.2)(yaml@2.8.0) + wrangler: 4.74.0(@cloudflare/workers-types@4.20260317.1) + ws: 8.18.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + - workerd + + '@cloudflare/workerd-darwin-64@1.20260312.1': optional: true - '@cloudflare/workerd-darwin-arm64@1.20260219.0': + '@cloudflare/workerd-darwin-arm64@1.20260312.1': optional: true - '@cloudflare/workerd-linux-64@1.20260219.0': + '@cloudflare/workerd-linux-64@1.20260312.1': optional: true - '@cloudflare/workerd-linux-arm64@1.20260219.0': + '@cloudflare/workerd-linux-arm64@1.20260312.1': optional: true - '@cloudflare/workerd-windows-64@1.20260219.0': + '@cloudflare/workerd-windows-64@1.20260312.1': optional: true - '@cloudflare/workers-types@4.20260227.0': {} + '@cloudflare/workers-types@4.20260317.1': {} '@colors/colors@1.6.0': {} @@ -7331,45 +7260,24 @@ snapshots: '@publint/pack@0.1.0': {} - '@rolldown/binding-android-arm64@1.0.0-rc.6': - optional: true - '@rolldown/binding-android-arm64@1.0.0-rc.9': optional: true - '@rolldown/binding-darwin-arm64@1.0.0-rc.6': - optional: true - '@rolldown/binding-darwin-arm64@1.0.0-rc.9': optional: true - '@rolldown/binding-darwin-x64@1.0.0-rc.6': - optional: true - '@rolldown/binding-darwin-x64@1.0.0-rc.9': optional: true - '@rolldown/binding-freebsd-x64@1.0.0-rc.6': - optional: true - '@rolldown/binding-freebsd-x64@1.0.0-rc.9': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.6': - optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.9': optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.6': - optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.9': optional: true - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.6': - optional: true - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.9': optional: true @@ -7379,48 +7287,26 @@ snapshots: '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.9': optional: true - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.6': - optional: true - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.9': optional: true - '@rolldown/binding-linux-x64-musl@1.0.0-rc.6': - optional: true - '@rolldown/binding-linux-x64-musl@1.0.0-rc.9': optional: true - '@rolldown/binding-openharmony-arm64@1.0.0-rc.6': - optional: true - '@rolldown/binding-openharmony-arm64@1.0.0-rc.9': optional: true - '@rolldown/binding-wasm32-wasi@1.0.0-rc.6': - dependencies: - '@napi-rs/wasm-runtime': 1.1.1 - optional: true - '@rolldown/binding-wasm32-wasi@1.0.0-rc.9': dependencies: '@napi-rs/wasm-runtime': 1.1.1 optional: true - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.6': - optional: true - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.9': optional: true - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.6': - optional: true - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.9': optional: true - '@rolldown/pluginutils@1.0.0-rc.6': {} - '@rolldown/pluginutils@1.0.0-rc.9': {} '@rollup/pluginutils@5.1.3(rollup@4.59.0)': @@ -7852,7 +7738,7 @@ snapshots: '@vue/shared': 3.5.16 estree-walker: 2.0.2 magic-string: 0.30.21 - postcss: 8.5.6 + postcss: 8.5.8 source-map-js: 1.2.1 '@vue/compiler-ssr@3.5.16': @@ -8253,11 +8139,11 @@ snapshots: dependencies: node-source-walk: 7.0.1 - detective-postcss@7.0.1(postcss@8.5.6): + detective-postcss@7.0.1(postcss@8.5.8): dependencies: is-url: 1.2.4 - postcss: 8.5.6 - postcss-values-parser: 6.0.2(postcss@8.5.6) + postcss: 8.5.8 + postcss-values-parser: 6.0.2(postcss@8.5.8) detective-sass@6.0.1: dependencies: @@ -8493,9 +8379,9 @@ snapshots: esutils: 2.0.3 globals: 16.5.0 known-css-properties: 0.37.0 - postcss: 8.5.6 - postcss-load-config: 3.1.4(postcss@8.5.6)(ts-node@10.9.2(@types/node@24.10.13)(typescript@5.9.3)) - postcss-safe-parser: 7.0.1(postcss@8.5.6) + postcss: 8.5.8 + postcss-load-config: 3.1.4(postcss@8.5.8)(ts-node@10.9.2(@types/node@24.10.13)(typescript@5.9.3)) + postcss-safe-parser: 7.0.1(postcss@8.5.8) semver: 7.7.4 svelte-eslint-parser: 1.5.1(svelte@5.53.5) optionalDependencies: @@ -9240,12 +9126,12 @@ snapshots: min-indent@1.0.1: {} - miniflare@4.20260219.0: + miniflare@4.20260312.1: dependencies: '@cspotcode/source-map-support': 0.8.1 sharp: 0.34.5 undici: 7.18.2 - workerd: 1.20260219.0 + workerd: 1.20260312.1 ws: 8.18.0 youch: 4.1.0-beta.10 transitivePeerDependencies: @@ -9527,14 +9413,6 @@ snapshots: '@polka/url': 1.0.0-next.28 trouter: 4.0.0 - postcss-load-config@3.1.4(postcss@8.5.6)(ts-node@10.9.2(@types/node@24.10.13)(typescript@5.9.3)): - dependencies: - lilconfig: 2.1.0 - yaml: 1.10.2 - optionalDependencies: - postcss: 8.5.6 - ts-node: 10.9.2(@types/node@24.10.13)(typescript@5.9.3) - postcss-load-config@3.1.4(postcss@8.5.8)(ts-node@10.9.2(@types/node@24.10.13)(typescript@5.9.3)): dependencies: lilconfig: 2.1.0 @@ -9542,34 +9420,27 @@ snapshots: optionalDependencies: postcss: 8.5.8 ts-node: 10.9.2(@types/node@24.10.13)(typescript@5.9.3) - optional: true - postcss-safe-parser@7.0.1(postcss@8.5.6): + postcss-safe-parser@7.0.1(postcss@8.5.8): dependencies: - postcss: 8.5.6 + postcss: 8.5.8 - postcss-scss@4.0.9(postcss@8.5.6): + postcss-scss@4.0.9(postcss@8.5.8): dependencies: - postcss: 8.5.6 + postcss: 8.5.8 postcss-selector-parser@7.1.1: dependencies: cssesc: 3.0.0 util-deprecate: 1.0.2 - postcss-values-parser@6.0.2(postcss@8.5.6): + postcss-values-parser@6.0.2(postcss@8.5.8): dependencies: color-name: 1.1.4 is-url-superb: 4.0.0 - postcss: 8.5.6 + postcss: 8.5.8 quote-unquote: 1.0.0 - postcss@8.5.6: - dependencies: - nanoid: 3.3.11 - picocolors: 1.1.1 - source-map-js: 1.2.1 - postcss@8.5.8: dependencies: nanoid: 3.3.11 @@ -9583,7 +9454,7 @@ snapshots: detective-amd: 6.0.1 detective-cjs: 6.0.1 detective-es6: 5.0.1 - detective-postcss: 7.0.1(postcss@8.5.6) + detective-postcss: 7.0.1(postcss@8.5.8) detective-sass: 6.0.1 detective-scss: 5.0.1 detective-stylus: 5.0.1 @@ -9591,7 +9462,7 @@ snapshots: detective-vue2: 2.2.0(typescript@5.9.3) module-definition: 6.0.1 node-source-walk: 7.0.1 - postcss: 8.5.6 + postcss: 8.5.8 typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -9746,25 +9617,6 @@ snapshots: reusify@1.0.4: {} - rolldown@1.0.0-rc.6: - dependencies: - '@oxc-project/types': 0.115.0 - '@rolldown/pluginutils': 1.0.0-rc.6 - optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.0-rc.6 - '@rolldown/binding-darwin-arm64': 1.0.0-rc.6 - '@rolldown/binding-darwin-x64': 1.0.0-rc.6 - '@rolldown/binding-freebsd-x64': 1.0.0-rc.6 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.6 - '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.6 - '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.6 - '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.6 - '@rolldown/binding-linux-x64-musl': 1.0.0-rc.6 - '@rolldown/binding-openharmony-arm64': 1.0.0-rc.6 - '@rolldown/binding-wasm32-wasi': 1.0.0-rc.6 - '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.6 - '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.6 - rolldown@1.0.0-rc.9: dependencies: '@oxc-project/types': 0.115.0 @@ -10016,8 +9868,8 @@ snapshots: eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 espree: 10.4.0 - postcss: 8.5.6 - postcss-scss: 4.0.9(postcss@8.5.6) + postcss: 8.5.8 + postcss-scss: 4.0.9(postcss@8.5.8) postcss-selector-parser: 7.1.1 semver: 7.7.4 optionalDependencies: @@ -10355,31 +10207,31 @@ snapshots: triple-beam: 1.4.1 winston-transport: 4.9.0 - workerd@1.20260219.0: + workerd@1.20260312.1: optionalDependencies: - '@cloudflare/workerd-darwin-64': 1.20260219.0 - '@cloudflare/workerd-darwin-arm64': 1.20260219.0 - '@cloudflare/workerd-linux-64': 1.20260219.0 - '@cloudflare/workerd-linux-arm64': 1.20260219.0 - '@cloudflare/workerd-windows-64': 1.20260219.0 + '@cloudflare/workerd-darwin-64': 1.20260312.1 + '@cloudflare/workerd-darwin-arm64': 1.20260312.1 + '@cloudflare/workerd-linux-64': 1.20260312.1 + '@cloudflare/workerd-linux-arm64': 1.20260312.1 + '@cloudflare/workerd-windows-64': 1.20260312.1 worktop@0.8.0-next.18: dependencies: mrmime: 2.0.0 regexparam: 3.0.0 - wrangler@4.67.0(@cloudflare/workers-types@4.20260227.0): + wrangler@4.74.0(@cloudflare/workers-types@4.20260317.1): dependencies: '@cloudflare/kv-asset-handler': 0.4.2 - '@cloudflare/unenv-preset': 2.14.0(unenv@2.0.0-rc.24)(workerd@1.20260219.0) + '@cloudflare/unenv-preset': 2.15.0(unenv@2.0.0-rc.24)(workerd@1.20260312.1) blake3-wasm: 2.1.5 esbuild: 0.27.3 - miniflare: 4.20260219.0 + miniflare: 4.20260312.1 path-to-regexp: 6.3.0 unenv: 2.0.0-rc.24 - workerd: 1.20260219.0 + workerd: 1.20260312.1 optionalDependencies: - '@cloudflare/workers-types': 4.20260227.0 + '@cloudflare/workers-types': 4.20260317.1 fsevents: 2.3.3 transitivePeerDependencies: - bufferutil diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 131b23c04541..53ac8c06ccb9 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -49,7 +49,7 @@ catalog: valibot: ^1.1.0 vite: ^8.0.0 vitest: ^4.1.0 - wrangler: ^4.67.0 + wrangler: ^4.74.0 catalogs: # these show up in the ci.yml like `vite: 'beta'`, etc. vite-baseline: From 5346ed5ea921a6fc43973dc6bf2a73aecf52ed4e Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Fri, 20 Mar 2026 18:47:09 +0800 Subject: [PATCH 002/117] update before_handle type --- packages/kit/src/types/internal.d.ts | 7 +- packages/kit/types/index.d.ts | 104 ++++++++++++++++----------- 2 files changed, 67 insertions(+), 44 deletions(-) diff --git a/packages/kit/src/types/internal.d.ts b/packages/kit/src/types/internal.d.ts index 3a2237e653f6..7f83cf7c17f4 100644 --- a/packages/kit/src/types/internal.d.ts +++ b/packages/kit/src/types/internal.d.ts @@ -178,7 +178,12 @@ export class InternalServer extends Server { prerendering?: PrerenderOptions; read: (file: string) => NonSharedBuffer; /** A hook called before `handle` during dev, so that `AsyncLocalStorage` can be populated. */ - before_handle?: (event: RequestEvent, config: any, prerender: PrerenderOption) => void; + before_handle?: ( + event: RequestEvent, + config: any, + prerender: PrerenderOption, + handle: () => Promise + ) => Promise; } ): Promise; } diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 8d3624222fe3..027d41a37fbf 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -3040,40 +3040,52 @@ declare module '@sveltejs/kit/internal/server' { validate_only?: boolean; }; - type RemoteInfo = - | { - type: 'query' | 'command'; - id: string; - name: string; - } - | { - /** - * Corresponds to the name of the client-side exports (that's why we use underscores and not dots) - */ - type: 'query_batch'; - id: string; - name: string; - /** Direct access to the function, for remote functions called from the client */ - run: (args: any[], options: SSROptions) => Promise; - } - | { - type: 'form'; - id: string; - name: string; - fn: ( - body: Record, - meta: BinaryFormMeta, - form_data: FormData | null - ) => Promise; - } - | { - type: 'prerender'; - id: string; - name: string; - has_arg: boolean; - dynamic?: boolean; - inputs?: RemotePrerenderInputsGenerator; - }; + interface BaseRemoteInternals { + type: string; + id: string; + name: string; + } + + interface RemoteQueryInternals extends BaseRemoteInternals { + type: 'query'; + } + interface RemoteQueryLiveInternals extends BaseRemoteInternals { + type: 'query_live'; + run( + event: RequestEvent, + state: RequestState, + arg: any + ): Promise<{ iterator: AsyncIterator; cancel: () => void }>; + } + + interface RemoteQueryBatchInternals extends BaseRemoteInternals { + type: 'query_batch'; + run: (args: any[], options: SSROptions) => Promise; + } + + interface RemoteCommandInternals extends BaseRemoteInternals { + type: 'command'; + } + + interface RemoteFormInternals extends BaseRemoteInternals { + type: 'form'; + fn(body: Record, meta: BinaryFormMeta, form_data: FormData | null): Promise; + } + + interface RemotePrerenderInternals extends BaseRemoteInternals { + type: 'prerender'; + has_arg: boolean; + dynamic?: boolean; + inputs?: RemotePrerenderInputsGenerator; + } + + type RemoteInternals = + | RemoteQueryInternals + | RemoteQueryLiveInternals + | RemoteQueryBatchInternals + | RemoteCommandInternals + | RemoteFormInternals + | RemotePrerenderInternals; type RecordSpan = (options: { name: string; @@ -3086,17 +3098,23 @@ declare module '@sveltejs/kit/internal/server' { * used for tracking things like remote function calls */ interface RequestState { - prerendering: PrerenderOptions | undefined; - transport: ServerHooks['transport']; - handleValidationError: ServerHooks['handleValidationError']; - tracing: { + readonly prerendering: PrerenderOptions | undefined; + readonly transport: ServerHooks['transport']; + readonly handleValidationError: ServerHooks['handleValidationError']; + readonly tracing: { record_span: RecordSpan; }; - is_in_remote_function: boolean; - form_instances?: Map; - remote_data?: Map>>; - refreshes?: Record>; - allows_commands?: boolean; + readonly remote: { + data: null | Map< + RemoteInternals, + Record }> + >; + forms: null | Map; + refreshes: null | Record>; + }; + readonly is_in_remote_function: boolean; + readonly is_in_render: boolean; + readonly is_in_universal_load: boolean; } interface RequestStore { From 013fa511b093566e4c5c7eee42869b5354cf8806 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Fri, 20 Mar 2026 20:09:57 +0800 Subject: [PATCH 003/117] format --- packages/adapter-cloudflare/utils.js | 2 ++ packages/kit/src/runtime/app/environment/internal.js | 3 ++- packages/kit/src/runtime/app/paths/internal/server.js | 9 ++++++--- packages/kit/src/types/virtual.d.ts | 2 +- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/adapter-cloudflare/utils.js b/packages/adapter-cloudflare/utils.js index ea4a11f5481f..eb7caff50dd3 100644 --- a/packages/adapter-cloudflare/utils.js +++ b/packages/adapter-cloudflare/utils.js @@ -1,3 +1,5 @@ +// TODO: we might not need this since the cloudflare vite plugin will do this for us + /** @param {import('wrangler').Unstable_Config} wrangler_config */ export function validate_worker_settings(wrangler_config) { const config_path = wrangler_config.configPath || 'your wrangler.jsonc file'; diff --git a/packages/kit/src/runtime/app/environment/internal.js b/packages/kit/src/runtime/app/environment/internal.js index ec9e7a780989..bb84c30951e7 100644 --- a/packages/kit/src/runtime/app/environment/internal.js +++ b/packages/kit/src/runtime/app/environment/internal.js @@ -1,4 +1,5 @@ -export const version = typeof __SVELTEKIT_APP_VERSION__ === 'string' ? __SVELTEKIT_APP_VERSION__ : 'unknown'; +export const version = + typeof __SVELTEKIT_APP_VERSION__ === 'string' ? __SVELTEKIT_APP_VERSION__ : 'unknown'; export let building = false; export let prerendering = false; diff --git a/packages/kit/src/runtime/app/paths/internal/server.js b/packages/kit/src/runtime/app/paths/internal/server.js index 22077ab991c5..954265c26e39 100644 --- a/packages/kit/src/runtime/app/paths/internal/server.js +++ b/packages/kit/src/runtime/app/paths/internal/server.js @@ -1,7 +1,10 @@ export let base = typeof __SVELTEKIT_PATHS_BASE__ !== 'undefined' ? __SVELTEKIT_PATHS_BASE__ : ''; -export let assets = typeof __SVELTEKIT_PATHS_ASSETS__ !== 'undefined' ? __SVELTEKIT_PATHS_ASSETS__ : base; -export const app_dir = typeof __SVELTEKIT_APP_DIR__ !== 'undefined' ? __SVELTEKIT_APP_DIR__ : '_app'; -export const relative = typeof __SVELTEKIT_PATHS_RELATIVE__ !== 'undefined' ? __SVELTEKIT_PATHS_RELATIVE__ : true; +export let assets = + typeof __SVELTEKIT_PATHS_ASSETS__ !== 'undefined' ? __SVELTEKIT_PATHS_ASSETS__ : base; +export const app_dir = + typeof __SVELTEKIT_APP_DIR__ !== 'undefined' ? __SVELTEKIT_APP_DIR__ : '_app'; +export const relative = + typeof __SVELTEKIT_PATHS_RELATIVE__ !== 'undefined' ? __SVELTEKIT_PATHS_RELATIVE__ : true; const initial = { base, assets }; diff --git a/packages/kit/src/types/virtual.d.ts b/packages/kit/src/types/virtual.d.ts index bc1744855c47..910262c9b8d4 100644 --- a/packages/kit/src/types/virtual.d.ts +++ b/packages/kit/src/types/virtual.d.ts @@ -10,5 +10,5 @@ declare module '__sveltekit/ssr-manifest' { } declare module '__sveltekit/dev' { - export { Server } from '@sveltejs/kit'; + export { Server } from '@sveltejs/kit'; } From fa879bab87aabab0bf02d306cdc0d8f7a1d717e7 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Fri, 20 Mar 2026 20:16:23 +0800 Subject: [PATCH 004/117] fix type --- packages/kit/src/runtime/server/external.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/kit/src/runtime/server/external.js b/packages/kit/src/runtime/server/external.js index dba092dbce65..98628e5ff756 100644 --- a/packages/kit/src/runtime/server/external.js +++ b/packages/kit/src/runtime/server/external.js @@ -8,9 +8,11 @@ export let read_implementation = null; /** - * @type {SSRManifest | null} + * The manifest will be set when the server starts. Exporting it this way allows + * us to access its value without having to pass it around as an argument. + * This is especially useful for public APIs such as `read` and `match` */ -export let manifest = null; +export let manifest = /** @type {SSRManifest} */ (/** @type {unknown} */ (null)); /** * @param {ReadImplementation} fn From 6f4026888a0d2d60834c971f8ec8ec15e349b42f Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Sat, 21 Mar 2026 10:37:26 +0800 Subject: [PATCH 005/117] format --- .../test/apps/workers/config/wrangler.jsonc | 2 +- packages/adapter-cloudflare/utils.spec.js | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/adapter-cloudflare/test/apps/workers/config/wrangler.jsonc b/packages/adapter-cloudflare/test/apps/workers/config/wrangler.jsonc index b3975e5485e1..fed0d7b53a2a 100644 --- a/packages/adapter-cloudflare/test/apps/workers/config/wrangler.jsonc +++ b/packages/adapter-cloudflare/test/apps/workers/config/wrangler.jsonc @@ -1,5 +1,5 @@ // we've moved the wrangler config away from the root of the project // to test that the adapter still resolves the paths correctly { - "$schema": "../../../../node_modules/wrangler/config-schema.json", + "$schema": "../../../../node_modules/wrangler/config-schema.json" } diff --git a/packages/adapter-cloudflare/utils.spec.js b/packages/adapter-cloudflare/utils.spec.js index 229a0fbef60a..4bdf4298fbe6 100644 --- a/packages/adapter-cloudflare/utils.spec.js +++ b/packages/adapter-cloudflare/utils.spec.js @@ -1,7 +1,5 @@ import { describe, test, expect } from 'vitest'; -import { - validate_worker_settings, -} from './utils.js'; +import { validate_worker_settings } from './utils.js'; describe('validates Wrangler config', () => { test('Worker and static assets', () => { From 49e07b95033e72cfc4372163de889df617f1c1a3 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Sat, 21 Mar 2026 10:38:54 +0800 Subject: [PATCH 006/117] fix test --- packages/adapter-cloudflare/utils.spec.js | 47 ----------------------- 1 file changed, 47 deletions(-) diff --git a/packages/adapter-cloudflare/utils.spec.js b/packages/adapter-cloudflare/utils.spec.js index 4bdf4298fbe6..c2b3c1aa248c 100644 --- a/packages/adapter-cloudflare/utils.spec.js +++ b/packages/adapter-cloudflare/utils.spec.js @@ -30,51 +30,4 @@ describe('validates Wrangler config', () => { ).not.toThrow(); }); - test('missing `assets.directory` key', () => { - expect(() => - validate_worker_settings( - /** @type {import('wrangler').Unstable_Config} */ ({ - configPath: 'wrangler.jsonc', - main: 'dist/index.js', - assets: { - binding: 'ASSETS' - } - }) - ) - ).toThrow( - `You must specify the \`assets.directory\` key in wrangler.jsonc. Consult https://developers.cloudflare.com/workers/static-assets/binding/#directory` - ); - }); - - test('missing `assets.binding` key', () => { - expect(() => - validate_worker_settings( - /** @type {import('wrangler').Unstable_Config} */ ({ - configPath: 'wrangler.jsonc', - main: 'dist/index.js', - assets: { - directory: 'dist/assets' - } - }) - ) - ).toThrow( - `You must specify the \`assets.binding\` key in wrangler.jsonc before deploying your Worker. Consult https://developers.cloudflare.com/workers/static-assets/binding/#binding` - ); - }); - - test('missing `main` key or should remove `assets.binding` key', () => { - expect(() => - validate_worker_settings( - /** @type {import('wrangler').Unstable_Config} */ ({ - configPath: 'wrangler.jsonc', - assets: { - directory: 'dist/assets', - binding: 'ASSETS' - } - }) - ) - ).toThrow( - `You must specify the \`main\` key in wrangler.jsonc if you want to deploy a Worker alongside your static assets. Otherwise, remove the \`assets.binding\` key if you only want to deploy static assets.` - ); - }); }); From c844a5f218ea5b5afd0b9814f470f397978b20df Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Sat, 21 Mar 2026 10:55:29 +0800 Subject: [PATCH 007/117] dev only plugins --- packages/kit/src/exports/vite/index.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 1fb0927201cb..748abd367ee3 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -51,7 +51,8 @@ const cwd = process.cwd(); /** @type {string} */ let root; -const dev_environment = /** @type {import('types').DevEnvironment} */ ({}); +/** @type {import('types').DevEnvironment | null} */ +let dev_environment = null; /** @type {import('./types.js').EnforcedConfig} */ const enforced_config = { @@ -600,6 +601,8 @@ function kit({ svelte_config, vite_adapter }) { return create_service_worker_module(svelte_config); case sveltekit_server_assets: { + if (vite_config_env.command === 'build') return; + return dedent` export const server_assets = { ${Object.entries(server_assets) @@ -616,6 +619,8 @@ function kit({ svelte_config, vite_adapter }) { } case sveltekit_ssr_manifest: { + if (!dev_environment) return; + const { manifest, manifest_data, env } = dev_environment; return dedent` @@ -742,6 +747,8 @@ function kit({ svelte_config, vite_adapter }) { } case sveltekit_dev: { + if (vite_config_env.command === 'build') return; + const runtime_base = get_runtime_base(root); const adapter = svelte_config.kit.adapter; @@ -1466,7 +1473,10 @@ function kit({ svelte_config, vite_adapter }) { * @see https://vitejs.dev/guide/api-plugin.html#configureserver */ configureServer(vite) { - dev_environment.vite = vite; + // the other properties will be populated in the `dev` function + dev_environment = /** @type {import('types').DevEnvironment} */ ({ + vite + }); return dev(vite, vite_config, svelte_config, () => remotes, root, dev_environment); }, @@ -1820,6 +1830,7 @@ function kit({ svelte_config, vite_adapter }) { /** @type {import('vite').Plugin} */ const plugin_server_filesystem = { name: 'vite-plugin-sveltekit-server-filesystem', + apply: 'serve', applyToEnvironment(environment) { return environment.name !== 'client' && environment.name !== 'serviceWorker'; }, @@ -1829,6 +1840,8 @@ function kit({ svelte_config, vite_adapter }) { load: { order: 'pre', handler(id) { + if (!dev_environment) return; + const { pathname, searchParams } = new URL(id, 'file://'); if ( (searchParams.has('url') || vite_config.assetsInclude(pathname)) && From c5f22d746b8ae22f7b272db2855248c8c87f65c4 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Sat, 21 Mar 2026 10:57:16 +0800 Subject: [PATCH 008/117] format again --- packages/adapter-cloudflare/utils.spec.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/adapter-cloudflare/utils.spec.js b/packages/adapter-cloudflare/utils.spec.js index c2b3c1aa248c..26abd64287e4 100644 --- a/packages/adapter-cloudflare/utils.spec.js +++ b/packages/adapter-cloudflare/utils.spec.js @@ -29,5 +29,4 @@ describe('validates Wrangler config', () => { ) ).not.toThrow(); }); - }); From 3e19d7cad3aa8656c0e38dd7800be8efd986c63d Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Sat, 21 Mar 2026 10:59:13 +0800 Subject: [PATCH 009/117] no more cf pages test app --- .../test/apps/pages/.gitignore | 4 --- .../test/apps/pages/package.json | 21 -------------- .../test/apps/pages/playwright.config.js | 1 - .../apps/pages/server-side-dep/index.d.ts | 1 - .../test/apps/pages/server-side-dep/index.js | 4 --- .../apps/pages/server-side-dep/package.json | 11 ------- .../test/apps/pages/src/app.html | 11 ------- .../apps/pages/src/routes/+page.server.js | 8 ----- .../test/apps/pages/src/routes/+page.svelte | 5 ---- .../test/apps/pages/svelte.config.js | 10 ------- .../test/apps/pages/test/test.js | 6 ---- .../test/apps/pages/tsconfig.json | 14 --------- .../test/apps/pages/vite.config.js | 11 ------- pnpm-lock.yaml | 29 ------------------- 14 files changed, 136 deletions(-) delete mode 100644 packages/adapter-cloudflare/test/apps/pages/.gitignore delete mode 100644 packages/adapter-cloudflare/test/apps/pages/package.json delete mode 100644 packages/adapter-cloudflare/test/apps/pages/playwright.config.js delete mode 100644 packages/adapter-cloudflare/test/apps/pages/server-side-dep/index.d.ts delete mode 100644 packages/adapter-cloudflare/test/apps/pages/server-side-dep/index.js delete mode 100644 packages/adapter-cloudflare/test/apps/pages/server-side-dep/package.json delete mode 100644 packages/adapter-cloudflare/test/apps/pages/src/app.html delete mode 100644 packages/adapter-cloudflare/test/apps/pages/src/routes/+page.server.js delete mode 100644 packages/adapter-cloudflare/test/apps/pages/src/routes/+page.svelte delete mode 100644 packages/adapter-cloudflare/test/apps/pages/svelte.config.js delete mode 100644 packages/adapter-cloudflare/test/apps/pages/test/test.js delete mode 100644 packages/adapter-cloudflare/test/apps/pages/tsconfig.json delete mode 100644 packages/adapter-cloudflare/test/apps/pages/vite.config.js diff --git a/packages/adapter-cloudflare/test/apps/pages/.gitignore b/packages/adapter-cloudflare/test/apps/pages/.gitignore deleted file mode 100644 index 1bd7b63de4b6..000000000000 --- a/packages/adapter-cloudflare/test/apps/pages/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -.DS_Store -node_modules -/.svelte-kit -/.wrangler \ No newline at end of file diff --git a/packages/adapter-cloudflare/test/apps/pages/package.json b/packages/adapter-cloudflare/test/apps/pages/package.json deleted file mode 100644 index 0839ddd0a69f..000000000000 --- a/packages/adapter-cloudflare/test/apps/pages/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "test-cloudflare-pages", - "version": "0.0.1", - "private": true, - "scripts": { - "dev": "vite dev", - "build": "vite build", - "preview": "wrangler pages dev .svelte-kit/cloudflare --port 8787", - "prepare": "svelte-kit sync || echo ''", - "test": "playwright test" - }, - "devDependencies": { - "@sveltejs/kit": "workspace:^", - "@sveltejs/vite-plugin-svelte": "catalog:", - "server-side-dep": "file:server-side-dep", - "svelte": "catalog:", - "vite": "catalog:", - "wrangler": "catalog:" - }, - "type": "module" -} diff --git a/packages/adapter-cloudflare/test/apps/pages/playwright.config.js b/packages/adapter-cloudflare/test/apps/pages/playwright.config.js deleted file mode 100644 index 33d36b651014..000000000000 --- a/packages/adapter-cloudflare/test/apps/pages/playwright.config.js +++ /dev/null @@ -1 +0,0 @@ -export { config as default } from '../../utils.js'; diff --git a/packages/adapter-cloudflare/test/apps/pages/server-side-dep/index.d.ts b/packages/adapter-cloudflare/test/apps/pages/server-side-dep/index.d.ts deleted file mode 100644 index 0e1188e04a67..000000000000 --- a/packages/adapter-cloudflare/test/apps/pages/server-side-dep/index.d.ts +++ /dev/null @@ -1 +0,0 @@ -export function sum(a: number, b: number): number; diff --git a/packages/adapter-cloudflare/test/apps/pages/server-side-dep/index.js b/packages/adapter-cloudflare/test/apps/pages/server-side-dep/index.js deleted file mode 100644 index 568b90577d43..000000000000 --- a/packages/adapter-cloudflare/test/apps/pages/server-side-dep/index.js +++ /dev/null @@ -1,4 +0,0 @@ -/** @type {import('./index.js').sum} */ -export function sum(a, b) { - return a + b; -} diff --git a/packages/adapter-cloudflare/test/apps/pages/server-side-dep/package.json b/packages/adapter-cloudflare/test/apps/pages/server-side-dep/package.json deleted file mode 100644 index 5b26c9d1855a..000000000000 --- a/packages/adapter-cloudflare/test/apps/pages/server-side-dep/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "server-side-dep", - "version": "0.0.1", - "type": "module", - "exports": { - ".": { - "default": "./index.js", - "types": "./index.d.ts" - } - } -} diff --git a/packages/adapter-cloudflare/test/apps/pages/src/app.html b/packages/adapter-cloudflare/test/apps/pages/src/app.html deleted file mode 100644 index d533c5e31716..000000000000 --- a/packages/adapter-cloudflare/test/apps/pages/src/app.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - %sveltekit.head% - - -
%sveltekit.body%
- - diff --git a/packages/adapter-cloudflare/test/apps/pages/src/routes/+page.server.js b/packages/adapter-cloudflare/test/apps/pages/src/routes/+page.server.js deleted file mode 100644 index 0eeb7d398ffd..000000000000 --- a/packages/adapter-cloudflare/test/apps/pages/src/routes/+page.server.js +++ /dev/null @@ -1,8 +0,0 @@ -// this tests that Wrangler can correctly resolve and bundle server-side dependencies -import { sum } from 'server-side-dep'; - -export function load() { - return { - sum: sum(1, 2) - }; -} diff --git a/packages/adapter-cloudflare/test/apps/pages/src/routes/+page.svelte b/packages/adapter-cloudflare/test/apps/pages/src/routes/+page.svelte deleted file mode 100644 index d5e339683387..000000000000 --- a/packages/adapter-cloudflare/test/apps/pages/src/routes/+page.svelte +++ /dev/null @@ -1,5 +0,0 @@ - - -

Sum: {data.sum}

diff --git a/packages/adapter-cloudflare/test/apps/pages/svelte.config.js b/packages/adapter-cloudflare/test/apps/pages/svelte.config.js deleted file mode 100644 index 20cd2b3ff5b8..000000000000 --- a/packages/adapter-cloudflare/test/apps/pages/svelte.config.js +++ /dev/null @@ -1,10 +0,0 @@ -import adapter from '../../../index.js'; - -/** @type {import('@sveltejs/kit').Config} */ -const config = { - kit: { - adapter: adapter() - } -}; - -export default config; diff --git a/packages/adapter-cloudflare/test/apps/pages/test/test.js b/packages/adapter-cloudflare/test/apps/pages/test/test.js deleted file mode 100644 index 0619c030bbc2..000000000000 --- a/packages/adapter-cloudflare/test/apps/pages/test/test.js +++ /dev/null @@ -1,6 +0,0 @@ -import { expect, test } from '@playwright/test'; - -test('worker', async ({ page }) => { - await page.goto('/'); - await expect(page.locator('h1')).toContainText('Sum: 3'); -}); diff --git a/packages/adapter-cloudflare/test/apps/pages/tsconfig.json b/packages/adapter-cloudflare/test/apps/pages/tsconfig.json deleted file mode 100644 index 34380ebc986e..000000000000 --- a/packages/adapter-cloudflare/test/apps/pages/tsconfig.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "compilerOptions": { - "allowJs": true, - "checkJs": true, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, - "skipLibCheck": true, - "sourceMap": true, - "strict": true, - "moduleResolution": "bundler" - }, - "extends": "./.svelte-kit/tsconfig.json" -} diff --git a/packages/adapter-cloudflare/test/apps/pages/vite.config.js b/packages/adapter-cloudflare/test/apps/pages/vite.config.js deleted file mode 100644 index 29ad08debe6a..000000000000 --- a/packages/adapter-cloudflare/test/apps/pages/vite.config.js +++ /dev/null @@ -1,11 +0,0 @@ -import { sveltekit } from '@sveltejs/kit/vite'; - -/** @type {import('vite').UserConfig} */ -const config = { - build: { - minify: false - }, - plugins: [sveltekit()] -}; - -export default config; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e45e8381c4b7..dee67878e03a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -105,9 +105,6 @@ catalogs: vitest: specifier: ^4.1.0 version: 4.1.0 - wrangler: - specifier: ^4.74.0 - version: 4.74.0 importers: @@ -199,27 +196,6 @@ importers: specifier: 'catalog:' version: 4.1.0(@opentelemetry/api@1.9.0)(@types/node@24.10.13)(@vitest/browser-playwright@4.1.0-beta.4)(vite@8.0.0(@types/node@24.10.13)(esbuild@0.27.3)(jiti@2.4.2)(yaml@2.8.0)) - packages/adapter-cloudflare/test/apps/pages: - devDependencies: - '@sveltejs/kit': - specifier: workspace:^ - version: link:../../../../kit - '@sveltejs/vite-plugin-svelte': - specifier: 'catalog:' - version: 7.0.0(svelte@5.53.12)(vite@8.0.0(@types/node@24.10.13)(esbuild@0.27.3)(jiti@2.4.2)(yaml@2.8.0)) - server-side-dep: - specifier: file:server-side-dep - version: file:packages/adapter-cloudflare/test/apps/pages/server-side-dep - svelte: - specifier: 'catalog:' - version: 5.53.12 - vite: - specifier: 'catalog:' - version: 8.0.0(@types/node@24.10.13)(esbuild@0.27.3)(jiti@2.4.2)(yaml@2.8.0) - wrangler: - specifier: 'catalog:' - version: 4.74.0(@cloudflare/workers-types@4.20260317.1) - packages/adapter-cloudflare/test/apps/workers: devDependencies: '@sveltejs/kit': @@ -5161,9 +5137,6 @@ packages: engines: {node: '>=10'} hasBin: true - server-side-dep@file:packages/adapter-cloudflare/test/apps/pages/server-side-dep: - resolution: {directory: packages/adapter-cloudflare/test/apps/pages/server-side-dep, type: directory} - server-side-dep@file:packages/adapter-cloudflare/test/apps/workers/server-side-dep: resolution: {directory: packages/adapter-cloudflare/test/apps/workers/server-side-dep, type: directory} @@ -9692,8 +9665,6 @@ snapshots: semver@7.7.4: {} - server-side-dep@file:packages/adapter-cloudflare/test/apps/pages/server-side-dep: {} - server-side-dep@file:packages/adapter-cloudflare/test/apps/workers/server-side-dep: {} sharp@0.34.5: From 0a399814215ab9162e1342b902bffb14652a60e3 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Sat, 21 Mar 2026 13:11:09 +0800 Subject: [PATCH 010/117] fix lint --- packages/kit/src/runtime/server/utils.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/kit/src/runtime/server/utils.js b/packages/kit/src/runtime/server/utils.js index d9fd818268ff..dc18138df894 100644 --- a/packages/kit/src/runtime/server/utils.js +++ b/packages/kit/src/runtime/server/utils.js @@ -209,11 +209,12 @@ export function format_server_error(status, error, event) { return `${formatted_text}\n${DEV ? clean_up_stack_trace(error) : error.stack}`; } +// TODO: do we still need to clean up stack traces when using the environment API? /** * In dev, tidy up stack traces by making paths relative to the current project directory * @param {string} file */ -let relative = (file) => file; +const relative = (file) => file; // if (DEV) { // try { From 96ca6d5beae04d56da3ac94e7d396db71abc63fa Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Mon, 23 Mar 2026 08:29:40 +0800 Subject: [PATCH 011/117] hooray ipc --- .../adapter-cloudflare/fallback-worker.js | 4 +- packages/adapter-cloudflare/index.js | 72 +-- packages/kit/src/exports/public.d.ts | 10 +- .../kit/src/exports/vite/dev/fetchable.js | 18 +- packages/kit/src/exports/vite/dev/index.js | 533 +++++------------ packages/kit/src/exports/vite/index.js | 542 +++++++++++++----- packages/kit/src/exports/vite/module_ids.js | 5 +- packages/kit/src/runtime/server/generated.js | 2 +- .../kit/src/runtime/server/page/csp.spec.js | 1 + packages/kit/src/types/internal.d.ts | 3 +- packages/kit/src/types/virtual.d.ts | 4 +- packages/kit/types/index.d.ts | 10 +- 12 files changed, 618 insertions(+), 586 deletions(-) diff --git a/packages/adapter-cloudflare/fallback-worker.js b/packages/adapter-cloudflare/fallback-worker.js index 26499611189a..a5e861be23b5 100644 --- a/packages/adapter-cloudflare/fallback-worker.js +++ b/packages/adapter-cloudflare/fallback-worker.js @@ -120,6 +120,4 @@ export default { }; // Without this, server file changes will invalidate the entire server module graph -if (import.meta.hot) { - import.meta.hot.accept(); -} +import.meta.hot?.accept(); diff --git a/packages/adapter-cloudflare/index.js b/packages/adapter-cloudflare/index.js index 33c93acc4905..19882400e6ee 100644 --- a/packages/adapter-cloudflare/index.js +++ b/packages/adapter-cloudflare/index.js @@ -138,46 +138,48 @@ export default function (options = {}) { read: () => true, instrumentation: () => true }, - vitePlugins: [ - cloudflare({ - ...options.vitePluginOptions, - configPath: options.vitePluginOptions?.configPath ?? options.config, - viteEnvironment: { - name: options.vitePluginOptions?.viteEnvironment?.name ?? 'ssr', - childEnvironments: options.vitePluginOptions?.viteEnvironment?.childEnvironments - }, - config: (user_config) => { - // user programmatic config - if (typeof options.vitePluginOptions?.config === 'function') { - options.vitePluginOptions?.config(user_config); - } else { - Object.assign(user_config, options.vitePluginOptions?.config); - } - - if (DEV) { - if (!user_config.assets?.binding) { - user_config.assets = { - binding: 'ASSETS' - }; + vite: { + plugins: [ + cloudflare({ + ...options.vitePluginOptions, + configPath: options.vitePluginOptions?.configPath ?? options.config, + viteEnvironment: { + name: options.vitePluginOptions?.viteEnvironment?.name ?? 'ssr', + childEnvironments: options.vitePluginOptions?.viteEnvironment?.childEnvironments + }, + config: (user_config) => { + // user programmatic config + if (typeof options.vitePluginOptions?.config === 'function') { + options.vitePluginOptions?.config(user_config); + } else { + Object.assign(user_config, options.vitePluginOptions?.config); } - if (!user_config.main) { - user_config.main = path.resolve(import.meta.dirname, 'fallback-worker.js'); + if (DEV) { + if (!user_config.assets?.binding) { + user_config.assets = { + binding: 'ASSETS' + }; + } + + if (!user_config.main) { + user_config.main = path.resolve(import.meta.dirname, 'fallback-worker.js'); + } + } else { + // TODO: if `main` or `assets.binding` is configured, ensure `main`, `assets.directory` and `assets.binding` are populated } - } else { - // TODO: if `main` or `assets.binding` is configured, ensure `main`, `assets.directory` and `assets.binding` are populated - } - if ( - !user_config.compatibility_flags.find( - (flag) => flag === 'nodejs_als' || flag === 'nodejs_compat' - ) - ) { - user_config.compatibility_flags.push('nodejs_als'); + if ( + !user_config.compatibility_flags.find( + (flag) => flag === 'nodejs_als' || flag === 'nodejs_compat' + ) + ) { + user_config.compatibility_flags.push('nodejs_als'); + } } - } - }) - ] + }) + ] + } }; } diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index e20bd4e12937..bc48c2211730 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -70,10 +70,12 @@ export interface Adapter { * @deprecated removed in 3.0.0 */ emulate?: () => MaybePromise; - /** - * @since 3.0.0 - */ - vitePlugins?: PluginOption; + vite?: { + /** + * @since 3.0.0 + */ + plugins?: PluginOption; + }; } export type LoadProperties | void> = input extends void diff --git a/packages/kit/src/exports/vite/dev/fetchable.js b/packages/kit/src/exports/vite/dev/fetchable.js index 171588286fab..1253b463c12e 100644 --- a/packages/kit/src/exports/vite/dev/fetchable.js +++ b/packages/kit/src/exports/vite/dev/fetchable.js @@ -1,4 +1,4 @@ -// import { buildErrorMessage, createServer } from 'vite'; +import { buildErrorMessage } from 'vite'; // `posixify` and `to_fs` are duplicated from utils/filesystem.js to avoid // imports from `node:*` which aren't available in Cloudflare's workerd runtime @@ -33,9 +33,6 @@ export function from_fs(str) { return str[2] === ':' && /[A-Z]/.test(str[1]) ? str.slice(1) : str; } -// const server = await createServer({}); -// const ssr_environment = server.environments.ssr; - /** @param {string} id */ export async function resolve(id) { // TODO: doesn't work for files symlinked to kit package workspace @@ -49,22 +46,22 @@ export async function resolve(id) { return { module, module_node: '', url }; } +// TODO: do we even need this or will Vite handle import errors for us? /** * @param {string} url */ export async function loud_ssr_load_module(url) { - // TODO: properly implement this for fetchable environments - // eslint-disable-next-line no-useless-catch try { // return await server.ssrLoadModule(url, { fixStacktrace: true }); return await import(/* @vite-ignore */ url); } catch (/** @type {any} */ err) { // const msg = buildErrorMessage(err, [styleText('red', `Internal server error: ${err.message}`)]); - // const msg = buildErrorMessage(err, [`Internal server error: ${err.message}`]); + const msg = buildErrorMessage(err, [`Internal server error: ${err.message}`]); // if (!server.config.logger.hasErrorLogged(err)) { // server.config.logger.error(msg, { error: err }); // } + console.error(msg); // server.ws.send({ // type: 'error', @@ -76,6 +73,13 @@ export async function loud_ssr_load_module(url) { // stack: err.stack // } // }); + import.meta.hot?.send('vite:error', { + ...err, + // these properties are non-enumerable and will + // not be serialized unless we explicitly include them + message: err.message, + stack: err.stack + }); throw err; } diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 344463670748..e7e18cbf8789 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -4,16 +4,14 @@ import { URL } from 'node:url'; import { styleText } from 'node:util'; import sirv from 'sirv'; -import { isCSSRequest, loadEnv, buildErrorMessage } from 'vite'; +import { isCSSRequest, isFetchableDevEnvironment } from 'vite'; -import { createReadableStream, getRequest, setResponse } from '../../../exports/node/index.js'; +import { getRequest, setResponse } from '@sveltejs/kit/node'; import { coalesce_to_error } from '../../../utils/error.js'; -import { from_fs, posixify, resolve_entry, to_fs } from '../../../utils/filesystem.js'; +import { posixify, resolve_entry, to_fs } from '../../../utils/filesystem.js'; import { load_error_page } from '../../../core/config/index.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 { compact } from '../../../utils/array.js'; import { not_found } from '../utils.js'; import { escape_html } from '../../../utils/escape.js'; import { sveltekit_ssr_manifest } from '../module_ids.js'; @@ -25,62 +23,19 @@ 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 {() => Array<{ hash: string, file: string }>} get_remotes * @param {string} root The project root directory * @param {import('types').DevEnvironment} dev_environment * @return {() => void} */ -export function dev(vite, vite_config, svelte_config, get_remotes, root, dev_environment) { +export function dev(vite, vite_config, svelte_config, root, dev_environment) { sync.init(svelte_config, vite_config.mode, 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 }); - } - - vite.ws.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 */ - async function resolve(id) { - const url = id.startsWith('..') ? to_fs(path.posix.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 }; - } - function update_manifest() { try { ({ manifest_data } = sync.create(svelte_config, root)); @@ -105,185 +60,6 @@ export function dev(vite, vite_config, svelte_config, get_remotes, root, dev_env 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 = 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 () => { - /** @type {Record} */ - const matchers = {}; - - for (const key in manifest_data.matchers) { - const file = manifest_data.matchers[key]; - const url = path.resolve(root, file); - const module = await vite.ssrLoadModule(url, { fixStacktrace: true }); - - if (module.match) { - matchers[key] = module.match; - } else { - throw new Error(`${file} does not export a \`match\` function`); - } - } - - return matchers; - } - } - }; - dev_environment.manifest = manifest; invalidate_module(vite, sveltekit_ssr_manifest); } @@ -369,6 +145,8 @@ export function dev(vite, vite_config, svelte_config, get_remotes, root, dev_env }); const assets = svelte_config.kit.paths.assets ? SVELTE_KIT_ASSETS : svelte_config.kit.paths.base; + dev_environment.assets = assets; + const asset_server = sirv(svelte_config.kit.files.assets, { dev: true, etag: true, @@ -379,6 +157,8 @@ export function dev(vite, vite_config, svelte_config, get_remotes, root, dev_env } }); + requests = new Map(); + vite.middlewares.use((req, res, next) => { const base = `${vite.config.server.https ? 'https' : 'http'}://${ req.headers[':authority'] || req.headers.host @@ -402,9 +182,6 @@ export function dev(vite, vite_config, svelte_config, get_remotes, root, dev_env next(); }); - const env = loadEnv(vite_config.mode, svelte_config.kit.env.dir, ''); - dev_environment.env = env; - return () => { const serve_static_middleware = vite.middlewares.stack.find( (middleware) => @@ -415,180 +192,142 @@ export function dev(vite, vite_config, svelte_config, get_remotes, root, dev_env // serving routes with those names. See https://github.com/vitejs/vite/issues/7363 remove_static_middlewares(vite.middlewares); - if (svelte_config.kit.adapter?.vitePlugins) { - vite.middlewares.use((req, res, next) => { - // Vite's base middleware strips out the base path. Restore it - let original_url = req.url; - req.url = req.originalUrl; - try { - const base = `${vite.config.server.https ? 'https' : 'http'}://${ - req.headers[':authority'] || req.headers.host - }`; - - let decoded = decodeURI(new URL(base + req.url).pathname); - - // requests to _app/immutable during development are fetchable dev - // environments trying to read the filesystem - const immutable = `/${manifest.appPath}/immutable`; - if (decoded.startsWith(immutable)) { - decoded = decoded.slice(immutable.length); - original_url = original_url?.slice(immutable.length); - } + vite.middlewares.use(async (req, res, next) => { + dev_environment.remote_address = req.socket.remoteAddress; + + // Vite's base middleware strips out the base path. Restore it + let original_url = req.url; + req.url = req.originalUrl; + try { + const base = `${vite.config.server.https ? 'https' : 'http'}://${ + req.headers[':authority'] || req.headers.host + }`; + + let decoded = decodeURI(new URL(base + req.url).pathname); + + // requests to _app/immutable during development are fetchable dev + // environments trying to read the filesystem + const immutable = `/${svelte_config.kit.appDir}/immutable`; + if (decoded.startsWith(immutable)) { + decoded = decoded.slice(immutable.length); + original_url = original_url?.slice(immutable.length); + } - const file = posixify( - path.resolve(root, decoded.slice(svelte_config.kit.paths.base.length + 1)) - ); - const is_file = fs.existsSync(file) && !fs.statSync(file).isDirectory(); - const allowed = - !vite_config.server.fs.strict || - vite_config.server.fs.allow.some((dir) => file.startsWith(dir)); + // TODO: come up with a better name than ipc + const prefix = `/${svelte_config.kit.appDir}/ipc/`; + if (decoded.startsWith(prefix)) { + const id = decoded.slice(prefix.length); + const request = await getRequest({ + base, + request: req + }); - if (is_file && allowed) { - req.url = original_url; - // @ts-expect-error - serve_static_middleware.handle(req, res); + if (!requests.has(id)) { + res.writeHead(400); + res.end(`ipc call id does not exist: ${id}`); return; } - if (!decoded.startsWith(svelte_config.kit.paths.base)) { - return not_found(req, res, svelte_config.kit.paths.base); + const requested = requests.get(id); + try { + const data = await request.json(); + requested?.resolve(data); + } catch (e) { + requested?.reject(e); } + requests.delete(id); - next(); - } catch (e) { - const error = coalesce_to_error(e); - res.statusCode = 500; - res.end(error); + res.writeHead(200); + res.end(); + return; } - }); - } else { - vite.middlewares.use(async (req, res) => { - // Vite's base middleware strips out the base path. Restore it - const original_url = req.url; - req.url = req.originalUrl; - try { - const base = `${vite.config.server.https ? 'https' : 'http'}://${ - req.headers[':authority'] || req.headers.host - }`; - - const decoded = decodeURI(new URL(base + req.url).pathname); - const file = posixify( - path.resolve(root, decoded.slice(svelte_config.kit.paths.base.length + 1)) - ); - const is_file = fs.existsSync(file) && !fs.statSync(file).isDirectory(); - const allowed = - !vite_config.server.fs.strict || - vite_config.server.fs.allow.some((dir) => file.startsWith(dir)); - if (is_file && allowed) { - req.url = original_url; - // @ts-expect-error - serve_static_middleware.handle(req, res); - return; - } - - if (!decoded.startsWith(svelte_config.kit.paths.base)) { - return not_found(req, res, svelte_config.kit.paths.base); - } + const file = posixify( + path.resolve(root, decoded.slice(svelte_config.kit.paths.base.length + 1)) + ); + const is_file = fs.existsSync(file) && !fs.statSync(file).isDirectory(); + const allowed = + !vite_config.server.fs.strict || + vite_config.server.fs.allow.some((dir) => file.startsWith(dir)); + + if (is_file && allowed) { + req.url = original_url; + // @ts-expect-error + serve_static_middleware.handle(req, res); + return; + } - if (decoded === svelte_config.kit.paths.base + '/service-worker.js') { - const resolved = resolve_entry(svelte_config.kit.files.serviceWorker); + if (!decoded.startsWith(svelte_config.kit.paths.base)) { + return not_found(req, res, svelte_config.kit.paths.base); + } - if (resolved) { - res.writeHead(200, { - 'content-type': 'application/javascript' - }); - res.end(`import '${svelte_config.kit.paths.base}${to_fs(resolved)}';`); - } else { - res.writeHead(404); - res.end('not found'); - } + if (decoded === svelte_config.kit.paths.base + '/service-worker.js') { + const resolved = resolve_entry(svelte_config.kit.files.serviceWorker); - return; + if (resolved) { + res.writeHead(200, { + 'content-type': 'application/javascript' + }); + res.end(`import '${svelte_config.kit.paths.base}${to_fs(resolved)}';`); + } else { + res.writeHead(404); + res.end('not found'); } - const resolved_instrumentation = resolve_entry( - path.join(svelte_config.kit.files.src, 'instrumentation.server') - ); - - if (resolved_instrumentation) { - await vite.ssrLoadModule(resolved_instrumentation); - } + return; + } - // 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 - }) - ); + if (manifest_error) { + console.error(styleText(['bold', 'red'], manifest_error.message)); - const { set_assets } = await vite.ssrLoadModule('$app/paths/internal/server'); - set_assets(assets); + const error_page = load_error_page(svelte_config); - const server = new Server(manifest); + /** @param {{ status: number; message: string }} opts */ + const error_template = ({ status, message }) => { + return error_page + .replace(/%sveltekit\.status%/g, String(status)) + .replace(/%sveltekit\.error\.message%/g, escape_html(message)); + }; - await server.init({ - env, - read: (file) => createReadableStream(from_fs(file)) + res.writeHead(500, { + 'Content-Type': 'text/html; charset=utf-8' }); + res.end( + error_template({ status: 500, message: manifest_error.message ?? 'Invalid routes' }) + ); + return; + } + + // fallback to our own fetch handler if the adapter doesn't provide one + if ( + !svelte_config.kit.adapter?.vite?.plugins && + isFetchableDevEnvironment(vite.environments.ssr) + ) { const request = await getRequest({ base, request: req }); + const response = await vite.environments.ssr.dispatchFetch(request); - if (manifest_error) { - console.error(styleText(['bold', 'red'], manifest_error.message)); - - const error_page = load_error_page(svelte_config); - - /** @param {{ status: number; message: string }} opts */ - const error_template = ({ status, message }) => { - return error_page - .replace(/%sveltekit\.status%/g, String(status)) - .replace(/%sveltekit\.error\.message%/g, escape_html(message)); - }; - - res.writeHead(500, { - 'Content-Type': 'text/html; charset=utf-8' - }); - res.end( - error_template({ status: 500, message: manifest_error.message ?? 'Invalid routes' }) - ); - - 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)); - } - }); - - if (rendered.status === 404) { + if (response.status === 404) { // @ts-expect-error serve_static_middleware.handle(req, res, () => { - void setResponse(res, rendered); + void setResponse(res, response); }); } else { - void setResponse(res, rendered); + void setResponse(res, response); } - } catch (e) { - const error = coalesce_to_error(e); - res.statusCode = 500; - res.end(error); + return; } - }); - } + + next(); + } catch (e) { + const error = coalesce_to_error(e); + res.statusCode = 500; + res.end(error.stack); + } + }); }; } @@ -626,23 +365,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))); @@ -651,16 +387,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 @@ -694,3 +420,24 @@ export function invalidate_module(vite, id) { } } } + +/** @type {Map void, reject: (error: any) => void }>} */ +let requests; + +/** + * @overload + * @param {import('vite').ViteDevServer} dev + * @param {`remote-${string}`} id + * @returns {Promise>} + */ +/** + * @param {import('vite').ViteDevServer} dev + * @param {string} id + * @returns {Promise} + */ +export function request(dev, id) { + dev.environments.ssr.hot.send(`sveltekit:ipc/${id}`); + return new Promise((resolve, reject) => { + requests.set(id, { resolve, reject }); + }); +} diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 748abd367ee3..d6b3d9e23ad5 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -3,18 +3,32 @@ import path from 'node:path'; import process from 'node:process'; import { styleText } from 'node:util'; -import { exactRegex, prefixRegex } from 'rolldown/filter'; import * as devalue from 'devalue'; +import { exactRegex, prefixRegex } from 'rolldown/filter'; +import { + createFetchableDevEnvironment, + createServerHotChannel, + createServerModuleRunner, + loadEnv +} from 'vite'; -import { copy, mkdirp, posixify, read, resolve_entry, rimraf } from '../../utils/filesystem.js'; +import { + copy, + mkdirp, + posixify, + read, + resolve_entry, + rimraf, + to_fs +} from '../../utils/filesystem.js'; import { create_static_module, create_dynamic_module } from '../../core/env.js'; import * as sync from '../../core/sync/sync.js'; import { create_assets } from '../../core/sync/create_manifest_data/index.js'; -import { runtime_directory, logger, get_runtime_base } 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 { assets_base, find_deps, resolve_symlinks } from './build/utils.js'; -import { dev, invalidate_module } from './dev/index.js'; +import { dev, invalidate_module, request } from './dev/index.js'; import { preview } from './preview/index.js'; import { error_for_missing_config, @@ -38,7 +52,10 @@ import { env_static_public, service_worker, sveltekit_dev, + sveltekit_ipc, + sveltekit_remotes, sveltekit_server_assets, + sveltekit_server_entry, sveltekit_ssr_manifest } from './module_ids.js'; import { import_peer } from '../../utils/import.js'; @@ -163,8 +180,8 @@ export async function sveltekit(options = {}) { return [ plugin_svelte_config({ vite_plugin_svelte_options, svelte_config }), vite_plugin_svelte.svelte(vite_plugin_svelte_options), - kit({ svelte_config, vite_adapter: options.adapter }), - options.adapter + kit({ svelte_config, adapter_in_vite_config: !!options.adapter }), + options.adapter?.vite?.plugins ]; } @@ -245,11 +262,11 @@ function revive_functions(value) { * * @param {{ * svelte_config: import('types').ValidatedConfig; - * vite_adapter?: import('@sveltejs/kit').Adapter + * adapter_in_vite_config: boolean * }} options * @return {import('vite').PluginOption[]} */ -function kit({ svelte_config, vite_adapter }) { +function kit({ svelte_config, adapter_in_vite_config }) { /** @type {typeof import('vite')} */ let vite; @@ -303,6 +320,12 @@ function kit({ svelte_config, vite_adapter }) { const sourcemapIgnoreList = /** @param {string} relative_path */ (relative_path) => relative_path.includes('node_modules') || relative_path.includes(kit.outDir); + /** + * Only available if the adapter didn't provide its own environment + * @type {import('vite/module-runner').ModuleRunner | null} + */ + let runner = null; + /** @type {import('vite').Plugin} */ const plugin_setup = { name: 'vite-plugin-sveltekit-setup', @@ -324,10 +347,10 @@ function kit({ svelte_config, vite_adapter }) { ({ kit } = svelte_config); out = `${kit.outDir}/output`; - if (kit.adapter?.vitePlugins && !vite_adapter) { - // TODO: error if adapter vite plugins are not passed through the Vite config + if (kit.adapter?.vite?.plugins && !adapter_in_vite_config) { throw new Error( - `${kit.adapter.name} requires \`adapter.vitePlugins\` to be passed into the \`sveltekit\` Vite plugin in vite.config.js. See ...` + `${kit.adapter.name} requires the adapter to be passed through the \`sveltekit\` Vite plugin in the \`vite.config.js\` file. For example:\n\n` + + `+++import adapter from '${kit.adapter.name}';+++\n\nexport default defineConfig({\n plugins: [sveltekit( +++{ adapter }+++ )]\n});` ); } @@ -358,17 +381,11 @@ function kit({ svelte_config, vite_adapter }) { const client_hooks = resolve_entry(kit.files.hooks.client); if (client_hooks) allow.add(path.dirname(client_hooks)); - const generated = path.posix.join(kit.outDir, 'generated'); - // dev and preview config can be shared /** @type {import('vite').UserConfig} */ const new_config = { resolve: { alias: [ - { - find: '@sveltejs/kit/src/runtime/server/generated.js', - replacement: `${generated}/server.js` - }, { find: '$app', replacement: `${runtime_directory}/app` }, ...get_config_aliases(kit, root) ] @@ -438,6 +455,17 @@ function kit({ svelte_config, vite_adapter }) { // uses basic concatenation) '@sveltejs/kit/src/runtime' ] + }, + future: { + removePluginHookHandleHotUpdate: 'warn', + removePluginHookSsrArgument: 'warn', + removeServerHot: 'warn', + removeServerModuleGraph: 'warn', + removeServerPluginContainer: 'warn', + removeServerReloadModule: 'warn', + removeServerTransformRequest: 'warn', + removeServerWarmupRequest: 'warn', + removeSsrLoadModule: 'warn' } }; @@ -479,13 +507,37 @@ function kit({ svelte_config, vite_adapter }) { // @ts-ignore this prevents a reference error if `client.js` is imported on the server globalThis.__sveltekit_dev = {}; - new_config.environments = { - ssr: { - dev: { - // TODO: + if (!kit.adapter?.vite?.plugins) { + new_config.environments = { + ssr: { + dev: { + createEnvironment(name, config) { + return createFetchableDevEnvironment(name, config, { + hot: true, + transport: createServerHotChannel(), + async handleRequest(request) { + if (!runner) + throw new Error('The module runner should have been created by now'); + + try { + /** + * @type {{ + * respond: (request: Request, remote_address: string | undefined, kit: import('types').ValidatedKitConfig) => Promise + * }} + */ + const { respond } = await runner.import('__sveltekit/server-entry'); + return await respond(request, dev_environment?.remote_address, kit); + } catch (error) { + console.error(error); + throw error; + } + } + }); + } + } } - } - }; + }; + } } warn_overridden_config(config, new_config); @@ -561,9 +613,12 @@ function kit({ svelte_config, vite_adapter }) { exactRegex(env_dynamic_private), exactRegex(env_dynamic_public), exactRegex(service_worker), - exactRegex(sveltekit_server_assets), + exactRegex(sveltekit_server_entry), + exactRegex(sveltekit_dev), exactRegex(sveltekit_ssr_manifest), - exactRegex(sveltekit_dev) + exactRegex(sveltekit_server_assets), + exactRegex(sveltekit_remotes), + exactRegex(sveltekit_ipc) ] }, handler(id) { @@ -611,20 +666,35 @@ function kit({ svelte_config, vite_adapter }) { }; if (import.meta.hot) { - import.meta.hot.on('sveltekit:server-asset', (data) => { + import.meta.hot.on('sveltekit:server-assets', (data) => { Object.assign(server_assets, data); }); } `; } + case sveltekit_remotes: { + if (!dev_environment) return; + + return dedent` + export const remotes = ${s(remotes)}; + + if (import.meta.hot) { + import.meta.hot.on('sveltekit:remotes', (data) => { + remotes.push(data); + }); + } + `; + } + case sveltekit_ssr_manifest: { if (!dev_environment) return; - const { manifest, manifest_data, env } = dev_environment; + const { manifest_data, env } = dev_environment; return dedent` import { server_assets } from '__sveltekit/server-assets'; + import { remotes } from '__sveltekit/remotes'; import { resolve, loud_ssr_load_module } from '${get_runtime_base(root)}/../exports/vite/dev/fetchable.js'; export const base_path = ${s(kit.paths.base)}; @@ -632,83 +702,160 @@ function kit({ svelte_config, vite_adapter }) { export const env = ${s(env)}; export const manifest = { - appDir: ${s(manifest.appDir)}, - appPath: ${s(manifest.appPath)}, - assets: ${devalue.uneval(manifest.assets)}, - mimeTypes: ${s(manifest.mimeTypes)}, + appDir: ${s(kit.appDir)}, + appPath: ${s(kit.appDir)}, + assets: new Set(${s(manifest_data.assets.map((asset) => asset.file))}), + mimeTypes: ${s(get_mime_lookup(manifest_data))}, _: { - client: ${devalue.uneval(manifest._.client, revive_functions)}, + client: { + start: '${get_runtime_base(root)}/client/entry.js', + app: '${to_fs(kit.outDir)}/generated/client/app.js', + imports: [], + stylesheets: [], + fonts: [], + uses_env_dynamic_public: true, + nodes: + ${ + kit.router.resolution === 'client' + ? undefined + : manifest_data.nodes.map((node, i) => { + if (node.component || node.universal) { + return `${kit.paths.base}${to_fs(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: + ${ + kit.router.resolution === 'client' + ? undefined + : devalue.uneval( + 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, nodes: [${manifest_data.nodes .map((node, index) => { return dedent` - async () => { - const node = ${devalue.uneval(node, revive_functions)}; + async () => { + const node = ${devalue.uneval(node, revive_functions)}; - const result = {}; - result.index = ${index}; - result.universal_id = node.universal; - result.server_id = node.server; + 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 = []; + // these are unused in dev, but it's easier to include them + result.imports = []; + result.stylesheets = []; + result.fonts = []; - const module_nodes = []; + const module_nodes = []; - ${ - node.component - ? dedent` - result.component = async () => { - const { module_node, module } = await resolve(${s(path.resolve(root, node.component))}); + ${ + node.component + ? dedent` + result.component = async () => { + const { module_node, module } = await resolve(${s(path.resolve(root, node.component))}); - module_nodes.push(module_node); + module_nodes.push(module_node); - return module.default; - } - ` - : '' - } - - ${ - node.universal - ? dedent` - 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(${s(path.resolve(root, node.universal))}); - module_nodes.push(module_node); - result.universal = module; - } - ` - : '' - } - - ${ - node.server - ? dedent` - const { module } = await resolve(${s(path.resolve(root, node.server))}); - result.server = module; - ` - : '' - } + return module.default; + } + ` + : '' + } - // in dev we inline all styles to avoid FOUC. this gets populated lazily so that - // components/stylesheets loaded via import() during \`load\` are included + ${ + node.universal + ? dedent` + 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(${s(path.resolve(root, node.universal))}); + module_nodes.push(module_node); + result.universal = module; + } + ` + : '' + } - // TODO: result.inline_styles + ${ + node.server + ? dedent` + const { module } = await resolve(${s(path.resolve(root, node.server))}); + result.server = module; + ` + : '' + } - return result; - } - `; + // in dev we inline all styles to avoid FOUC. this gets populated lazily so that + // components/stylesheets loaded via import() during \`load\` are included + + // TODO: result.inline_styles + // 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 result; + } + `; }) .join(',\n')}], prerendered_routes: new Set(), get remotes() { - // TODO: fetchable remotes in dev - return {}; + return Object.fromEntries( + remotes.map((remote) => [ + remote.hash, + () => import(/* @vite-ignore */(\`${root}/\${remote.file}\`)).then((module) => ({ default: module })) + ]) + ); }, routes: [${compact( manifest_data.routes.map((route) => { @@ -717,28 +864,44 @@ function kit({ svelte_config, vite_adapter }) { const endpoint = route.endpoint; return dedent` - { - id: ${s(route.id)}, - pattern: ${devalue.uneval(route.pattern)}, - params: ${devalue.uneval(route.params)}, - page: ${devalue.uneval(route.page)}, - endpoint: ${ - endpoint - ? dedent` - async () => { - const url = ${s(path.resolve(root, endpoint.file))}; - return await loud_ssr_load_module(url); - } - ` - : null - }, - endpoint_id: ${s(endpoint?.file)} - } - `; + { + id: ${s(route.id)}, + pattern: ${devalue.uneval(route.pattern)}, + params: ${devalue.uneval(route.params)}, + page: ${devalue.uneval(route.page)}, + endpoint: ${ + endpoint + ? dedent` + async () => { + const url = ${s(path.resolve(root, endpoint.file))}; + return await loud_ssr_load_module(url); + } + ` + : null + }, + endpoint_id: ${s(endpoint?.file)} + } + `; }) ).join(',\n')}], matchers: async () => { // TODO: fetchable param matchers in dev + // /** @type {Record} */ + // const matchers = {}; + + // for (const key in manifest_data.matchers) { + // const file = manifest_data.matchers[key]; + // const url = path.resolve(root, file); + // const module = await vite.ssrLoadModule(url, { fixStacktrace: true }); + + // if (module.match) { + // matchers[key] = module.match; + // } else { + // throw new Error(\`\${file} does not export a \\\`match\\\` function\`); + // } + // } + + // return matchers; return {}; } } @@ -747,16 +910,24 @@ function kit({ svelte_config, vite_adapter }) { } case sveltekit_dev: { - if (vite_config_env.command === 'build') return; + if (!dev_environment) return; const runtime_base = get_runtime_base(root); const adapter = svelte_config.kit.adapter; + const resolved_instrumentation = resolve_entry( + path.join(svelte_config.kit.files.src, 'instrumentation.server') + ); + + // TODO: if instrumentation exists, import it first + return dedent` import { AsyncLocalStorage } from 'node:async_hooks'; + import { set_assets } from '${runtime_base}/app/paths/internal/server.js'; + // TODO: do we need to import Server before set_assets? see https://github.com/sveltejs/kit/commit/26d1958b9ee08541d719b77c6853a56142808ebc#diff-24f4a64dcf3021ab46c64bd6eddd314d4c630bde4557cc2e714f9c1f8b57ad06R441 + import { Server as InternalServer } from '${runtime_base}/server/index.js'; import { check_feature } from '${runtime_base}/../utils/features.js'; import { SCHEME } from '${runtime_base}/../utils/url.js'; - import { Server as InternalServer } from '${runtime_base}/server/index.js'; const async_local_storage = new AsyncLocalStorage(); @@ -780,10 +951,12 @@ function kit({ svelte_config, vite_adapter }) { return fetch(info, init); }; + set_assets(${s(dev_environment.assets)}); + export class Server extends InternalServer { async respond(request, options) { options.before_handle = async (event, config, prerender, handle) => { - // als.enterWith() is not supported in Cloudflare Workers + // 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); }; @@ -792,6 +965,68 @@ function kit({ svelte_config, vite_adapter }) { } `; } + + case sveltekit_server_entry: { + return dedent` + import fs from 'node:fs'; + import path from 'node:path'; + + import { Server } from '__sveltekit/dev'; + import { env, manifest } from '__sveltekit/ssr-manifest'; + import { createReadableStream } from '@sveltejs/kit/node'; + import { from_fs } from '${get_runtime_base(root)}/../exports/vite/dev/fetchable.js'; + + const server = new Server(manifest); + + await server.init({ + env, + read: (file) => createReadableStream(from_fs(file)) + }); + + /** + * + * @param {Request} request + * @param {string | undefined} remote_address + * @param {import('types').ValidatedKitConfig} kit + * @returns + */ + export async function respond(request, remote_address, kit) { + 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(kit.files.assets, file)); + } + }); + } + + import.meta.hot?.accept(); + `; + } + + case sveltekit_ipc: { + if (!dev_environment) return; + + return dedent` + export function handle(id, data) { + if (import.meta.hot) { + import.meta.hot.on('sveltekit:ipc/' + id, () => { + fetch('${dev_environment.vite.resolvedUrls?.local[0]}_app/ipc/' + id, { + method: 'PUT', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify(data) + }); + }); + } + } + `; + } } } } @@ -826,7 +1061,12 @@ function kit({ svelte_config, vite_adapter }) { // ]), async handler(id, importer, options) { if (importer && !importer.endsWith('index.html')) { - const resolved = await this.resolve(id, importer, { ...options, skipSelf: true }); + const resolved = await this.resolve(id, importer, { + custom: options.custom, + isEntry: options.isEntry, + kind: options.kind, + skipSelf: true + }); if (resolved) { const normalized = normalize_id(resolved.id, normalized_lib, normalized_cwd); @@ -936,9 +1176,6 @@ function kit({ svelte_config, vite_adapter }) { } }; - /** @type {import('vite').ViteDevServer} */ - let dev_server; - /** @type {Array<{ hash: string, file: string }>} */ const remotes = []; @@ -986,14 +1223,6 @@ function kit({ svelte_config, vite_adapter }) { } }, - configureServer(_dev_server) { - if (!kit.experimental.remoteFunctions) { - return; - } - - dev_server = _dev_server; - }, - async transform(code, id) { if (!kit.experimental.remoteFunctions) { return; @@ -1010,12 +1239,10 @@ function kit({ svelte_config, vite_adapter }) { file }; - remotes.push(remote); - 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 + // a ssrLoadModule 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 @@ -1026,7 +1253,7 @@ function kit({ svelte_config, vite_adapter }) { import * as $$_self_$$ from './${path.basename(id)}'; import { init_remote_functions as $$_init_$$ } from '@sveltejs/kit/internal'; - ${dev_server ? 'await Promise.resolve()' : ''} + ${dev_environment?.vite ? 'await Promise.resolve()' : ''} $$_init_$$($$_self_$$, ${s(file)}, ${s(remote.hash)}); @@ -1034,10 +1261,25 @@ function kit({ svelte_config, vite_adapter }) { fn.__.id = ${s(remote.hash)} + '/' + name; fn.__.name = name; } + ${ + dev_environment?.vite + ? dedent` + import { handle } from '__sveltekit/ipc'; + + handle('remote-${remote.hash}', (() => { + const exports = new Map(); + for (const name in $$_self_$$) { + exports.set(name, { type: $$_self_$$[name].__.type }); + } + return Object.fromEntries(exports); + })()); + ` + : '' + } `; // Emit a dedicated entry chunk for this remote in SSR builds (prod only) - if (!dev_server) { + if (!dev_environment?.vite) { remote_original_by_hash.set(remote.hash, id); if (!emitted_remote_hashes.has(remote.hash)) { this.emitFile({ @@ -1060,11 +1302,17 @@ function kit({ svelte_config, vite_adapter }) { // in dev, load the server module here (which will result in this hook // 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 (dev_environment?.vite) { + remotes.push(remote); + dev_environment.vite.environments.ssr.hot.send(`sveltekit:remotes`, remote); + invalidate_module(dev_environment.vite, sveltekit_remotes); + + await dev_environment.vite.environments.ssr.transformRequest(id); + + const exports = await request(dev_environment.vite, `remote-${remote.hash}`); - for (const [name, value] of Object.entries(module)) { - const type = value?.__?.type; + for (const [name, value] of Object.entries(exports)) { + const type = value.type; if (type) { map.set(name, type); } @@ -1092,7 +1340,7 @@ function kit({ svelte_config, vite_adapter }) { let result = `import * as ${namespace} from '__sveltekit/remote';\n\n${exports.join('\n')}\n`; - if (dev_server) { + if (dev_environment?.vite) { result += `\nimport.meta.hot?.accept();\n`; } @@ -1107,7 +1355,7 @@ function kit({ svelte_config, vite_adapter }) { /** @type {import('types').Prerendered} */ let prerendered; - /** @type {Set} */ + /** @type {Set} client output and static files */ let build; /** @type {string} */ let service_worker_code; @@ -1353,7 +1601,7 @@ function kit({ svelte_config, vite_adapter }) { } } }, - // during the initial server build we don't know yet + // these are stubs that will be replaced after the initial server build define: { __SVELTEKIT_HAS_SERVER_LOAD__: 'true', __SVELTEKIT_HAS_UNIVERSAL_LOAD__: 'true', @@ -1473,11 +1721,17 @@ function kit({ svelte_config, vite_adapter }) { * @see https://vitejs.dev/guide/api-plugin.html#configureserver */ configureServer(vite) { - // the other properties will be populated in the `dev` function + // other properties will be populated during the `dev` function dev_environment = /** @type {import('types').DevEnvironment} */ ({ - vite + vite, + env: loadEnv(vite_config.mode, svelte_config.kit.env.dir, '') }); - return dev(vite, vite_config, svelte_config, () => remotes, root, dev_environment); + + if (!kit.adapter?.vite?.plugins) { + runner = createServerModuleRunner(vite.environments.ssr); + } + + return dev(vite, vite_config, svelte_config, root, dev_environment); }, /** @@ -1851,7 +2105,7 @@ function kit({ svelte_config, vite_adapter }) { const size = fs.statSync(pathname).size; // update it immediately - dev_environment.vite.environments.ssr.hot.send('sveltekit:server-asset', { + dev_environment.vite.environments.ssr.hot.send(`sveltekit:server-assets`, { [filepath]: size }); @@ -1863,8 +2117,26 @@ function kit({ svelte_config, vite_adapter }) { } }; + /** @type {import('vite').Plugin} */ + const plugin_generated = { + name: 'vite-plugin-sveltekit-resolve-generated', + resolveId: { + order: 'pre', + filter: { + id: /\/generated\.js$/ + }, + handler(_, importer) { + const generated = path.posix.join(kit.outDir, 'generated'); + if (importer?.startsWith(runtime_directory)) { + return `${generated}/server/internal.js`; + } + } + } + }; + return [ plugin_setup, + plugin_generated, plugin_remote, plugin_server_filesystem, plugin_virtual_modules, diff --git a/packages/kit/src/exports/vite/module_ids.js b/packages/kit/src/exports/vite/module_ids.js index 86b64f659878..b0df0f348ede 100644 --- a/packages/kit/src/exports/vite/module_ids.js +++ b/packages/kit/src/exports/vite/module_ids.js @@ -8,11 +8,12 @@ export const env_dynamic_public = '\0virtual:env/dynamic/public'; export const service_worker = '\0virtual:service-worker'; +export const sveltekit_server_entry = '\0virtual:__sveltekit/server-entry'; export const sveltekit_dev = '\0virtual:__sveltekit/dev'; - export const sveltekit_ssr_manifest = '\0virtual:__sveltekit/ssr-manifest'; - export const sveltekit_server_assets = '\0virtual:__sveltekit/server-assets'; +export const sveltekit_remotes = '\0virtual:__sveltekit/remotes'; +export const sveltekit_ipc = '\0virtual:__sveltekit/ipc'; export const app_server = posixify( fileURLToPath(new URL('../../runtime/app/server/index.js', import.meta.url)) diff --git a/packages/kit/src/runtime/server/generated.js b/packages/kit/src/runtime/server/generated.js index 8d07cff00c77..14c65ac953eb 100644 --- a/packages/kit/src/runtime/server/generated.js +++ b/packages/kit/src/runtime/server/generated.js @@ -1,4 +1,4 @@ -// This module is a stub and will be overridden once Vite takes over module loading +// This module is a stub and will not be used once Vite takes over module loading import { set_building, set_prerendering } from '../app/environment/internal.js'; import { set_assets } from '../app/paths/internal/server.js'; diff --git a/packages/kit/src/runtime/server/page/csp.spec.js b/packages/kit/src/runtime/server/page/csp.spec.js index 195a995e8f77..df8f0bf458ba 100644 --- a/packages/kit/src/runtime/server/page/csp.spec.js +++ b/packages/kit/src/runtime/server/page/csp.spec.js @@ -37,6 +37,7 @@ describe.skipIf(process.env.NODE_ENV === 'production')('CSPs in dev', () => { ); }); + // TODO: re-enable when we support strict-dynamic in dev again test.skip('removes strict-dynamic', () => { ['default-src', 'script-src'].forEach((name) => { const csp = new Csp( diff --git a/packages/kit/src/types/internal.d.ts b/packages/kit/src/types/internal.d.ts index 7f83cf7c17f4..e67a8986b13b 100644 --- a/packages/kit/src/types/internal.d.ts +++ b/packages/kit/src/types/internal.d.ts @@ -669,8 +669,9 @@ export interface RequestStore { export interface DevEnvironment { vite: ViteDevServer; manifest_data: ManifestData; - manifest: SSRManifest; env: Record; + assets: string; + remote_address: string | undefined; } export * from '../exports/index.js'; diff --git a/packages/kit/src/types/virtual.d.ts b/packages/kit/src/types/virtual.d.ts index 910262c9b8d4..d448c0f9f72b 100644 --- a/packages/kit/src/types/virtual.d.ts +++ b/packages/kit/src/types/virtual.d.ts @@ -10,5 +10,7 @@ declare module '__sveltekit/ssr-manifest' { } declare module '__sveltekit/dev' { - export { Server } from '@sveltejs/kit'; + import { InternalServer } from 'types'; + + export { InternalServer as Server }; } diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 027d41a37fbf..90869354c713 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -44,10 +44,12 @@ declare module '@sveltejs/kit' { * @deprecated removed in 3.0.0 */ emulate?: () => MaybePromise; - /** - * @since 3.0.0 - */ - vitePlugins?: PluginOption; + vite?: { + /** + * @since 3.0.0 + */ + plugins?: PluginOption; + }; } export type LoadProperties | void> = input extends void From a74f3c74db81b5e1b6f10fae71dc69ea0e29a52a Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Tue, 24 Mar 2026 18:59:28 +0800 Subject: [PATCH 012/117] im so stupid --- packages/kit/src/exports/vite/dev/fetchable.js | 4 +--- packages/kit/src/exports/vite/dev/index.js | 2 ++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/kit/src/exports/vite/dev/fetchable.js b/packages/kit/src/exports/vite/dev/fetchable.js index 1253b463c12e..7287f30e1842 100644 --- a/packages/kit/src/exports/vite/dev/fetchable.js +++ b/packages/kit/src/exports/vite/dev/fetchable.js @@ -1,5 +1,3 @@ -import { buildErrorMessage } from 'vite'; - // `posixify` and `to_fs` are duplicated from utils/filesystem.js to avoid // imports from `node:*` which aren't available in Cloudflare's workerd runtime @@ -56,7 +54,7 @@ export async function loud_ssr_load_module(url) { return await import(/* @vite-ignore */ url); } catch (/** @type {any} */ err) { // const msg = buildErrorMessage(err, [styleText('red', `Internal server error: ${err.message}`)]); - const msg = buildErrorMessage(err, [`Internal server error: ${err.message}`]); + const msg = `Internal server error: ${err.message}`; // if (!server.config.logger.hasErrorLogged(err)) { // server.config.logger.error(msg, { error: err }); diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index e7e18cbf8789..91af54e4dd07 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -424,6 +424,8 @@ export function invalidate_module(vite, id) { /** @type {Map void, reject: (error: any) => void }>} */ let requests; +// TODO: try using import.meta.hot.send from the module instead of fetch + /** * @overload * @param {import('vite').ViteDevServer} dev From 0a73d637664d38d72326c5603b30cc1450c81e0f Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Tue, 24 Mar 2026 21:02:14 +0800 Subject: [PATCH 013/117] move server entry to file --- packages/kit/src/exports/vite/dev/server.js | 39 ++++++++++++++++ packages/kit/src/exports/vite/index.js | 50 ++------------------- packages/kit/src/exports/vite/module_ids.js | 1 - 3 files changed, 42 insertions(+), 48 deletions(-) create mode 100644 packages/kit/src/exports/vite/dev/server.js 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..b325190e49ed --- /dev/null +++ b/packages/kit/src/exports/vite/dev/server.js @@ -0,0 +1,39 @@ +import fs from 'node:fs'; +import path from 'node:path'; + +import { Server } from '__sveltekit/dev'; +import { env, manifest } from '__sveltekit/ssr-manifest'; +import { createReadableStream } from '@sveltejs/kit/node'; +import { from_fs } from './fetchable.js'; + +const server = new Server(manifest); + +await server.init({ + env, + read: (file) => createReadableStream(from_fs(file)) +}); + +/** + * + * @param {Request} request + * @param {string | undefined} remote_address + * @param {import('types').ValidatedKitConfig} kit + * @returns + */ +export async function respond(request, remote_address, kit) { + 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(kit.files.assets, file)); + } + }); +} + +import.meta.hot?.accept(); diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 31bd4860cf36..27a5e5f6b066 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -55,7 +55,6 @@ import { sveltekit_ipc, sveltekit_remotes, sveltekit_server_assets, - sveltekit_server_entry, sveltekit_ssr_manifest } from './module_ids.js'; import { import_peer } from '../../utils/import.js'; @@ -524,7 +523,9 @@ function kit({ svelte_config, adapter_in_vite_config }) { * respond: (request: Request, remote_address: string | undefined, kit: import('types').ValidatedKitConfig) => Promise * }} */ - const { respond } = await runner.import('__sveltekit/server-entry'); + const { respond } = await runner.import( + import.meta.resolve('./dev/server.js') + ); return await respond(request, dev_environment?.remote_address, kit); } catch (error) { console.error(error); @@ -612,7 +613,6 @@ function kit({ svelte_config, adapter_in_vite_config }) { exactRegex(env_dynamic_private), exactRegex(env_dynamic_public), exactRegex(service_worker), - exactRegex(sveltekit_server_entry), exactRegex(sveltekit_dev), exactRegex(sveltekit_ssr_manifest), exactRegex(sveltekit_server_assets), @@ -965,50 +965,6 @@ function kit({ svelte_config, adapter_in_vite_config }) { `; } - case sveltekit_server_entry: { - return dedent` - import fs from 'node:fs'; - import path from 'node:path'; - - import { Server } from '__sveltekit/dev'; - import { env, manifest } from '__sveltekit/ssr-manifest'; - import { createReadableStream } from '@sveltejs/kit/node'; - import { from_fs } from '${get_runtime_base(root)}/../exports/vite/dev/fetchable.js'; - - const server = new Server(manifest); - - await server.init({ - env, - read: (file) => createReadableStream(from_fs(file)) - }); - - /** - * - * @param {Request} request - * @param {string | undefined} remote_address - * @param {import('types').ValidatedKitConfig} kit - * @returns - */ - export async function respond(request, remote_address, kit) { - 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(kit.files.assets, file)); - } - }); - } - - import.meta.hot?.accept(); - `; - } - case sveltekit_ipc: { if (!dev_environment) return; diff --git a/packages/kit/src/exports/vite/module_ids.js b/packages/kit/src/exports/vite/module_ids.js index b0df0f348ede..045e80dad55a 100644 --- a/packages/kit/src/exports/vite/module_ids.js +++ b/packages/kit/src/exports/vite/module_ids.js @@ -8,7 +8,6 @@ export const env_dynamic_public = '\0virtual:env/dynamic/public'; export const service_worker = '\0virtual:service-worker'; -export const sveltekit_server_entry = '\0virtual:__sveltekit/server-entry'; export const sveltekit_dev = '\0virtual:__sveltekit/dev'; export const sveltekit_ssr_manifest = '\0virtual:__sveltekit/ssr-manifest'; export const sveltekit_server_assets = '\0virtual:__sveltekit/server-assets'; From 9f0694ab81f97bd293bf835470c90b367a783083 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Tue, 24 Mar 2026 21:02:28 +0800 Subject: [PATCH 014/117] only one config path as source of truth --- packages/adapter-cloudflare/index.d.ts | 66 ++++--------------- packages/adapter-cloudflare/index.js | 4 +- .../test/apps/workers/package.json | 2 +- .../test/apps/workers/svelte.config.js | 2 +- packages/adapter-cloudflare/test/utils.js | 2 +- 5 files changed, 16 insertions(+), 60 deletions(-) diff --git a/packages/adapter-cloudflare/index.d.ts b/packages/adapter-cloudflare/index.d.ts index 940302b92d19..62b78aac323b 100644 --- a/packages/adapter-cloudflare/index.d.ts +++ b/packages/adapter-cloudflare/index.d.ts @@ -1,83 +1,39 @@ import { PluginConfig } from '@cloudflare/vite-plugin'; import { Adapter } from '@sveltejs/kit'; -import './ambient.js'; import { GetPlatformProxyOptions } from 'wrangler'; +import './ambient.js'; export default function plugin(options?: AdapterOptions): Adapter; export interface AdapterOptions { /** - * Path to your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). + * Options to pass to the Cloudflare Vite plugin. + * @see https://developers.cloudflare.com/workers/vite-plugin/reference/api/#interface-pluginconfig */ - config?: string; + vitePluginOptions?: PluginConfig; /** * Whether to render a plaintext 404.html page or a rendered SPA fallback page * for non-matching asset requests. * - * For Cloudflare Workers, the default behaviour is to return a null-body + * The default behaviour is to return a null-body * 404-status response for non-matching assets requests. However, if the * [`assets.not_found_handling`](https://developers.cloudflare.com/workers/static-assets/routing/#2-not_found_handling) * Wrangler configuration setting is set to `"404-page"`, this page will be * served if a request fails to match an asset. If `assets.not_found_handling` * is set to `"single-page-application"`, the adapter will render a SPA fallback * `index.html` page regardless of the `fallback` option specified. - * - * For Cloudflare Pages, this page will only be served when a request that - * matches an entry in `routes.exclude` fails to match an asset. - * - * Most of the time `plaintext` is sufficient, but if you are using `routes.exclude` to manually - * exclude a set of prerendered pages without exceeding the 100 route limit, you may wish to - * use `spa` instead to avoid showing an unstyled 404 page to users. - * - * See [Cloudflare Pages' Not Found behavior](https://developers.cloudflare.com/pages/configuration/serving-pages/#not-found-behavior) for more info. - * * @default 'plaintext' */ fallback?: 'plaintext' | 'spa'; - - /** - * Only for Cloudflare Pages. Customize the automatically-generated [`_routes.json`](https://developers.cloudflare.com/pages/platform/functions/routing/#create-a-_routesjson-file) file. - */ - routes?: { - /** - * Routes that will be invoked by functions. Accepts wildcards. - * @default ["/*"] - */ - include?: string[]; - - /** - * Routes that will not be invoked by functions. Accepts wildcards. - * `exclude` takes priority over `include`. - * - * To have the adapter automatically exclude certain things, you can use these placeholders: - * - * - `` to exclude build artifacts (files generated by Vite) - * - `` for the contents of your `static` directory - * - `` for prerendered routes - * - `` to exclude all of the above - * - * @default [""] - */ - exclude?: string[]; - }; - /** * Config object passed to [`getPlatformProxy`](https://developers.cloudflare.com/workers/wrangler/api/#getplatformproxy) * during development and preview. - * @deprecated removed in 8.0.0 + * @deprecated removed in 8.0.0. Use `vitePluginOptions` instead */ platformProxy?: GetPlatformProxyOptions; - - vitePluginOptions?: PluginConfig; -} - -/** - * The JSON format of the {@link https://developers.cloudflare.com/pages/functions/routing/#create-a-_routesjson-file | `_routes.json`} - * file that controls when the Cloudflare Pages Function is invoked. - */ -export interface RoutesJSONSpec { - version: 1; - description: string; - include: string[]; - exclude: string[]; + /** + * Path to your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). + * @deprecated removed in 8.0.0. Use `vitePluginOptions.configPath` instead + */ + config?: string; } diff --git a/packages/adapter-cloudflare/index.js b/packages/adapter-cloudflare/index.js index 19882400e6ee..37385b6ba647 100644 --- a/packages/adapter-cloudflare/index.js +++ b/packages/adapter-cloudflare/index.js @@ -11,7 +11,7 @@ const name = '@sveltejs/adapter-cloudflare'; /** @type {import('./index.js').default} */ export default function (options = {}) { - const { wrangler_config } = validate_wrangler_config(options.config); + const { wrangler_config } = validate_wrangler_config(options.vitePluginOptions?.configPath); return { name, @@ -142,7 +142,7 @@ export default function (options = {}) { plugins: [ cloudflare({ ...options.vitePluginOptions, - configPath: options.vitePluginOptions?.configPath ?? options.config, + configPath: options.vitePluginOptions?.configPath, viteEnvironment: { name: options.vitePluginOptions?.viteEnvironment?.name ?? 'ssr', childEnvironments: options.vitePluginOptions?.viteEnvironment?.childEnvironments diff --git a/packages/adapter-cloudflare/test/apps/workers/package.json b/packages/adapter-cloudflare/test/apps/workers/package.json index 3790a9b3a242..46d61300d85d 100644 --- a/packages/adapter-cloudflare/test/apps/workers/package.json +++ b/packages/adapter-cloudflare/test/apps/workers/package.json @@ -5,7 +5,7 @@ "scripts": { "dev": "vite dev", "build": "vite build", - "preview": "vite preview -p 8787", + "preview": "vite preview", "prepare": "svelte-kit sync || echo ''", "test:dev": "DEV=true playwright test", "test:build": "playwright test", diff --git a/packages/adapter-cloudflare/test/apps/workers/svelte.config.js b/packages/adapter-cloudflare/test/apps/workers/svelte.config.js index 26cd6a965908..7af75d9f0e0b 100644 --- a/packages/adapter-cloudflare/test/apps/workers/svelte.config.js +++ b/packages/adapter-cloudflare/test/apps/workers/svelte.config.js @@ -4,7 +4,7 @@ import adapter from '../../../index.js'; const config = { kit: { adapter: adapter({ - config: 'config/wrangler.jsonc' + config: './config/wrangler.jsonc' }) } }; diff --git a/packages/adapter-cloudflare/test/utils.js b/packages/adapter-cloudflare/test/utils.js index dcb3ce52c9d6..4ee245ff335d 100644 --- a/packages/adapter-cloudflare/test/utils.js +++ b/packages/adapter-cloudflare/test/utils.js @@ -9,7 +9,7 @@ export const config = { timeout: process.env.CI ? 45000 : 15000, webServer: { command: process.env.DEV ? 'pnpm dev' : 'pnpm build && pnpm preview', - port: process.env.DEV ? 5173 : 8787 + port: process.env.DEV ? 5173 : 4173 }, retries: process.env.CI ? 2 : number_from_env('KIT_E2E_RETRIES', 0), projects: [ From 585fc7a29d727b0555440e0bf6ec7205669fb0b6 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Tue, 24 Mar 2026 21:16:15 +0800 Subject: [PATCH 015/117] deduplicate to_fs --- packages/kit/src/core/adapt/builder.js | 3 +- packages/kit/src/core/adapt/builder.spec.js | 2 +- packages/kit/src/core/postbuild/prerender.js | 3 +- .../core/sync/create_manifest_data/index.js | 3 +- .../kit/src/core/sync/write_non_ambient.js | 2 +- packages/kit/src/core/sync/write_server.js | 3 +- packages/kit/src/core/sync/write_tsconfig.js | 2 +- .../kit/src/core/sync/write_types/index.js | 3 +- packages/kit/src/core/utils.js | 3 +- packages/kit/src/exports/vite/dev/index.js | 4 ++- packages/kit/src/exports/vite/dev/server.js | 2 +- .../src/exports/vite/{dev => }/fetchable.js | 11 +++---- packages/kit/src/exports/vite/index.js | 14 +++------ packages/kit/src/exports/vite/module_ids.js | 2 +- packages/kit/src/exports/vite/utils.js | 2 +- packages/kit/src/exports/vite/utils.spec.js | 2 +- packages/kit/src/utils/filesystem.js | 31 +------------------ packages/kit/src/utils/os.js | 4 +++ 18 files changed, 35 insertions(+), 61 deletions(-) rename packages/kit/src/exports/vite/{dev => }/fetchable.js (89%) create mode 100644 packages/kit/src/utils/os.js diff --git a/packages/kit/src/core/adapt/builder.js b/packages/kit/src/core/adapt/builder.js index bb5ef66edea7..781f12f9ca53 100644 --- a/packages/kit/src/core/adapt/builder.js +++ b/packages/kit/src/core/adapt/builder.js @@ -7,7 +7,7 @@ import { extname, resolve, join, dirname, relative } from 'node:path'; import { pipeline } from 'node:stream'; import { promisify, styleText } from 'node:util'; import zlib from 'node:zlib'; -import { copy, rimraf, mkdirp, posixify } from '../../utils/filesystem.js'; +import { copy, rimraf, mkdirp } from '../../utils/filesystem.js'; import { generate_manifest } from '../generate_manifest/index.js'; import { get_route_segments } from '../../utils/routing.js'; import { get_env } from '../../exports/vite/utils.js'; @@ -16,6 +16,7 @@ import { write } from '../sync/utils.js'; import { list_files } from '../utils.js'; import { find_server_assets } from '../generate_manifest/find_server_assets.js'; import { reserved } from '../env.js'; +import { posixify } from '../../utils/os.js'; const pipe = promisify(pipeline); const extensions = ['.html', '.js', '.mjs', '.json', '.css', '.svg', '.xml', '.wasm', '.txt']; diff --git a/packages/kit/src/core/adapt/builder.spec.js b/packages/kit/src/core/adapt/builder.spec.js index 304a1ba5987d..2e255854ff68 100644 --- a/packages/kit/src/core/adapt/builder.spec.js +++ b/packages/kit/src/core/adapt/builder.spec.js @@ -3,7 +3,7 @@ import { join, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; import { assert, expect, test } from 'vitest'; import { create_builder } from './builder.js'; -import { posixify } from '../../utils/filesystem.js'; +import { posixify } from '../../utils/os.js'; import { list_files } from '../utils.js'; test('copy files', () => { diff --git a/packages/kit/src/core/postbuild/prerender.js b/packages/kit/src/core/postbuild/prerender.js index dbb8442e3bc4..a4b654459fa3 100644 --- a/packages/kit/src/core/postbuild/prerender.js +++ b/packages/kit/src/core/postbuild/prerender.js @@ -1,7 +1,7 @@ import { existsSync, readFileSync, statSync, writeFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { pathToFileURL } from 'node:url'; -import { mkdirp, posixify, walk } from '../../utils/filesystem.js'; +import { mkdirp, walk } from '../../utils/filesystem.js'; import { decode_uri, is_root_relative, resolve } from '../../utils/url.js'; import { escape_html } from '../../utils/escape.js'; import { logger } from '../utils.js'; @@ -15,6 +15,7 @@ import { createReadableStream } from '@sveltejs/kit/node'; import generate_fallback from './fallback.js'; import { stringify_remote_arg } from '../../runtime/shared.js'; import { filter_env } from '../../utils/env.js'; +import { posixify } from '../../utils/os.js'; export default forked(import.meta.url, prerender); diff --git a/packages/kit/src/core/sync/create_manifest_data/index.js b/packages/kit/src/core/sync/create_manifest_data/index.js index d7ffe012d6bd..0d28dcaf4d52 100644 --- a/packages/kit/src/core/sync/create_manifest_data/index.js +++ b/packages/kit/src/core/sync/create_manifest_data/index.js @@ -2,7 +2,8 @@ import { lookup } from 'mrmime'; import fs from 'node:fs'; import path from 'node:path'; import { styleText } from 'node:util'; -import { posixify, resolve_entry } from '../../../utils/filesystem.js'; +import { resolve_entry } from '../../../utils/filesystem.js'; +import { posixify } from '../../../utils/os.js'; import { parse_route_id } from '../../../utils/routing.js'; import { list_files, runtime_directory } from '../../utils.js'; import { sort_routes } from './sort.js'; diff --git a/packages/kit/src/core/sync/write_non_ambient.js b/packages/kit/src/core/sync/write_non_ambient.js index 3b92b73dbc1b..9d945461d8a9 100644 --- a/packages/kit/src/core/sync/write_non_ambient.js +++ b/packages/kit/src/core/sync/write_non_ambient.js @@ -1,6 +1,6 @@ import path from 'node:path'; import { GENERATED_COMMENT } from '../../constants.js'; -import { posixify } from '../../utils/filesystem.js'; +import { posixify } from '../../utils/os.js'; import { write_if_changed } from './utils.js'; import { s } from '../../utils/misc.js'; import { get_route_segments } from '../../utils/routing.js'; diff --git a/packages/kit/src/core/sync/write_server.js b/packages/kit/src/core/sync/write_server.js index 8b5e69e21408..fde234092037 100644 --- a/packages/kit/src/core/sync/write_server.js +++ b/packages/kit/src/core/sync/write_server.js @@ -1,12 +1,13 @@ import path from 'node:path'; import { styleText } from 'node:util'; import { hash } from '../../utils/hash.js'; -import { posixify, resolve_entry } from '../../utils/filesystem.js'; +import { resolve_entry } from '../../utils/filesystem.js'; import { s } from '../../utils/misc.js'; import { load_error_page, load_template } from '../config/index.js'; import { runtime_directory } from '../utils.js'; import { write_if_changed } from './utils.js'; import { escape_html } from '../../utils/escape.js'; +import { posixify } from '../../utils/os.js'; /** * @param {{ diff --git a/packages/kit/src/core/sync/write_tsconfig.js b/packages/kit/src/core/sync/write_tsconfig.js index 9c038bde2b25..9ac3f4b905b2 100644 --- a/packages/kit/src/core/sync/write_tsconfig.js +++ b/packages/kit/src/core/sync/write_tsconfig.js @@ -1,7 +1,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { styleText } from 'node:util'; -import { posixify } from '../../utils/filesystem.js'; +import { posixify } from '../../utils/os.js'; import { write_if_changed } from './utils.js'; /** diff --git a/packages/kit/src/core/sync/write_types/index.js b/packages/kit/src/core/sync/write_types/index.js index 3ecd36c23c7c..21fd68d79d52 100644 --- a/packages/kit/src/core/sync/write_types/index.js +++ b/packages/kit/src/core/sync/write_types/index.js @@ -1,8 +1,9 @@ import fs from 'node:fs'; import path from 'node:path'; import MagicString from 'magic-string'; -import { posixify, rimraf, walk } from '../../../utils/filesystem.js'; +import { rimraf, walk } from '../../../utils/filesystem.js'; import { compact } from '../../../utils/array.js'; +import { posixify } from '../../../utils/os.js'; import { ts } from '../ts.js'; const remove_relative_parent_traversals = (/** @type {string} */ path) => path.replace(/\.\.\//g, ''); diff --git a/packages/kit/src/core/utils.js b/packages/kit/src/core/utils.js index 73ded00b82fb..97f8eb8ecca1 100644 --- a/packages/kit/src/core/utils.js +++ b/packages/kit/src/core/utils.js @@ -2,7 +2,8 @@ import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { styleText } from 'node:util'; -import { posixify, to_fs } from '../utils/filesystem.js'; +import { posixify } from '../utils/os.js'; +import { to_fs } from '../exports/vite/fetchable.js'; /** * Resolved path of the `runtime` directory diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 91af54e4dd07..425f7735b160 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -8,13 +8,15 @@ import { isCSSRequest, isFetchableDevEnvironment } from 'vite'; import { getRequest, setResponse } from '@sveltejs/kit/node'; import { coalesce_to_error } from '../../../utils/error.js'; -import { posixify, resolve_entry, to_fs } from '../../../utils/filesystem.js'; +import { resolve_entry } from '../../../utils/filesystem.js'; +import { posixify } from '../../../utils/os.js'; import { load_error_page } from '../../../core/config/index.js'; import { SVELTE_KIT_ASSETS } from '../../../constants.js'; import * as sync from '../../../core/sync/sync.js'; import { not_found } from '../utils.js'; import { escape_html } from '../../../utils/escape.js'; import { sveltekit_ssr_manifest } from '../module_ids.js'; +import { to_fs } from '../fetchable.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/server.js b/packages/kit/src/exports/vite/dev/server.js index b325190e49ed..6722528a8acd 100644 --- a/packages/kit/src/exports/vite/dev/server.js +++ b/packages/kit/src/exports/vite/dev/server.js @@ -4,7 +4,7 @@ import path from 'node:path'; import { Server } from '__sveltekit/dev'; import { env, manifest } from '__sveltekit/ssr-manifest'; import { createReadableStream } from '@sveltejs/kit/node'; -import { from_fs } from './fetchable.js'; +import { from_fs } from '../fetchable.js'; const server = new Server(manifest); diff --git a/packages/kit/src/exports/vite/dev/fetchable.js b/packages/kit/src/exports/vite/fetchable.js similarity index 89% rename from packages/kit/src/exports/vite/dev/fetchable.js rename to packages/kit/src/exports/vite/fetchable.js index 7287f30e1842..b559629feff2 100644 --- a/packages/kit/src/exports/vite/dev/fetchable.js +++ b/packages/kit/src/exports/vite/fetchable.js @@ -1,16 +1,13 @@ -// `posixify` and `to_fs` are duplicated from utils/filesystem.js to avoid -// imports from `node:*` which aren't available in Cloudflare's workerd runtime +// this file needs to be runtime agnostic and avoid importing from `node:*` since +// it may not be available in edge environments -/** @param {string} str */ -function posixify(str) { - return str.replace(/\\/g, '/'); -} +import { posixify } from '../../utils/os.js'; /** * Prepend given path with `/@fs` prefix * @param {string} str */ -function to_fs(str) { +export function to_fs(str) { str = posixify(str); return `/@fs${ // Windows/Linux separation - Windows starts with a drive letter, we need a / in front there diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 27a5e5f6b066..a3e1bd797222 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -12,15 +12,7 @@ import { loadEnv } from 'vite'; -import { - copy, - mkdirp, - posixify, - read, - resolve_entry, - rimraf, - to_fs -} from '../../utils/filesystem.js'; +import { copy, mkdirp, read, resolve_entry, rimraf } from '../../utils/filesystem.js'; import { create_static_module, create_dynamic_module } from '../../core/env.js'; import * as sync from '../../core/sync/sync.js'; import { create_assets } from '../../core/sync/create_manifest_data/index.js'; @@ -61,6 +53,8 @@ import { import_peer } from '../../utils/import.js'; import { compact } from '../../utils/array.js'; import { should_ignore, has_children } from './static_analysis/utils.js'; import { load_config } from '../../core/config/index.js'; +import { to_fs } from './fetchable.js'; +import { posixify } from '../../utils/os.js'; const cwd = process.cwd(); @@ -694,7 +688,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { return dedent` import { server_assets } from '__sveltekit/server-assets'; import { remotes } from '__sveltekit/remotes'; - import { resolve, loud_ssr_load_module } from '${get_runtime_base(root)}/../exports/vite/dev/fetchable.js'; + import { resolve, loud_ssr_load_module } from '${get_runtime_base(root)}/../exports/vite/fetchable.js'; export const base_path = ${s(kit.paths.base)}; export const prerendered = new Set(); diff --git a/packages/kit/src/exports/vite/module_ids.js b/packages/kit/src/exports/vite/module_ids.js index 045e80dad55a..ccf30f06d634 100644 --- a/packages/kit/src/exports/vite/module_ids.js +++ b/packages/kit/src/exports/vite/module_ids.js @@ -1,5 +1,5 @@ import { fileURLToPath } from 'node:url'; -import { posixify } from '../../utils/filesystem.js'; +import { posixify } from '../../utils/os.js'; export const env_static_private = '\0virtual:env/static/private'; export const env_static_public = '\0virtual:env/static/public'; diff --git a/packages/kit/src/exports/vite/utils.js b/packages/kit/src/exports/vite/utils.js index 117ce6575c15..c866da4f9d3a 100644 --- a/packages/kit/src/exports/vite/utils.js +++ b/packages/kit/src/exports/vite/utils.js @@ -1,6 +1,6 @@ import path from 'node:path'; import { loadEnv } from 'vite'; -import { posixify } from '../../utils/filesystem.js'; +import { posixify } from '../../utils/os.js'; import { negotiate } from '../../utils/http.js'; import { filter_env } from '../../utils/env.js'; import { escape_html } from '../../utils/escape.js'; diff --git a/packages/kit/src/exports/vite/utils.spec.js b/packages/kit/src/exports/vite/utils.spec.js index 7878487573fe..8512ecc081ae 100644 --- a/packages/kit/src/exports/vite/utils.spec.js +++ b/packages/kit/src/exports/vite/utils.spec.js @@ -1,7 +1,7 @@ import path from 'node:path'; import { expect, test } from 'vitest'; import { validate_config } from '../../core/config/index.js'; -import { posixify } from '../../utils/filesystem.js'; +import { posixify } from '../../utils/os.js'; import { dedent } from '../../core/sync/utils.js'; import { get_config_aliases, error_for_missing_config } from './utils.js'; diff --git a/packages/kit/src/utils/filesystem.js b/packages/kit/src/utils/filesystem.js index 07c04ba32f7b..1173086bd112 100644 --- a/packages/kit/src/utils/filesystem.js +++ b/packages/kit/src/utils/filesystem.js @@ -1,5 +1,6 @@ import fs from 'node:fs'; import path from 'node:path'; +import { posixify } from './os.js'; /** @param {string} dir */ export function mkdirp(dir) { @@ -110,11 +111,6 @@ export function walk(cwd, dirs = false) { return (walk_dir(''), all_files); } -/** @param {string} str */ -export function posixify(str) { - return str.replace(/\\/g, '/'); -} - /** * Like `path.join`, but posixified and with a leading `./` if necessary * @param {string[]} str @@ -138,31 +134,6 @@ export function relative_path(from, to) { return join_relative(path.relative(from, to)); } -/** - * Prepend given path with `/@fs` prefix - * @param {string} str - */ -export function to_fs(str) { - str = posixify(str); - return `/@fs${ - // Windows/Linux separation - Windows starts with a drive letter, we need a / in front there - str.startsWith('/') ? '' : '/' - }${str}`; -} - -/** - * Removes `/@fs` prefix from given path and posixifies it - * @param {string} str - */ -export function from_fs(str) { - str = posixify(str); - if (!str.startsWith('/@fs')) return str; - - str = str.slice(4); - // Windows/Linux separation - Windows starts with a drive letter, we need to strip the additional / here - return str[2] === ':' && /[A-Z]/.test(str[1]) ? str.slice(1) : str; -} - /** * Given an entry point like [cwd]/src/hooks, returns a filename like [cwd]/src/hooks.js or [cwd]/src/hooks/index.js * @param {string} entry diff --git a/packages/kit/src/utils/os.js b/packages/kit/src/utils/os.js new file mode 100644 index 000000000000..52ee66f8a21b --- /dev/null +++ b/packages/kit/src/utils/os.js @@ -0,0 +1,4 @@ +/** @param {string} str */ +export function posixify(str) { + return str.replace(/\\/g, '/'); +} From 8c875c96f48e4b016cbbb061c42313f719a667b0 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Tue, 24 Mar 2026 21:31:44 +0800 Subject: [PATCH 016/117] clean up ipc --- packages/kit/src/exports/vite/dev/index.js | 53 +++++----------------- packages/kit/src/exports/vite/index.js | 28 ++++++------ 2 files changed, 26 insertions(+), 55 deletions(-) diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 425f7735b160..2efe71cdab7f 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -159,8 +159,6 @@ export function dev(vite, vite_config, svelte_config, root, dev_environment) { } }); - requests = new Map(); - vite.middlewares.use((req, res, next) => { const base = `${vite.config.server.https ? 'https' : 'http'}://${ req.headers[':authority'] || req.headers.host @@ -215,35 +213,6 @@ export function dev(vite, vite_config, svelte_config, root, dev_environment) { original_url = original_url?.slice(immutable.length); } - // TODO: come up with a better name than ipc - const prefix = `/${svelte_config.kit.appDir}/ipc/`; - if (decoded.startsWith(prefix)) { - const id = decoded.slice(prefix.length); - const request = await getRequest({ - base, - request: req - }); - - if (!requests.has(id)) { - res.writeHead(400); - res.end(`ipc call id does not exist: ${id}`); - return; - } - - const requested = requests.get(id); - try { - const data = await request.json(); - requested?.resolve(data); - } catch (e) { - requested?.reject(e); - } - requests.delete(id); - - res.writeHead(200); - res.end(); - return; - } - const file = posixify( path.resolve(root, decoded.slice(svelte_config.kit.paths.base.length + 1)) ); @@ -423,11 +392,6 @@ export function invalidate_module(vite, id) { } } -/** @type {Map void, reject: (error: any) => void }>} */ -let requests; - -// TODO: try using import.meta.hot.send from the module instead of fetch - /** * @overload * @param {import('vite').ViteDevServer} dev @@ -435,13 +399,20 @@ let requests; * @returns {Promise>} */ /** + * Retrieves data from a module that's been loaded into an environment. The module + * must import and call `send` from `__sveltekit/ipc` for this function to receive it * @param {import('vite').ViteDevServer} dev - * @param {string} id + * @param {string} event_id * @returns {Promise} */ -export function request(dev, id) { - dev.environments.ssr.hot.send(`sveltekit:ipc/${id}`); - return new Promise((resolve, reject) => { - requests.set(id, { resolve, reject }); +export function get_module_data(dev, event_id) { + const event = `sveltekit:${event_id}`; + + return new Promise((resolve) => { + /** @param {unknown} data */ + const listener = (data) => resolve(data); + dev.environments.ssr.hot.on(event, listener); + dev.environments.ssr.hot.send(event); + dev.environments.ssr.hot.off(event, listener); }); } diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index a3e1bd797222..e74ec65f4af5 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -20,7 +20,7 @@ import { runtime_directory, logger, get_runtime_base, get_mime_lookup } from '.. import { generate_manifest } from '../../core/generate_manifest/index.js'; import { build_server_nodes } from './build/build_server.js'; import { assets_base, find_deps, resolve_symlinks } from './build/utils.js'; -import { dev, invalidate_module, request } from './dev/index.js'; +import { dev, invalidate_module, get_module_data } from './dev/index.js'; import { preview } from './preview/index.js'; import { error_for_missing_config, @@ -963,16 +963,16 @@ function kit({ svelte_config, adapter_in_vite_config }) { if (!dev_environment) return; return dedent` - export function handle(id, data) { - if (import.meta.hot) { - import.meta.hot.on('sveltekit:ipc/' + id, () => { - fetch('${dev_environment.vite.resolvedUrls?.local[0]}_app/ipc/' + id, { - method: 'PUT', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify(data) - }); - }); - } + export function send(event_id, data) { + if (!import.meta.hot) return; + + const event = 'sveltekit:' + event_id; + const listener = () => { + import.meta.hot.send(event, data); + import.meta.hot.off(event, listener); + }; + + import.meta.hot.on(event, listener); } `; } @@ -1213,9 +1213,9 @@ function kit({ svelte_config, adapter_in_vite_config }) { ${ dev_environment?.vite ? dedent` - import { handle } from '__sveltekit/ipc'; + import { send } from '__sveltekit/ipc'; - handle('remote-${remote.hash}', (() => { + send('remote-${remote.hash}', (() => { const exports = new Map(); for (const name in $$_self_$$) { exports.set(name, { type: $$_self_$$[name].__.type }); @@ -1258,7 +1258,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { await dev_environment.vite.environments.ssr.transformRequest(id); - const exports = await request(dev_environment.vite, `remote-${remote.hash}`); + const exports = await get_module_data(dev_environment.vite, `remote-${remote.hash}`); for (const [name, value] of Object.entries(exports)) { const type = value.type; From ca2adb61d66ccbbb5433a92ba186718d4f5535be Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Tue, 24 Mar 2026 21:31:48 +0800 Subject: [PATCH 017/117] add test todo --- .../test/apps/workers/src/routes/remote/+page.svelte | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 packages/adapter-cloudflare/test/apps/workers/src/routes/remote/+page.svelte diff --git a/packages/adapter-cloudflare/test/apps/workers/src/routes/remote/+page.svelte b/packages/adapter-cloudflare/test/apps/workers/src/routes/remote/+page.svelte new file mode 100644 index 000000000000..ccb27f557f02 --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/workers/src/routes/remote/+page.svelte @@ -0,0 +1,2 @@ + + \ No newline at end of file From c3e5074e20e5ee9a09ec5067861307f6758f05f1 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Tue, 24 Mar 2026 22:55:08 +0800 Subject: [PATCH 018/117] we did it? --- packages/kit/src/core/utils.js | 2 +- packages/kit/src/exports/vite/dev/index.js | 71 ++++++- packages/kit/src/exports/vite/dev/server.js | 2 +- packages/kit/src/exports/vite/fetchable.js | 81 ------- packages/kit/src/exports/vite/filesystem.js | 29 +++ packages/kit/src/exports/vite/index.js | 221 ++++++++++++-------- 6 files changed, 237 insertions(+), 169 deletions(-) delete mode 100644 packages/kit/src/exports/vite/fetchable.js create mode 100644 packages/kit/src/exports/vite/filesystem.js diff --git a/packages/kit/src/core/utils.js b/packages/kit/src/core/utils.js index 97f8eb8ecca1..42aeb3b55a99 100644 --- a/packages/kit/src/core/utils.js +++ b/packages/kit/src/core/utils.js @@ -3,7 +3,7 @@ import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { styleText } from 'node:util'; import { posixify } from '../utils/os.js'; -import { to_fs } from '../exports/vite/fetchable.js'; +import { to_fs } from '../exports/vite/filesystem.js'; /** * Resolved path of the `runtime` directory diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 2efe71cdab7f..3ab978b220cf 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -16,7 +16,7 @@ import * as sync from '../../../core/sync/sync.js'; import { not_found } from '../utils.js'; import { escape_html } from '../../../utils/escape.js'; import { sveltekit_ssr_manifest } from '../module_ids.js'; -import { to_fs } from '../fetchable.js'; +import { to_fs } from '../filesystem.js'; // vite-specifc queries that we should skip handling for css urls const vite_css_query_regex = /(?:\?|&)(?:raw|url|inline)(?:&|$)/; @@ -410,9 +410,74 @@ export function get_module_data(dev, event_id) { return new Promise((resolve) => { /** @param {unknown} data */ - const listener = (data) => resolve(data); + const listener = (data) => { + dev.environments.ssr.hot.off(event, listener); + resolve(data); + }; dev.environments.ssr.hot.on(event, listener); dev.environments.ssr.hot.send(event); - dev.environments.ssr.hot.off(event, listener); }); } + +/** + * @param {import('types').ManifestData} manifest_data + * @param {import('vite/module-runner').ModuleRunner} runner + * @param {string} root + * @returns {Promise>} + */ +export async function get_matchers(manifest_data, runner, root) { + /** @type {Record} */ + const matchers = {}; + + for (const key in manifest_data.matchers) { + const file = manifest_data.matchers[key]; + const url = path.resolve(root, file); + const module = await runner.import(url); + + if (module.match) { + matchers[key] = module.match; + } else { + throw new Error(`${file} does not export a \`match\` function`); + } + } + + return matchers; +} + +/** + * + * @param {import('vite').ViteDevServer} vite + * @param {import('vite/module-runner').ModuleRunner} runner + * @param {string[]} urls + */ +export async function get_inline_css(vite, runner, urls) { + /** @type {Set} */ + const deps = new Set(); + + for (const url of urls) { + const module_node = await vite.environments.ssr.moduleGraph.getModuleByUrl(url); + if (!module_node) throw new Error(`Could not find node for ${url}`); + await find_deps(vite, module_node, deps); + } + + /** @type {Map} */ + const styles = new Map(); + + 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.set(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 Object.fromEntries(styles); +} diff --git a/packages/kit/src/exports/vite/dev/server.js b/packages/kit/src/exports/vite/dev/server.js index 6722528a8acd..e39024b1f971 100644 --- a/packages/kit/src/exports/vite/dev/server.js +++ b/packages/kit/src/exports/vite/dev/server.js @@ -4,7 +4,7 @@ import path from 'node:path'; import { Server } from '__sveltekit/dev'; import { env, manifest } from '__sveltekit/ssr-manifest'; import { createReadableStream } from '@sveltejs/kit/node'; -import { from_fs } from '../fetchable.js'; +import { from_fs } from '../filesystem.js'; const server = new Server(manifest); diff --git a/packages/kit/src/exports/vite/fetchable.js b/packages/kit/src/exports/vite/fetchable.js deleted file mode 100644 index b559629feff2..000000000000 --- a/packages/kit/src/exports/vite/fetchable.js +++ /dev/null @@ -1,81 +0,0 @@ -// this file needs to be runtime agnostic and avoid importing from `node:*` since -// it may not be available in edge environments - -import { posixify } from '../../utils/os.js'; - -/** - * Prepend given path with `/@fs` prefix - * @param {string} str - */ -export function to_fs(str) { - str = posixify(str); - return `/@fs${ - // Windows/Linux separation - Windows starts with a drive letter, we need a / in front there - str.startsWith('/') ? '' : '/' - }${str}`; -} - -/** - * Removes `/@fs` prefix from given path and posixifies it - * @param {string} str - */ -export function from_fs(str) { - str = posixify(str); - if (!str.startsWith('/@fs')) return str; - - str = str.slice(4); - // Windows/Linux separation - Windows starts with a drive letter, we need to strip the additional / here - return str[2] === ':' && /[A-Z]/.test(str[1]) ? str.slice(1) : str; -} - -/** @param {string} id */ -export async function resolve(id) { - // TODO: doesn't work for files symlinked to kit package workspace - const url = id.startsWith('..') ? to_fs(id) : `${id}`; - - const module = await loud_ssr_load_module(url); - - // const module_node = await ssr_environment.moduleGraph.getModuleByUrl(url); - // if (!module_node) throw new Error(`Could not find node for ${url}`); - - return { module, module_node: '', url }; -} - -// TODO: do we even need this or will Vite handle import errors for us? -/** - * @param {string} url - */ -export async function loud_ssr_load_module(url) { - try { - // return await server.ssrLoadModule(url, { fixStacktrace: true }); - return await import(/* @vite-ignore */ url); - } catch (/** @type {any} */ err) { - // const msg = buildErrorMessage(err, [styleText('red', `Internal server error: ${err.message}`)]); - const msg = `Internal server error: ${err.message}`; - - // if (!server.config.logger.hasErrorLogged(err)) { - // server.config.logger.error(msg, { error: err }); - // } - console.error(msg); - - // server.ws.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 - // } - // }); - import.meta.hot?.send('vite:error', { - ...err, - // these properties are non-enumerable and will - // not be serialized unless we explicitly include them - message: err.message, - stack: err.stack - }); - - throw err; - } -} diff --git a/packages/kit/src/exports/vite/filesystem.js b/packages/kit/src/exports/vite/filesystem.js new file mode 100644 index 000000000000..4214cc04f881 --- /dev/null +++ b/packages/kit/src/exports/vite/filesystem.js @@ -0,0 +1,29 @@ +// this file needs to be runtime agnostic and avoid importing from `node:*` since +// it may not be available in edge environments + +import { posixify } from '../../utils/os.js'; + +/** + * Prepend given path with `/@fs` prefix + * @param {string} str + */ +export function to_fs(str) { + str = posixify(str); + return `/@fs${ + // Windows/Linux separation - Windows starts with a drive letter, we need a / in front there + str.startsWith('/') ? '' : '/' + }${str}`; +} + +/** + * Removes `/@fs` prefix from given path and posixifies it + * @param {string} str + */ +export function from_fs(str) { + str = posixify(str); + if (!str.startsWith('/@fs')) return str; + + str = str.slice(4); + // Windows/Linux separation - Windows starts with a drive letter, we need to strip the additional / here + return str[2] === ':' && /[A-Z]/.test(str[1]) ? str.slice(1) : str; +} diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index e74ec65f4af5..057eb88940d2 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -20,7 +20,13 @@ import { runtime_directory, logger, get_runtime_base, get_mime_lookup } from '.. import { generate_manifest } from '../../core/generate_manifest/index.js'; import { build_server_nodes } from './build/build_server.js'; import { assets_base, find_deps, resolve_symlinks } from './build/utils.js'; -import { dev, invalidate_module, get_module_data } from './dev/index.js'; +import { + dev, + invalidate_module, + get_module_data, + get_matchers, + get_inline_css +} from './dev/index.js'; import { preview } from './preview/index.js'; import { error_for_missing_config, @@ -53,7 +59,7 @@ import { import_peer } from '../../utils/import.js'; import { compact } from '../../utils/array.js'; import { should_ignore, has_children } from './static_analysis/utils.js'; import { load_config } from '../../core/config/index.js'; -import { to_fs } from './fetchable.js'; +import { to_fs } from './filesystem.js'; import { posixify } from '../../utils/os.js'; const cwd = process.cwd(); @@ -319,6 +325,13 @@ function kit({ svelte_config, adapter_in_vite_config }) { */ let runner = null; + function get_module_runner() { + if (!runner) { + throw new Error('The module runner should have been created during the configureServer hook'); + } + return runner; + } + /** @type {import('vite').Plugin} */ const plugin_setup = { name: 'vite-plugin-sveltekit-setup', @@ -508,16 +521,13 @@ function kit({ svelte_config, adapter_in_vite_config }) { hot: true, transport: createServerHotChannel(), async handleRequest(request) { - if (!runner) - throw new Error('The module runner should have been created by now'); - try { /** * @type {{ * respond: (request: Request, remote_address: string | undefined, kit: import('types').ValidatedKitConfig) => Promise * }} */ - const { respond } = await runner.import( + const { respond } = await get_module_runner().import( import.meta.resolve('./dev/server.js') ); return await respond(request, dev_environment?.remote_address, kit); @@ -688,7 +698,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { return dedent` import { server_assets } from '__sveltekit/server-assets'; import { remotes } from '__sveltekit/remotes'; - import { resolve, loud_ssr_load_module } from '${get_runtime_base(root)}/../exports/vite/fetchable.js'; + import { to_fs } from '${get_runtime_base(root)}/../exports/vite/filesystem.js'; export const base_path = ${s(kit.paths.base)}; export const prerendered = new Set(); @@ -764,16 +774,15 @@ function kit({ svelte_config, adapter_in_vite_config }) { result.stylesheets = []; result.fonts = []; - const module_nodes = []; + const urls = []; ${ node.component ? dedent` result.component = async () => { - const { module_node, module } = await resolve(${s(path.resolve(root, node.component))}); - - module_nodes.push(module_node); - + const filepath = ${s(path.resolve(root, node.component))}; + const { module, url } = await resolve(filepath); + urls.push(url); return module.default; } ` @@ -787,8 +796,9 @@ function kit({ svelte_config, adapter_in_vite_config }) { 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(${s(path.resolve(root, node.universal))}); - module_nodes.push(module_node); + const filepath = ${s(path.resolve(root, node.universal))}; + const { module, url } = await resolve(filepath); + urls.push(url); result.universal = module; } ` @@ -798,7 +808,8 @@ function kit({ svelte_config, adapter_in_vite_config }) { ${ node.server ? dedent` - const { module } = await resolve(${s(path.resolve(root, node.server))}); + const filepath = ${s(path.resolve(root, node.server))}; + const { module } = await resolve(filepath); result.server = module; ` : '' @@ -807,34 +818,20 @@ function kit({ svelte_config, adapter_in_vite_config }) { // in dev we inline all styles to avoid FOUC. this gets populated lazily so that // components/stylesheets loaded via import() during \`load\` are included - // TODO: result.inline_styles - // 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 - // } - // } - // } - // } + const event = 'sveltekit:inline-styles-node-${index}'; + result.inline_styles = async () => { + return new Promise((resolve) => { + if (!import.meta.hot) return; + + const listener = (data) => { + import.meta.hot.off(event, listener); + resolve(data); + }; + + import.meta.hot.on(event, listener); + import.meta.hot.send('sveltekit:inline-styles', { urls, node: result.index }); + }); + } return result; } @@ -878,27 +875,70 @@ function kit({ svelte_config, adapter_in_vite_config }) { }) ).join(',\n')}], matchers: async () => { - // TODO: fetchable param matchers in dev - // /** @type {Record} */ - // const matchers = {}; - - // for (const key in manifest_data.matchers) { - // const file = manifest_data.matchers[key]; - // const url = path.resolve(root, file); - // const module = await vite.ssrLoadModule(url, { fixStacktrace: true }); - - // if (module.match) { - // matchers[key] = module.match; - // } else { - // throw new Error(\`\${file} does not export a \\\`match\\\` function\`); - // } - // } - - // return matchers; - return {}; + const event = 'sveltekit:matchers-response'; + return new Promise((resolve) => { + if (!import.meta.hot) return; + + const listener = (data) => { + import.meta.hot.off(event, listener); + resolve(data); + }; + + import.meta.hot.on(event, listener); + import.meta.hot.send('sveltekit:matchers-request'); + }); } } }; + + // TODO: do we even need this or will Vite handle import errors for us? + /** + * @param {string} url + */ + async function loud_ssr_load_module(url) { + try { + return await import(/* @vite-ignore */ url); + } catch (/** @type {any} */ err) { + // TODO: move this to the vite process by calling import.meta.hot? + + // const msg = buildErrorMessage(err, [styleText('red', \`Internal server error: \${err.message}\`)]); + const msg = \`Internal server error: \${err.message}\`; + + // if (!server.config.logger.hasErrorLogged(err)) { + // server.config.logger.error(msg, { error: err }); + // } + console.error(msg); + + // server.ws.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 + // } + // }); + + // import.meta.hot?.send('vite:error', { + // ...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) { + // TODO: doesn't work for files symlinked to kit package workspace? + const url = id.startsWith('..') ? to_fs(id) : \`file:///\${id}\`; + const module = await loud_ssr_load_module(url); + return { module, url }; + } `; } @@ -1065,9 +1105,6 @@ function kit({ svelte_config, adapter_in_vite_config }) { return; } - // in dev, this doesn't exist, so we need to create it - manifest_data ??= sync.all(svelte_config, vite_config_env.mode, root).manifest_data; - /** @type {Set} */ const entrypoints = new Set(); for (const node of manifest_data.nodes) { @@ -1670,15 +1707,30 @@ function kit({ svelte_config, adapter_in_vite_config }) { * @see https://vitejs.dev/guide/api-plugin.html#configureserver */ configureServer(vite) { + manifest_data = sync.all(svelte_config, vite_config_env.mode, root).manifest_data; + // other properties will be populated during the `dev` function - dev_environment = /** @type {import('types').DevEnvironment} */ ({ + const info = (dev_environment = /** @type {import('types').DevEnvironment} */ ({ vite, - env: loadEnv(vite_config.mode, svelte_config.kit.env.dir, '') + env: loadEnv(vite_config.mode, svelte_config.kit.env.dir, ''), + manifest_data + })); + + const module_runner = (runner = createServerModuleRunner(vite.environments.ssr)); + + vite.environments.ssr.hot.on('sveltekit:matchers-request', async () => { + vite.environments.ssr.hot.send( + 'sveltekit:matchers-response', + await get_matchers(info.manifest_data, module_runner, root) + ); }); - if (!kit.adapter?.vite?.plugins) { - runner = createServerModuleRunner(vite.environments.ssr); - } + vite.environments.ssr.hot.on('sveltekit:inline-styles', async ({ urls, node }) => { + vite.environments.ssr.hot.send( + `sveltekit:inline-styles-node-${node}`, + await get_inline_css(vite, module_runner, urls) + ); + }); return dev(vite, vite_config, svelte_config, root, dev_environment); }, @@ -2142,19 +2194,22 @@ function find_overridden_config(config, resolved_config, enforced_config, path, /** * @param {import('types').ValidatedConfig} config + * @returns {string} */ -const create_service_worker_module = (config) => dedent` - if (typeof self === 'undefined' || self instanceof ServiceWorkerGlobalScope === false) { - throw new Error('This module can only be imported inside a service worker'); - } +function create_service_worker_module(config) { + return dedent` + if (typeof self === 'undefined' || self instanceof ServiceWorkerGlobalScope === false) { + throw new Error('This module can only be imported inside a service worker'); + } - export const build = []; - export const files = [ - ${create_assets(config) - .filter((asset) => config.kit.serviceWorker.files(asset.file)) - .map((asset) => `${s(`${config.kit.paths.base}/${asset.file}`)}`) - .join(',\n')} - ]; - export const prerendered = []; - export const version = ${s(config.kit.version.name)}; -`; + export const build = []; + export const files = [ + ${create_assets(config) + .filter((asset) => config.kit.serviceWorker.files(asset.file)) + .map((asset) => `${s(`${config.kit.paths.base}/${asset.file}`)}`) + .join(',\n')} + ]; + export const prerendered = []; + export const version = ${s(config.kit.version.name)}; + `; +} From 95dc4b8bcdb46215be5f0d0b0e2fafed469edcb4 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Tue, 24 Mar 2026 22:57:38 +0800 Subject: [PATCH 019/117] space --- .../test/apps/workers/src/routes/remote/+page.svelte | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/adapter-cloudflare/test/apps/workers/src/routes/remote/+page.svelte b/packages/adapter-cloudflare/test/apps/workers/src/routes/remote/+page.svelte index ccb27f557f02..d9254aadb2de 100644 --- a/packages/adapter-cloudflare/test/apps/workers/src/routes/remote/+page.svelte +++ b/packages/adapter-cloudflare/test/apps/workers/src/routes/remote/+page.svelte @@ -1,2 +1 @@ - \ No newline at end of file From eadcb7ab3de4d422fe5a19bd7a9a024bf14f73e1 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Tue, 24 Mar 2026 23:00:28 +0800 Subject: [PATCH 020/117] whoops --- packages/kit/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/package.json b/packages/kit/package.json index 00c26203eee6..2c9d85de26e6 100644 --- a/packages/kit/package.json +++ b/packages/kit/package.json @@ -1,6 +1,6 @@ { "name": "@sveltejs/kit", - "version": "3.0.0", + "version": "2.55.0", "description": "SvelteKit is the fastest way to build Svelte apps", "keywords": [ "framework", From f5396cb52dfb1b678ce42347e23134d3738c4dac Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Tue, 24 Mar 2026 23:14:30 +0800 Subject: [PATCH 021/117] run instrumentation if it exists --- packages/kit/src/exports/vite/index.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 057eb88940d2..b4ff55aba5b6 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -522,12 +522,21 @@ function kit({ svelte_config, adapter_in_vite_config }) { transport: createServerHotChannel(), async handleRequest(request) { try { + const module_runner = get_module_runner(); + + const resolved_instrumentation = resolve_entry( + path.join(svelte_config.kit.files.src, 'instrumentation.server') + ); + if (resolved_instrumentation) { + await module_runner.import(resolved_instrumentation); + } + /** * @type {{ * respond: (request: Request, remote_address: string | undefined, kit: import('types').ValidatedKitConfig) => Promise * }} */ - const { respond } = await get_module_runner().import( + const { respond } = await module_runner.import( import.meta.resolve('./dev/server.js') ); return await respond(request, dev_environment?.remote_address, kit); @@ -948,12 +957,6 @@ function kit({ svelte_config, adapter_in_vite_config }) { const runtime_base = get_runtime_base(root); const adapter = svelte_config.kit.adapter; - const resolved_instrumentation = resolve_entry( - path.join(svelte_config.kit.files.src, 'instrumentation.server') - ); - - // TODO: if instrumentation exists, import it first - return dedent` import { AsyncLocalStorage } from 'node:async_hooks'; import { set_assets } from '${runtime_base}/app/paths/internal/server.js'; From 3c7138b9649161f84dc48318028c57763374cc54 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Wed, 25 Mar 2026 02:23:58 +0800 Subject: [PATCH 022/117] correctly serialise nodes --- packages/kit/src/exports/vite/dev/server.js | 1 - packages/kit/src/exports/vite/index.js | 21 +++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/kit/src/exports/vite/dev/server.js b/packages/kit/src/exports/vite/dev/server.js index e39024b1f971..933528e88ae9 100644 --- a/packages/kit/src/exports/vite/dev/server.js +++ b/packages/kit/src/exports/vite/dev/server.js @@ -1,6 +1,5 @@ import fs from 'node:fs'; import path from 'node:path'; - import { Server } from '__sveltekit/dev'; import { env, manifest } from '__sveltekit/ssr-manifest'; import { createReadableStream } from '@sveltejs/kit/node'; diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index b4ff55aba5b6..e2fd50bea33f 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -713,6 +713,8 @@ function kit({ svelte_config, adapter_in_vite_config }) { export const prerendered = new Set(); export const env = ${s(env)}; + const nodes = ${s(devalue.uneval(manifest_data.nodes, revive_functions))} + export const manifest = { appDir: ${s(kit.appDir)}, appPath: ${s(kit.appDir)}, @@ -771,7 +773,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { .map((node, index) => { return dedent` async () => { - const node = ${devalue.uneval(node, revive_functions)}; + const node = nodes[${index}]; const result = {}; result.index = ${index}; @@ -789,8 +791,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { node.component ? dedent` result.component = async () => { - const filepath = ${s(path.resolve(root, node.component))}; - const { module, url } = await resolve(filepath); + const { module, url } = await resolve(${s(path.resolve(root, node.component))}); urls.push(url); return module.default; } @@ -805,8 +806,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { result.universal = node.page_options; } else { // TODO: explain why the file was loaded on the server if we fail to load it - const filepath = ${s(path.resolve(root, node.universal))}; - const { module, url } = await resolve(filepath); + const { module, url } = await resolve(${s(path.resolve(root, node.universal))}); urls.push(url); result.universal = module; } @@ -817,8 +817,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { ${ node.server ? dedent` - const filepath = ${s(path.resolve(root, node.server))}; - const { module } = await resolve(filepath); + const { module } = await resolve(${s(path.resolve(root, node.server))}); result.server = module; ` : '' @@ -1003,8 +1002,6 @@ function kit({ svelte_config, adapter_in_vite_config }) { } case sveltekit_ipc: { - if (!dev_environment) return; - return dedent` export function send(event_id, data) { if (!import.meta.hot) return; @@ -1743,6 +1740,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { * @see https://vitejs.dev/guide/api-plugin.html#configurepreviewserver */ configurePreviewServer(vite) { + // TODO: run the build output through the environment return preview(vite, vite_config, svelte_config); }, @@ -2085,7 +2083,10 @@ function kit({ svelte_config, adapter_in_vite_config }) { } }; - /** @type {import('vite').Plugin} */ + /** + * Allows us to access the filesystem from an environment that doesn't have `node:fs` + * @type {import('vite').Plugin} + */ const plugin_server_filesystem = { name: 'vite-plugin-sveltekit-server-filesystem', apply: 'serve', From 7897025312c4ebc1b51c5a977d85d345c686c774 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Wed, 25 Mar 2026 02:44:45 +0800 Subject: [PATCH 023/117] fix ts --- packages/adapter-cloudflare/tsconfig.json | 3 ++- packages/kit/src/types/virtual.d.ts | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/adapter-cloudflare/tsconfig.json b/packages/adapter-cloudflare/tsconfig.json index d8e4d8e14dc5..fd99ca7e30dc 100644 --- a/packages/adapter-cloudflare/tsconfig.json +++ b/packages/adapter-cloudflare/tsconfig.json @@ -8,7 +8,8 @@ "module": "nodenext", "baseUrl": ".", "paths": { - "@sveltejs/kit": ["../kit/types/index"] + "@sveltejs/kit": ["../kit/types/index"], + "types": ["../kit/src/types/internal.d.ts"] }, // taken from the Cloudflare Workers TypeScript template https://github.com/cloudflare/workers-sdk/blob/main/packages/create-cloudflare/templates/hello-world/ts/tsconfig.json "target": "es2024", diff --git a/packages/kit/src/types/virtual.d.ts b/packages/kit/src/types/virtual.d.ts index d448c0f9f72b..8c6e07940d92 100644 --- a/packages/kit/src/types/virtual.d.ts +++ b/packages/kit/src/types/virtual.d.ts @@ -1,5 +1,4 @@ declare module '__sveltekit/ssr-manifest' { - // eslint-disable-next-line no-duplicate-imports import { SSRManifest } from '@sveltejs/kit'; export const manifest: SSRManifest; From 5db82854d495668b7f71dd18e5f3cfe7b4536be9 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Wed, 25 Mar 2026 02:50:18 +0800 Subject: [PATCH 024/117] changesets --- .changeset/angry-swans-talk.md | 5 +++++ .changeset/old-yaks-refuse.md | 5 +++++ .changeset/short-chefs-stare.md | 5 +++++ 3 files changed, 15 insertions(+) create mode 100644 .changeset/angry-swans-talk.md create mode 100644 .changeset/old-yaks-refuse.md create mode 100644 .changeset/short-chefs-stare.md diff --git a/.changeset/angry-swans-talk.md b/.changeset/angry-swans-talk.md new file mode 100644 index 000000000000..f0d4ff422946 --- /dev/null +++ b/.changeset/angry-swans-talk.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/adapter-cloudflare': major +--- + +breaking: utilise the Cloudflare Vite plugin diff --git a/.changeset/old-yaks-refuse.md b/.changeset/old-yaks-refuse.md new file mode 100644 index 000000000000..e230223fb9d6 --- /dev/null +++ b/.changeset/old-yaks-refuse.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/adapter-cloudflare': major +--- + +breaking: remove support for Cloudflare Pages diff --git a/.changeset/short-chefs-stare.md b/.changeset/short-chefs-stare.md new file mode 100644 index 000000000000..74e7e3321a8a --- /dev/null +++ b/.changeset/short-chefs-stare.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': major +--- + +breaking: use the Vite Environment API in dev and preview From ad2fe7347acf01d1e493416ca69dc6b3e55df6c1 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Wed, 25 Mar 2026 02:53:23 +0800 Subject: [PATCH 025/117] split cloudflare changes --- packages/adapter-cloudflare/index.d.ts | 64 +++- packages/adapter-cloudflare/index.js | 180 ++++++---- packages/adapter-cloudflare/package.json | 6 +- .../test/apps/pages/.gitignore | 4 + .../test/apps/pages/package.json | 21 ++ .../test/apps/pages/playwright.config.js | 1 + .../apps/pages/server-side-dep/index.d.ts | 1 + .../test/apps/pages/server-side-dep/index.js | 4 + .../apps/pages/server-side-dep/package.json | 11 + .../test/apps/pages/src/app.html | 11 + .../apps/pages/src/routes/+page.server.js | 8 + .../test/apps/pages/src/routes/+page.svelte | 5 + .../test/apps/pages/svelte.config.js | 10 + .../test/apps/pages/test/test.js | 6 + .../test/apps/pages/tsconfig.json | 14 + .../test/apps/pages/vite.config.js | 11 + .../test/apps/workers/config/wrangler.jsonc | 7 +- .../test/apps/workers/package.json | 5 +- .../apps/workers/src/routes/read/+server.js | 6 +- .../test/apps/workers/svelte.config.js | 2 +- .../test/apps/workers/vite.config.js | 8 +- packages/adapter-cloudflare/test/utils.js | 2 +- packages/adapter-cloudflare/tsconfig.json | 8 +- packages/adapter-cloudflare/utils.js | 156 ++++++++- packages/adapter-cloudflare/utils.spec.js | 310 +++++++++++++++++- 25 files changed, 750 insertions(+), 111 deletions(-) create mode 100644 packages/adapter-cloudflare/test/apps/pages/.gitignore create mode 100644 packages/adapter-cloudflare/test/apps/pages/package.json create mode 100644 packages/adapter-cloudflare/test/apps/pages/playwright.config.js create mode 100644 packages/adapter-cloudflare/test/apps/pages/server-side-dep/index.d.ts create mode 100644 packages/adapter-cloudflare/test/apps/pages/server-side-dep/index.js create mode 100644 packages/adapter-cloudflare/test/apps/pages/server-side-dep/package.json create mode 100644 packages/adapter-cloudflare/test/apps/pages/src/app.html create mode 100644 packages/adapter-cloudflare/test/apps/pages/src/routes/+page.server.js create mode 100644 packages/adapter-cloudflare/test/apps/pages/src/routes/+page.svelte create mode 100644 packages/adapter-cloudflare/test/apps/pages/svelte.config.js create mode 100644 packages/adapter-cloudflare/test/apps/pages/test/test.js create mode 100644 packages/adapter-cloudflare/test/apps/pages/tsconfig.json create mode 100644 packages/adapter-cloudflare/test/apps/pages/vite.config.js diff --git a/packages/adapter-cloudflare/index.d.ts b/packages/adapter-cloudflare/index.d.ts index 62b78aac323b..b9062094bd08 100644 --- a/packages/adapter-cloudflare/index.d.ts +++ b/packages/adapter-cloudflare/index.d.ts @@ -1,39 +1,79 @@ -import { PluginConfig } from '@cloudflare/vite-plugin'; import { Adapter } from '@sveltejs/kit'; -import { GetPlatformProxyOptions } from 'wrangler'; import './ambient.js'; +import { GetPlatformProxyOptions } from 'wrangler'; export default function plugin(options?: AdapterOptions): Adapter; export interface AdapterOptions { /** - * Options to pass to the Cloudflare Vite plugin. - * @see https://developers.cloudflare.com/workers/vite-plugin/reference/api/#interface-pluginconfig + * Path to your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). */ - vitePluginOptions?: PluginConfig; + config?: string; /** * Whether to render a plaintext 404.html page or a rendered SPA fallback page * for non-matching asset requests. * - * The default behaviour is to return a null-body + * For Cloudflare Workers, the default behaviour is to return a null-body * 404-status response for non-matching assets requests. However, if the * [`assets.not_found_handling`](https://developers.cloudflare.com/workers/static-assets/routing/#2-not_found_handling) * Wrangler configuration setting is set to `"404-page"`, this page will be * served if a request fails to match an asset. If `assets.not_found_handling` * is set to `"single-page-application"`, the adapter will render a SPA fallback * `index.html` page regardless of the `fallback` option specified. + * + * For Cloudflare Pages, this page will only be served when a request that + * matches an entry in `routes.exclude` fails to match an asset. + * + * Most of the time `plaintext` is sufficient, but if you are using `routes.exclude` to manually + * exclude a set of prerendered pages without exceeding the 100 route limit, you may wish to + * use `spa` instead to avoid showing an unstyled 404 page to users. + * + * See [Cloudflare Pages' Not Found behavior](https://developers.cloudflare.com/pages/configuration/serving-pages/#not-found-behavior) for more info. + * * @default 'plaintext' */ fallback?: 'plaintext' | 'spa'; + + /** + * Only for Cloudflare Pages. Customize the automatically-generated [`_routes.json`](https://developers.cloudflare.com/pages/platform/functions/routing/#create-a-_routesjson-file) file. + */ + routes?: { + /** + * Routes that will be invoked by functions. Accepts wildcards. + * @default ["/*"] + */ + include?: string[]; + + /** + * Routes that will not be invoked by functions. Accepts wildcards. + * `exclude` takes priority over `include`. + * + * To have the adapter automatically exclude certain things, you can use these placeholders: + * + * - `` to exclude build artifacts (files generated by Vite) + * - `` for the contents of your `static` directory + * - `` for prerendered routes + * - `` to exclude all of the above + * + * @default [""] + */ + exclude?: string[]; + }; + /** * Config object passed to [`getPlatformProxy`](https://developers.cloudflare.com/workers/wrangler/api/#getplatformproxy) * during development and preview. - * @deprecated removed in 8.0.0. Use `vitePluginOptions` instead */ platformProxy?: GetPlatformProxyOptions; - /** - * Path to your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). - * @deprecated removed in 8.0.0. Use `vitePluginOptions.configPath` instead - */ - config?: string; +} + +/** + * The JSON format of the {@link https://developers.cloudflare.com/pages/functions/routing/#create-a-_routesjson-file | `_routes.json`} + * file that controls when the Cloudflare Pages Function is invoked. + */ +export interface RoutesJSONSpec { + version: 1; + description: string; + include: string[]; + exclude: string[]; } diff --git a/packages/adapter-cloudflare/index.js b/packages/adapter-cloudflare/index.js index 37385b6ba647..66c8a2da246e 100644 --- a/packages/adapter-cloudflare/index.js +++ b/packages/adapter-cloudflare/index.js @@ -1,18 +1,19 @@ -import { copyFileSync, existsSync, writeFileSync } from 'node:fs'; +import { copyFileSync, existsSync, readFileSync, writeFileSync } from 'node:fs'; import path from 'node:path'; import process from 'node:process'; import { fileURLToPath } from 'node:url'; -import { unstable_readConfig } from 'wrangler'; -import { cloudflare } from '@cloudflare/vite-plugin'; -import { validate_worker_settings } from './utils.js'; -import { DEV } from 'esm-env'; +import { getPlatformProxy, unstable_readConfig } from 'wrangler'; +import { + is_building_for_cloudflare_pages, + validate_worker_settings, + get_routes_json, + parse_redirects +} from './utils.js'; const name = '@sveltejs/adapter-cloudflare'; /** @type {import('./index.js').default} */ export default function (options = {}) { - const { wrangler_config } = validate_wrangler_config(options.vitePluginOptions?.configPath); - return { name, async adapt(builder) { @@ -37,23 +38,34 @@ export default function (options = {}) { ); } + const { wrangler_config, building_for_cloudflare_pages } = validate_wrangler_config( + options.config + ); + let dest = builder.getBuildDirectory('cloudflare'); let worker_dest = `${dest}/_worker.js`; let assets_binding = 'ASSETS'; - if (wrangler_config.main) { - worker_dest = wrangler_config.main; - } - if (wrangler_config.assets?.directory) { - // wrangler doesn't resolve `assets.directory` to an absolute path unlike - // `main` and `pages_build_output_dir` so we need to do it ourselves here - const parent_dir = wrangler_config.configPath - ? path.dirname(path.resolve(wrangler_config.configPath)) - : process.cwd(); - dest = path.resolve(parent_dir, wrangler_config.assets.directory); - } - if (wrangler_config.assets?.binding) { - assets_binding = wrangler_config.assets.binding; + if (building_for_cloudflare_pages) { + if (wrangler_config.pages_build_output_dir) { + dest = wrangler_config.pages_build_output_dir; + worker_dest = `${dest}/_worker.js`; + } + } else { + if (wrangler_config.main) { + worker_dest = wrangler_config.main; + } + if (wrangler_config.assets?.directory) { + // wrangler doesn't resolve `assets.directory` to an absolute path unlike + // `main` and `pages_build_output_dir` so we need to do it ourselves here + const parent_dir = wrangler_config.configPath + ? path.dirname(path.resolve(wrangler_config.configPath)) + : process.cwd(); + dest = path.resolve(parent_dir, wrangler_config.assets.directory); + } + if (wrangler_config.assets?.binding) { + assets_binding = wrangler_config.assets.binding; + } } const files = fileURLToPath(new URL('./files', import.meta.url).href); @@ -68,7 +80,10 @@ export default function (options = {}) { // client assets and prerendered pages const assets_dest = `${dest}${builder.config.kit.paths.base}`; builder.mkdirp(assets_dest); - if (wrangler_config.assets?.not_found_handling === '404-page') { + if ( + building_for_cloudflare_pages || + wrangler_config.assets?.not_found_handling === '404-page' + ) { // generate plaintext 404.html first which can then be overridden by prerendering, if the user defined such a page. // This file is served when a request fails to match an asset. // If we're building for Cloudflare Pages, it's only served when a request matches an entry in `routes.exclude` @@ -79,9 +94,12 @@ export default function (options = {}) { writeFileSync(fallback, 'Not Found'); } } - builder.writeClient(assets_dest); + const client_assets = builder.writeClient(assets_dest); builder.writePrerendered(assets_dest); - if (wrangler_config.assets?.not_found_handling === 'single-page-application') { + if ( + !building_for_cloudflare_pages && + wrangler_config.assets?.not_found_handling === 'single-page-application' + ) { await builder.generateFallback(path.join(assets_dest, 'index.html')); } @@ -132,53 +150,69 @@ export default function (options = {}) { }); } - writeFileSync(`${dest}/.assetsignore`, generate_assetsignore(), { flag: 'a' }); - }, - supports: { - read: () => true, - instrumentation: () => true + if (building_for_cloudflare_pages) { + // _routes.json + + // we need to add the source paths found in the `_redirects` file to the + // `_routes.json` file so that Cloudflare knows it shouldn't invoke the + // Worker but instead let the rules in the `_redirects` file take over. + /** @type {string[]} */ + let redirects = []; + if (existsSync(redirects_dest)) { + const redirect_rules = readFileSync(redirects_dest, 'utf8'); + redirects = parse_redirects(redirect_rules); + } + + writeFileSync( + `${dest}/_routes.json`, + JSON.stringify( + get_routes_json(builder, client_assets, redirects, options.routes ?? {}), + null, + '\t' + ) + ); + } else { + writeFileSync(`${dest}/.assetsignore`, generate_assetsignore(), { flag: 'a' }); + } }, - vite: { - plugins: [ - cloudflare({ - ...options.vitePluginOptions, - configPath: options.vitePluginOptions?.configPath, - viteEnvironment: { - name: options.vitePluginOptions?.viteEnvironment?.name ?? 'ssr', - childEnvironments: options.vitePluginOptions?.viteEnvironment?.childEnvironments - }, - config: (user_config) => { - // user programmatic config - if (typeof options.vitePluginOptions?.config === 'function') { - options.vitePluginOptions?.config(user_config); - } else { - Object.assign(user_config, options.vitePluginOptions?.config); + emulate() { + // we want to invoke `getPlatformProxy` only once, but await it only when it is accessed. + // If we would await it here, it would hang indefinitely because the platform proxy only resolves once a request happens + const get_emulated = async () => { + const proxy = await getPlatformProxy(options.platformProxy); + const platform = /** @type {App.Platform} */ ({ + env: proxy.env, + ctx: proxy.ctx, + context: proxy.ctx, // deprecated in favor of ctx + caches: proxy.caches, + cf: proxy.cf + }); + /** @type {Record} */ + const env = {}; + const prerender_platform = /** @type {App.Platform} */ (/** @type {unknown} */ ({ env })); + for (const key in proxy.env) { + Object.defineProperty(env, key, { + get: () => { + throw new Error(`Cannot access platform.env.${key} in a prerenderable route`); } + }); + } + return { platform, prerender_platform }; + }; - if (DEV) { - if (!user_config.assets?.binding) { - user_config.assets = { - binding: 'ASSETS' - }; - } - - if (!user_config.main) { - user_config.main = path.resolve(import.meta.dirname, 'fallback-worker.js'); - } - } else { - // TODO: if `main` or `assets.binding` is configured, ensure `main`, `assets.directory` and `assets.binding` are populated - } + /** @type {{ platform: App.Platform, prerender_platform: App.Platform }} */ + let emulated; - if ( - !user_config.compatibility_flags.find( - (flag) => flag === 'nodejs_als' || flag === 'nodejs_compat' - ) - ) { - user_config.compatibility_flags.push('nodejs_als'); - } - } - }) - ] + return { + platform: async ({ prerender }) => { + emulated ??= await get_emulated(); + return prerender ? emulated.prerender_platform : emulated.platform; + } + }; + }, + supports: { + read: () => true, + instrumentation: () => true } }; } @@ -233,16 +267,24 @@ _redirects /** * @param {string | undefined} config_file * @returns {{ - * wrangler_config: import('wrangler').Unstable_Config + * wrangler_config: import('wrangler').Unstable_Config, + * building_for_cloudflare_pages: boolean * }} */ function validate_wrangler_config(config_file = undefined) { const wrangler_config = unstable_readConfig({ config: config_file }); - validate_worker_settings(wrangler_config); + const building_for_cloudflare_pages = is_building_for_cloudflare_pages(wrangler_config); + + // we don't need to validate the config if we're building for Cloudflare Pages + // because the `main` and `assets` values cannot be changed there + if (!building_for_cloudflare_pages) { + validate_worker_settings(wrangler_config); + } return { - wrangler_config + wrangler_config, + building_for_cloudflare_pages }; } diff --git a/packages/adapter-cloudflare/package.json b/packages/adapter-cloudflare/package.json index 2f5c592b86de..8b185d760303 100644 --- a/packages/adapter-cloudflare/package.json +++ b/packages/adapter-cloudflare/package.json @@ -44,8 +44,7 @@ "prepublishOnly": "pnpm build" }, "dependencies": { - "@cloudflare/workers-types": "^4.20260312.0", - "esm-env": "^1.2.2", + "@cloudflare/workers-types": "^4.20260219.0", "worktop": "0.8.0-next.18" }, "devDependencies": { @@ -59,7 +58,6 @@ }, "peerDependencies": { "@sveltejs/kit": "^3.0.0", - "wrangler": "^4.74.0", - "@cloudflare/vite-plugin": "^1.29.0" + "wrangler": "^4.67.0" } } diff --git a/packages/adapter-cloudflare/test/apps/pages/.gitignore b/packages/adapter-cloudflare/test/apps/pages/.gitignore new file mode 100644 index 000000000000..1bd7b63de4b6 --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/pages/.gitignore @@ -0,0 +1,4 @@ +.DS_Store +node_modules +/.svelte-kit +/.wrangler \ No newline at end of file diff --git a/packages/adapter-cloudflare/test/apps/pages/package.json b/packages/adapter-cloudflare/test/apps/pages/package.json new file mode 100644 index 000000000000..0839ddd0a69f --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/pages/package.json @@ -0,0 +1,21 @@ +{ + "name": "test-cloudflare-pages", + "version": "0.0.1", + "private": true, + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "wrangler pages dev .svelte-kit/cloudflare --port 8787", + "prepare": "svelte-kit sync || echo ''", + "test": "playwright test" + }, + "devDependencies": { + "@sveltejs/kit": "workspace:^", + "@sveltejs/vite-plugin-svelte": "catalog:", + "server-side-dep": "file:server-side-dep", + "svelte": "catalog:", + "vite": "catalog:", + "wrangler": "catalog:" + }, + "type": "module" +} diff --git a/packages/adapter-cloudflare/test/apps/pages/playwright.config.js b/packages/adapter-cloudflare/test/apps/pages/playwright.config.js new file mode 100644 index 000000000000..33d36b651014 --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/pages/playwright.config.js @@ -0,0 +1 @@ +export { config as default } from '../../utils.js'; diff --git a/packages/adapter-cloudflare/test/apps/pages/server-side-dep/index.d.ts b/packages/adapter-cloudflare/test/apps/pages/server-side-dep/index.d.ts new file mode 100644 index 000000000000..0e1188e04a67 --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/pages/server-side-dep/index.d.ts @@ -0,0 +1 @@ +export function sum(a: number, b: number): number; diff --git a/packages/adapter-cloudflare/test/apps/pages/server-side-dep/index.js b/packages/adapter-cloudflare/test/apps/pages/server-side-dep/index.js new file mode 100644 index 000000000000..568b90577d43 --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/pages/server-side-dep/index.js @@ -0,0 +1,4 @@ +/** @type {import('./index.js').sum} */ +export function sum(a, b) { + return a + b; +} diff --git a/packages/adapter-cloudflare/test/apps/pages/server-side-dep/package.json b/packages/adapter-cloudflare/test/apps/pages/server-side-dep/package.json new file mode 100644 index 000000000000..5b26c9d1855a --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/pages/server-side-dep/package.json @@ -0,0 +1,11 @@ +{ + "name": "server-side-dep", + "version": "0.0.1", + "type": "module", + "exports": { + ".": { + "default": "./index.js", + "types": "./index.d.ts" + } + } +} diff --git a/packages/adapter-cloudflare/test/apps/pages/src/app.html b/packages/adapter-cloudflare/test/apps/pages/src/app.html new file mode 100644 index 000000000000..d533c5e31716 --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/pages/src/app.html @@ -0,0 +1,11 @@ + + + + + + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/packages/adapter-cloudflare/test/apps/pages/src/routes/+page.server.js b/packages/adapter-cloudflare/test/apps/pages/src/routes/+page.server.js new file mode 100644 index 000000000000..0eeb7d398ffd --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/pages/src/routes/+page.server.js @@ -0,0 +1,8 @@ +// this tests that Wrangler can correctly resolve and bundle server-side dependencies +import { sum } from 'server-side-dep'; + +export function load() { + return { + sum: sum(1, 2) + }; +} diff --git a/packages/adapter-cloudflare/test/apps/pages/src/routes/+page.svelte b/packages/adapter-cloudflare/test/apps/pages/src/routes/+page.svelte new file mode 100644 index 000000000000..d5e339683387 --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/pages/src/routes/+page.svelte @@ -0,0 +1,5 @@ + + +

Sum: {data.sum}

diff --git a/packages/adapter-cloudflare/test/apps/pages/svelte.config.js b/packages/adapter-cloudflare/test/apps/pages/svelte.config.js new file mode 100644 index 000000000000..20cd2b3ff5b8 --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/pages/svelte.config.js @@ -0,0 +1,10 @@ +import adapter from '../../../index.js'; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + kit: { + adapter: adapter() + } +}; + +export default config; diff --git a/packages/adapter-cloudflare/test/apps/pages/test/test.js b/packages/adapter-cloudflare/test/apps/pages/test/test.js new file mode 100644 index 000000000000..0619c030bbc2 --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/pages/test/test.js @@ -0,0 +1,6 @@ +import { expect, test } from '@playwright/test'; + +test('worker', async ({ page }) => { + await page.goto('/'); + await expect(page.locator('h1')).toContainText('Sum: 3'); +}); diff --git a/packages/adapter-cloudflare/test/apps/pages/tsconfig.json b/packages/adapter-cloudflare/test/apps/pages/tsconfig.json new file mode 100644 index 000000000000..34380ebc986e --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/pages/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "moduleResolution": "bundler" + }, + "extends": "./.svelte-kit/tsconfig.json" +} diff --git a/packages/adapter-cloudflare/test/apps/pages/vite.config.js b/packages/adapter-cloudflare/test/apps/pages/vite.config.js new file mode 100644 index 000000000000..29ad08debe6a --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/pages/vite.config.js @@ -0,0 +1,11 @@ +import { sveltekit } from '@sveltejs/kit/vite'; + +/** @type {import('vite').UserConfig} */ +const config = { + build: { + minify: false + }, + plugins: [sveltekit()] +}; + +export default config; diff --git a/packages/adapter-cloudflare/test/apps/workers/config/wrangler.jsonc b/packages/adapter-cloudflare/test/apps/workers/config/wrangler.jsonc index fed0d7b53a2a..0bf8a9db8c3a 100644 --- a/packages/adapter-cloudflare/test/apps/workers/config/wrangler.jsonc +++ b/packages/adapter-cloudflare/test/apps/workers/config/wrangler.jsonc @@ -1,5 +1,10 @@ // we've moved the wrangler config away from the root of the project // to test that the adapter still resolves the paths correctly { - "$schema": "../../../../node_modules/wrangler/config-schema.json" + "$schema": "../node_modules/wrangler/config-schema.json", + "main": "../dist/index.js", + "assets": { + "directory": "../dist/public", + "binding": "ASSETS" + } } diff --git a/packages/adapter-cloudflare/test/apps/workers/package.json b/packages/adapter-cloudflare/test/apps/workers/package.json index 46d61300d85d..4032805d1ec6 100644 --- a/packages/adapter-cloudflare/test/apps/workers/package.json +++ b/packages/adapter-cloudflare/test/apps/workers/package.json @@ -5,7 +5,7 @@ "scripts": { "dev": "vite dev", "build": "vite build", - "preview": "vite preview", + "preview": "wrangler dev dist/index.js", "prepare": "svelte-kit sync || echo ''", "test:dev": "DEV=true playwright test", "test:build": "playwright test", @@ -16,7 +16,8 @@ "@sveltejs/vite-plugin-svelte": "catalog:", "server-side-dep": "file:server-side-dep", "svelte": "catalog:", - "vite": "catalog:" + "vite": "catalog:", + "wrangler": "catalog:" }, "type": "module" } diff --git a/packages/adapter-cloudflare/test/apps/workers/src/routes/read/+server.js b/packages/adapter-cloudflare/test/apps/workers/src/routes/read/+server.js index 06a7b16f7cac..47717c4e0511 100644 --- a/packages/adapter-cloudflare/test/apps/workers/src/routes/read/+server.js +++ b/packages/adapter-cloudflare/test/apps/workers/src/routes/read/+server.js @@ -1,8 +1,6 @@ import { read } from '$app/server'; import file from './file.txt?url'; -export async function GET() { - const asset = read(file); - const text = await asset.text(); - return new Response(text, asset); +export function GET() { + return read(file); } diff --git a/packages/adapter-cloudflare/test/apps/workers/svelte.config.js b/packages/adapter-cloudflare/test/apps/workers/svelte.config.js index 7af75d9f0e0b..26cd6a965908 100644 --- a/packages/adapter-cloudflare/test/apps/workers/svelte.config.js +++ b/packages/adapter-cloudflare/test/apps/workers/svelte.config.js @@ -4,7 +4,7 @@ import adapter from '../../../index.js'; const config = { kit: { adapter: adapter({ - config: './config/wrangler.jsonc' + config: 'config/wrangler.jsonc' }) } }; diff --git a/packages/adapter-cloudflare/test/apps/workers/vite.config.js b/packages/adapter-cloudflare/test/apps/workers/vite.config.js index b80757c292ee..29ad08debe6a 100644 --- a/packages/adapter-cloudflare/test/apps/workers/vite.config.js +++ b/packages/adapter-cloudflare/test/apps/workers/vite.config.js @@ -1,17 +1,11 @@ import { sveltekit } from '@sveltejs/kit/vite'; -import adapter from '../../../index.js'; /** @type {import('vite').UserConfig} */ const config = { build: { minify: false }, - plugins: [sveltekit({ adapter: adapter() })], - server: { - fs: { - allow: ['../../../../kit'] - } - } + plugins: [sveltekit()] }; export default config; diff --git a/packages/adapter-cloudflare/test/utils.js b/packages/adapter-cloudflare/test/utils.js index 4ee245ff335d..dcb3ce52c9d6 100644 --- a/packages/adapter-cloudflare/test/utils.js +++ b/packages/adapter-cloudflare/test/utils.js @@ -9,7 +9,7 @@ export const config = { timeout: process.env.CI ? 45000 : 15000, webServer: { command: process.env.DEV ? 'pnpm dev' : 'pnpm build && pnpm preview', - port: process.env.DEV ? 5173 : 4173 + port: process.env.DEV ? 5173 : 8787 }, retries: process.env.CI ? 2 : number_from_env('KIT_E2E_RETRIES', 0), projects: [ diff --git a/packages/adapter-cloudflare/tsconfig.json b/packages/adapter-cloudflare/tsconfig.json index fd99ca7e30dc..8e8b455d2fd2 100644 --- a/packages/adapter-cloudflare/tsconfig.json +++ b/packages/adapter-cloudflare/tsconfig.json @@ -8,23 +8,21 @@ "module": "nodenext", "baseUrl": ".", "paths": { - "@sveltejs/kit": ["../kit/types/index"], - "types": ["../kit/src/types/internal.d.ts"] + "@sveltejs/kit": ["../kit/types/index"] }, // taken from the Cloudflare Workers TypeScript template https://github.com/cloudflare/workers-sdk/blob/main/packages/create-cloudflare/templates/hello-world/ts/tsconfig.json "target": "es2024", "lib": ["es2024"], - "types": ["@cloudflare/workers-types", "../kit/src/types/virtual.d.ts"] + "types": ["@cloudflare/workers-types"] }, "include": [ "index.js", - "fallback-worker.js", "utils.js", "utils.spec.js", "rolldown.config.js", "vitest.config.js", - "internal.d.ts", "test/utils.js", + "internal.d.ts", "src/worker.js" ] } diff --git a/packages/adapter-cloudflare/utils.js b/packages/adapter-cloudflare/utils.js index eb7caff50dd3..2b1c7c74b567 100644 --- a/packages/adapter-cloudflare/utils.js +++ b/packages/adapter-cloudflare/utils.js @@ -1,6 +1,24 @@ -// TODO: we might not need this since the cloudflare vite plugin will do this for us +import process from 'node:process'; -/** @param {import('wrangler').Unstable_Config} wrangler_config */ +/** + * @param {import('wrangler').Unstable_Config} wrangler_config + * @returns {boolean} + */ +export function is_building_for_cloudflare_pages(wrangler_config) { + if (process.env.CF_PAGES || wrangler_config.pages_build_output_dir) { + return true; + } + + if (!!process.env.WORKERS_CI || wrangler_config.main || wrangler_config.assets) { + return false; + } + + return true; +} + +/** + * @param {import('wrangler').Unstable_Config} wrangler_config + */ export function validate_worker_settings(wrangler_config) { const config_path = wrangler_config.configPath || 'your wrangler.jsonc file'; @@ -11,5 +29,137 @@ export function validate_worker_settings(wrangler_config) { ); } - // TODO: error on cloudflare pages configurations? + // we need the `assets.directory` key so that the static assets are deployed + if ((wrangler_config.main || wrangler_config.assets) && !wrangler_config.assets?.directory) { + throw new Error( + `You must specify the \`assets.directory\` key in ${config_path}. Consult https://developers.cloudflare.com/workers/static-assets/binding/#directory` + ); + } + + // we need the `assets.binding` key so that the Worker can access the static assets + if (wrangler_config.main && !wrangler_config.assets?.binding) { + throw new Error( + `You must specify the \`assets.binding\` key in ${config_path} before deploying your Worker. Consult https://developers.cloudflare.com/workers/static-assets/binding/#binding` + ); + } + + // the user might have forgot the `main` key or should remove the `assets.binding` + // key to deploy static assets without a Worker + if (!wrangler_config.main && wrangler_config.assets?.binding) { + throw new Error( + `You must specify the \`main\` key in ${config_path} if you want to deploy a Worker alongside your static assets. Otherwise, remove the \`assets.binding\` key if you only want to deploy static assets.` + ); + } +} + +/** + * Extracts the redirect source from each line of a [_redirects](https://developers.cloudflare.com/pages/configuration/redirects/) + * file so we can exclude them in [_routes.json](https://developers.cloudflare.com/pages/functions/routing/#create-a-_routesjson-file) + * to ensure the redirect is invoked instead of the Cloudflare Worker. + * @param {string} file_contents + * @returns {string[]} + */ +export function parse_redirects(file_contents) { + /** @type {string[]} */ + const redirects = []; + + for (const line of file_contents.split('\n')) { + const content = line.trim(); + if (!content || content.startsWith('#')) continue; + + const [pathname] = line.split(' '); + // pathnames with placeholders are not supported + if (!pathname || pathname.includes('/:')) { + throw new Error(`The following _redirects rule cannot be excluded by _routes.json: ${line}`); + } + redirects.push(pathname); + } + + return redirects; +} + +/** + * Generates the [_routes.json](https://developers.cloudflare.com/pages/functions/routing/#create-a-_routesjson-file) + * file that dictates which routes invoke the Cloudflare Worker. + * @param {import('@sveltejs/kit').Builder} builder + * @param {string[]} client_assets + * @param {string[]} redirects + * @param {import('./index.js').AdapterOptions['routes']} routes + * @returns {import('./index.js').RoutesJSONSpec} + */ +export function get_routes_json(builder, client_assets, redirects, routes) { + const include = routes?.include ?? ['/*']; + let exclude = routes?.exclude ?? ['']; + + if (!Array.isArray(include) || !Array.isArray(exclude)) { + throw new Error('routes.include and routes.exclude must be arrays'); + } + + if (include?.length === 0) { + throw new Error('routes.include must contain at least one route'); + } + + if (include?.length > 100) { + throw new Error('routes.include must contain 100 or fewer routes'); + } + + /** @type {Set} */ + const transformed_rules = new Set(); + for (const rule of exclude) { + if (rule === '') { + transformed_rules.add(''); + transformed_rules.add(''); + transformed_rules.add(''); + transformed_rules.add(''); + } else { + transformed_rules.add(rule); + } + } + + /** @type {Set} */ + const excluded_routes = new Set(); + for (const rule of transformed_rules) { + if (rule === '') { + const app_path = builder.getAppPath(); + excluded_routes.add(`/${app_path}/version.json`); + excluded_routes.add(`/${app_path}/immutable/*`); + continue; + } + + if (rule === '') { + for (const file of client_assets) { + if (file.startsWith(`${builder.config.kit.appDir}/`)) continue; + excluded_routes.add(`${builder.config.kit.paths.base}/${file}`); + } + continue; + } + + if (rule === '') { + builder.prerendered.paths.forEach((path) => excluded_routes.add(path)); + continue; + } + + if (rule === '') { + redirects.forEach((path) => excluded_routes.add(path)); + continue; + } + + excluded_routes.add(rule); + } + exclude = Array.from(excluded_routes); + + const excess = include.length + exclude.length - 100; + if (excess > 0) { + builder.log.warn( + `Cloudflare Pages Functions' includes/excludes exceeds _routes.json limits (see https://developers.cloudflare.com/pages/platform/functions/routing/#limits). Dropping ${excess} exclude rules — this will cause unnecessary function invocations.` + ); + exclude.length -= excess; + } + + return { + version: 1, + description: 'Generated by @sveltejs/adapter-cloudflare', + include, + exclude + }; } diff --git a/packages/adapter-cloudflare/utils.spec.js b/packages/adapter-cloudflare/utils.spec.js index 26abd64287e4..7d3755559376 100644 --- a/packages/adapter-cloudflare/utils.spec.js +++ b/packages/adapter-cloudflare/utils.spec.js @@ -1,5 +1,83 @@ -import { describe, test, expect } from 'vitest'; -import { validate_worker_settings } from './utils.js'; +import { describe, test, vi, expect } from 'vitest'; +import { + is_building_for_cloudflare_pages, + validate_worker_settings, + get_routes_json, + parse_redirects +} from './utils.js'; + +describe('detects Cloudflare Pages project', () => { + test('by default', () => { + expect( + is_building_for_cloudflare_pages(/** @type {import('wrangler').Unstable_Config} */ ({})) + ).toBe(true); + }); + + test('CF_PAGES environment variable', () => { + vi.stubEnv('CF_PAGES', '1'); + const result = is_building_for_cloudflare_pages( + /** @type {import('wrangler').Unstable_Config} */ ({}) + ); + vi.unstubAllEnvs(); + expect(result).toBe(true); + }); + + test('empty Wrangler configuration file', () => { + expect( + is_building_for_cloudflare_pages( + /** @type {import('wrangler').Unstable_Config} */ ({ + configPath: 'wrangler.jsonc' + }) + ) + ).toBe(true); + }); + + test('pages_build_output_dir config key', () => { + expect( + is_building_for_cloudflare_pages( + /** @type {import('wrangler').Unstable_Config} */ ({ + configPath: 'wrangler.jsonc', + pages_build_output_dir: 'dist' + }) + ) + ).toBe(true); + }); +}); + +describe('detects Cloudflare Workers project', () => { + test('main config key', () => { + expect( + is_building_for_cloudflare_pages( + /** @type {import('wrangler').Unstable_Config} */ ({ + configPath: 'wrangler.jsonc', + main: 'dist/index.js' + }) + ) + ).toBe(false); + }); + + test('assets config key', () => { + expect( + is_building_for_cloudflare_pages( + /** @type {import('wrangler').Unstable_Config} */ ({ + configPath: 'wrangler.jsonc', + assets: { + directory: 'dist/assets' + } + }) + ) + ).toBe(false); + }); + + test('WORKERS_CI environment variable', () => { + vi.stubEnv('WORKERS_CI', '1'); + const result = is_building_for_cloudflare_pages( + /** @type {import('wrangler').Unstable_Config} */ ({}) + ); + vi.unstubAllEnvs(); + expect(result).toBe(false); + }); +}); describe('validates Wrangler config', () => { test('Worker and static assets', () => { @@ -29,4 +107,232 @@ describe('validates Wrangler config', () => { ) ).not.toThrow(); }); + + test('missing `assets.directory` key', () => { + expect(() => + validate_worker_settings( + /** @type {import('wrangler').Unstable_Config} */ ({ + configPath: 'wrangler.jsonc', + main: 'dist/index.js', + assets: { + binding: 'ASSETS' + } + }) + ) + ).toThrow( + `You must specify the \`assets.directory\` key in wrangler.jsonc. Consult https://developers.cloudflare.com/workers/static-assets/binding/#directory` + ); + }); + + test('missing `assets.binding` key', () => { + expect(() => + validate_worker_settings( + /** @type {import('wrangler').Unstable_Config} */ ({ + configPath: 'wrangler.jsonc', + main: 'dist/index.js', + assets: { + directory: 'dist/assets' + } + }) + ) + ).toThrow( + `You must specify the \`assets.binding\` key in wrangler.jsonc before deploying your Worker. Consult https://developers.cloudflare.com/workers/static-assets/binding/#binding` + ); + }); + + test('missing `main` key or should remove `assets.binding` key', () => { + expect(() => + validate_worker_settings( + /** @type {import('wrangler').Unstable_Config} */ ({ + configPath: 'wrangler.jsonc', + assets: { + directory: 'dist/assets', + binding: 'ASSETS' + } + }) + ) + ).toThrow( + `You must specify the \`main\` key in wrangler.jsonc if you want to deploy a Worker alongside your static assets. Otherwise, remove the \`assets.binding\` key if you only want to deploy static assets.` + ); + }); +}); + +test('ignores comments in _redirects file', () => { + const redirects = parse_redirects( + ` +# This is a comment +/home301 / 301 + # Indented comment +/blog/* https://blog.my.domain/:splat +`.trim() + ); + + expect(redirects).toEqual(['/home301', '/blog/*']); +}); + +test('parses _redirects file', () => { + const redirects = parse_redirects( + ` +/home301 / 301 +/notrailing/ /nottrailing 301 + +/blog/* https://blog.my.domain/:splat +`.trim() + ); + + expect(redirects).toEqual(['/home301', '/notrailing/', '/blog/*']); +}); + +test('generates a _routes.json file', () => { + const routes = get_routes_json( + { + getAppPath: () => 'base-path/_app', + config: { + kit: { + appDir: '_app', + paths: { + base: '/base-path', + assets: '', + relative: true + }, + alias: {}, + csrf: { + checkOrigin: true, + trustedOrigins: [] + }, + embedded: false, + files: { + src: 'src', + assets: 'static', + hooks: { + client: 'src/hooks.client.js', + server: 'src/hooks.server.js', + universal: 'src/hooks.js' + }, + lib: 'src/lib', + params: 'src/params', + routes: 'src/routes', + serviceWorker: 'src/service-worker.js', + appTemplate: 'src/app.html', + errorTemplate: 'src/error.html' + }, + inlineStyleThreshold: 0, + moduleExtensions: ['.js', '.ts'], + csp: { + mode: 'auto', + // @ts-ignore + directives: {}, + // @ts-ignore + reportOnly: {} + }, + env: { + dir: '.', + publicPrefix: 'PUBLIC_', + privatePrefix: '' + }, + outDir: '.svelte-kit' + } + }, + prerendered: { + paths: ['/base-path/prerendered'], + pages: new Map(), + assets: new Map(), + redirects: new Map() + } + }, + ['_app/immutable/this-should-not-be-excluded.js', 'robots.txt'], + ['/base-path/redirect'], + undefined + ); + + expect(routes).toEqual({ + version: 1, + description: 'Generated by @sveltejs/adapter-cloudflare', + include: ['/*'], + exclude: [ + '/base-path/_app/version.json', + '/base-path/_app/immutable/*', + '/base-path/robots.txt', + '/base-path/prerendered', + '/base-path/redirect' + ] + }); +}); + +test('truncates excess _routes.json exclude rules', () => { + const routes = get_routes_json( + { + // @ts-ignore + log: { + warn: console.warn + }, + getAppPath: () => 'base-path/_app', + config: { + kit: { + appDir: '_app', + paths: { + base: '/base-path', + assets: '', + relative: true + }, + alias: {}, + csrf: { + checkOrigin: true, + trustedOrigins: [] + }, + embedded: false, + files: { + src: 'src', + assets: 'static', + hooks: { + client: 'src/hooks.client.js', + server: 'src/hooks.server.js', + universal: 'src/hooks.js' + }, + lib: 'src/lib', + params: 'src/params', + routes: 'src/routes', + serviceWorker: 'src/service-worker.js', + appTemplate: 'src/app.html', + errorTemplate: 'src/error.html' + }, + inlineStyleThreshold: 0, + moduleExtensions: ['.js', '.ts'], + csp: { + mode: 'auto', + // @ts-ignore + directives: {}, + // @ts-ignore + reportOnly: {} + }, + env: { + dir: '.', + publicPrefix: 'PUBLIC_', + privatePrefix: '' + }, + outDir: '.svelte-kit' + } + }, + prerendered: { + paths: Array.from({ length: 100 }, (_, i) => `/base-path/blog/post/${i + 1}`), + pages: new Map(), + assets: new Map(), + redirects: new Map() + } + }, + ['_app/immutable/this-should-not-be-excluded.js', 'robots.txt'], + [], + undefined + ); + + expect(routes).toEqual({ + version: 1, + description: 'Generated by @sveltejs/adapter-cloudflare', + include: ['/*'], + exclude: [ + '/base-path/_app/version.json', + '/base-path/_app/immutable/*', + '/base-path/robots.txt' + ].concat(Array.from({ length: 96 }, (_, i) => `/base-path/blog/post/${i + 1}`)) + }); }); From a83420d91dc354c3b8ceb88e493b978bd710290c Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Wed, 25 Mar 2026 02:55:23 +0800 Subject: [PATCH 026/117] split cf changes --- .changeset/angry-swans-talk.md | 5 --- .changeset/old-yaks-refuse.md | 5 --- pnpm-lock.yaml | 61 +++++++++++++++++++--------------- pnpm-workspace.yaml | 2 +- 4 files changed, 35 insertions(+), 38 deletions(-) delete mode 100644 .changeset/angry-swans-talk.md delete mode 100644 .changeset/old-yaks-refuse.md diff --git a/.changeset/angry-swans-talk.md b/.changeset/angry-swans-talk.md deleted file mode 100644 index f0d4ff422946..000000000000 --- a/.changeset/angry-swans-talk.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@sveltejs/adapter-cloudflare': major ---- - -breaking: utilise the Cloudflare Vite plugin diff --git a/.changeset/old-yaks-refuse.md b/.changeset/old-yaks-refuse.md deleted file mode 100644 index e230223fb9d6..000000000000 --- a/.changeset/old-yaks-refuse.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@sveltejs/adapter-cloudflare': major ---- - -breaking: remove support for Cloudflare Pages diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7a8d98e1fb6d..14da74863fc3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -105,6 +105,9 @@ catalogs: vitest: specifier: ^4.1.1 version: 4.1.1 + wrangler: + specifier: ^4.67.0 + version: 4.74.0 importers: @@ -158,20 +161,14 @@ importers: packages/adapter-cloudflare: dependencies: - '@cloudflare/vite-plugin': - specifier: ^1.29.0 - version: 1.29.0(vite@8.0.2(@types/node@24.10.13)(esbuild@0.27.3)(jiti@2.4.2)(yaml@2.8.0))(workerd@1.20260312.1)(wrangler@4.74.0(@cloudflare/workers-types@4.20260317.1)) '@cloudflare/workers-types': - specifier: ^4.20260312.0 + specifier: ^4.20260219.0 version: 4.20260317.1 - esm-env: - specifier: ^1.2.2 - version: 1.2.2 worktop: specifier: 0.8.0-next.18 version: 0.8.0-next.18 wrangler: - specifier: ^4.74.0 + specifier: ^4.67.0 version: 4.74.0(@cloudflare/workers-types@4.20260317.1) devDependencies: '@playwright/test': @@ -196,6 +193,27 @@ importers: specifier: 'catalog:' version: 4.1.1(@opentelemetry/api@1.9.0)(@types/node@24.10.13)(@vitest/browser-playwright@4.1.1)(vite@8.0.2(@types/node@24.10.13)(esbuild@0.27.3)(jiti@2.4.2)(yaml@2.8.0)) + packages/adapter-cloudflare/test/apps/pages: + devDependencies: + '@sveltejs/kit': + specifier: workspace:^ + version: link:../../../../kit + '@sveltejs/vite-plugin-svelte': + specifier: 'catalog:' + version: 7.0.0(svelte@5.53.12)(vite@8.0.2(@types/node@24.10.13)(esbuild@0.27.3)(jiti@2.4.2)(yaml@2.8.0)) + server-side-dep: + specifier: file:server-side-dep + version: file:packages/adapter-cloudflare/test/apps/pages/server-side-dep + svelte: + specifier: 'catalog:' + version: 5.53.12 + vite: + specifier: 'catalog:' + version: 8.0.2(@types/node@24.10.13)(esbuild@0.27.3)(jiti@2.4.2)(yaml@2.8.0) + wrangler: + specifier: 'catalog:' + version: 4.74.0(@cloudflare/workers-types@4.20260317.1) + packages/adapter-cloudflare/test/apps/workers: devDependencies: '@sveltejs/kit': @@ -213,6 +231,9 @@ importers: vite: specifier: 'catalog:' version: 8.0.2(@types/node@24.10.13)(esbuild@0.27.3)(jiti@2.4.2)(yaml@2.8.0) + wrangler: + specifier: 'catalog:' + version: 4.74.0(@cloudflare/workers-types@4.20260317.1) packages/adapter-netlify: dependencies: @@ -1540,12 +1561,6 @@ packages: workerd: optional: true - '@cloudflare/vite-plugin@1.29.0': - resolution: {integrity: sha512-pWaL3vdZadOKTEB15g72YTczq1LT/Pccz3o4k9zGii7eG0yfnRfwVKSBZj7hEuouQz4Cj9JFZK01yydLu56T4A==} - peerDependencies: - vite: ^6.1.0 || ^7.0.0 || ^8.0.0 - wrangler: ^4.74.0 - '@cloudflare/workerd-darwin-64@1.20260312.1': resolution: {integrity: sha512-HUAtDWaqUduS6yasV6+NgsK7qBpP1qGU49ow/Wb117IHjYp+PZPUGReDYocpB4GOMRoQlvdd4L487iFxzdARpw==} engines: {node: '>=16'} @@ -5112,6 +5127,9 @@ packages: engines: {node: '>=10'} hasBin: true + server-side-dep@file:packages/adapter-cloudflare/test/apps/pages/server-side-dep: + resolution: {directory: packages/adapter-cloudflare/test/apps/pages/server-side-dep, type: directory} + server-side-dep@file:packages/adapter-cloudflare/test/apps/workers/server-side-dep: resolution: {directory: packages/adapter-cloudflare/test/apps/workers/server-side-dep, type: directory} @@ -6013,19 +6031,6 @@ snapshots: optionalDependencies: workerd: 1.20260312.1 - '@cloudflare/vite-plugin@1.29.0(vite@8.0.2(@types/node@24.10.13)(esbuild@0.27.3)(jiti@2.4.2)(yaml@2.8.0))(workerd@1.20260312.1)(wrangler@4.74.0(@cloudflare/workers-types@4.20260317.1))': - dependencies: - '@cloudflare/unenv-preset': 2.15.0(unenv@2.0.0-rc.24)(workerd@1.20260312.1) - miniflare: 4.20260312.1 - unenv: 2.0.0-rc.24 - vite: 8.0.2(@types/node@24.10.13)(esbuild@0.27.3)(jiti@2.4.2)(yaml@2.8.0) - wrangler: 4.74.0(@cloudflare/workers-types@4.20260317.1) - ws: 8.18.0 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - workerd - '@cloudflare/workerd-darwin-64@1.20260312.1': optional: true @@ -9617,6 +9622,8 @@ snapshots: semver@7.7.4: {} + server-side-dep@file:packages/adapter-cloudflare/test/apps/pages/server-side-dep: {} + server-side-dep@file:packages/adapter-cloudflare/test/apps/workers/server-side-dep: {} sharp@0.34.5: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 6ef97cc88254..667a7f8fe314 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -49,7 +49,7 @@ catalog: valibot: ^1.1.0 vite: ^8.0.2 vitest: ^4.1.1 - wrangler: ^4.74.0 + wrangler: ^4.67.0 catalogs: # these show up in the ci.yml like `vite: 'beta'`, etc. vite-baseline: From 46aa98e3dc7769511f3dc90a8cb2cb890a1a7e67 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Wed, 25 Mar 2026 02:56:55 +0800 Subject: [PATCH 027/117] changeset --- .changeset/rich-wombats-flash.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/rich-wombats-flash.md diff --git a/.changeset/rich-wombats-flash.md b/.changeset/rich-wombats-flash.md new file mode 100644 index 000000000000..d30acaa595a0 --- /dev/null +++ b/.changeset/rich-wombats-flash.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': major +--- + +breaking: remove `adapter.emulate` From 0b0cfaa2799435c2b026e18ba618f62ae4fe8bfa Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Wed, 25 Mar 2026 03:16:13 +0800 Subject: [PATCH 028/117] always provide ssr dev config --- packages/kit/src/exports/public.d.ts | 2 + packages/kit/src/exports/vite/index.js | 68 +++++++++++++------------- packages/kit/types/index.d.ts | 2 + 3 files changed, 37 insertions(+), 35 deletions(-) diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index 114918028091..792daa1ca725 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -72,6 +72,8 @@ export interface Adapter { emulate?: () => MaybePromise; vite?: { /** + * Add a Vite plugin here to replace the default Node SSR environment. + * The provided Vite plugins should configure the dev and preview servers * @since 3.0.0 */ plugins?: PluginOption; diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index e2fd50bea33f..495d964e7c04 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -512,45 +512,43 @@ function kit({ svelte_config, adapter_in_vite_config }) { // @ts-ignore this prevents a reference error if `client.js` is imported on the server globalThis.__sveltekit_dev = {}; - if (!kit.adapter?.vite?.plugins) { - new_config.environments = { - ssr: { - dev: { - createEnvironment(name, config) { - return createFetchableDevEnvironment(name, config, { - hot: true, - transport: createServerHotChannel(), - async handleRequest(request) { - try { - const module_runner = get_module_runner(); - - const resolved_instrumentation = resolve_entry( - path.join(svelte_config.kit.files.src, 'instrumentation.server') - ); - if (resolved_instrumentation) { - await module_runner.import(resolved_instrumentation); - } - - /** - * @type {{ - * respond: (request: Request, remote_address: string | undefined, kit: import('types').ValidatedKitConfig) => Promise - * }} - */ - const { respond } = await module_runner.import( - import.meta.resolve('./dev/server.js') - ); - return await respond(request, dev_environment?.remote_address, kit); - } catch (error) { - console.error(error); - throw error; + new_config.environments = { + ssr: { + dev: { + createEnvironment(name, config) { + return createFetchableDevEnvironment(name, config, { + hot: true, + transport: createServerHotChannel(), + async handleRequest(request) { + try { + const module_runner = get_module_runner(); + + const resolved_instrumentation = resolve_entry( + path.join(svelte_config.kit.files.src, 'instrumentation.server') + ); + if (resolved_instrumentation) { + await module_runner.import(resolved_instrumentation); } + + /** + * @type {{ + * respond: (request: Request, remote_address: string | undefined, kit: import('types').ValidatedKitConfig) => Promise + * }} + */ + const { respond } = await module_runner.import( + import.meta.resolve('./dev/server.js') + ); + return await respond(request, dev_environment?.remote_address, kit); + } catch (error) { + console.error(error); + throw error; } - }); - } + } + }); } } - }; - } + } + }; } warn_overridden_config(config, new_config); diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 953ec53fb13a..268a288a6e42 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -46,6 +46,8 @@ declare module '@sveltejs/kit' { emulate?: () => MaybePromise; vite?: { /** + * Add a Vite plugin here to replace the default Node SSR environment. + * The provided Vite plugins should configure the dev and preview servers * @since 3.0.0 */ plugins?: PluginOption; From 332a28ff258590bfd65c4df2ccb289a1c26d0632 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Wed, 25 Mar 2026 03:16:18 +0800 Subject: [PATCH 029/117] leftover --- .../adapter-cloudflare/fallback-worker.js | 123 ------------------ 1 file changed, 123 deletions(-) delete mode 100644 packages/adapter-cloudflare/fallback-worker.js diff --git a/packages/adapter-cloudflare/fallback-worker.js b/packages/adapter-cloudflare/fallback-worker.js deleted file mode 100644 index a5e861be23b5..000000000000 --- a/packages/adapter-cloudflare/fallback-worker.js +++ /dev/null @@ -1,123 +0,0 @@ -import { Server } from '__sveltekit/dev'; -import { manifest, prerendered, base_path } from '__sveltekit/ssr-manifest'; -import { env } from 'cloudflare:workers'; -import { DEV } from 'esm-env'; -import * as Cache from 'worktop/cfw.cache'; - -const server = new Server(manifest); - -const app_path = `/${manifest.appPath}`; - -const immutable = `${app_path}/immutable/`; -const version_file = `${app_path}/version.json`; - -/** - * We don't know the origin until we receive a request, but - * that's guaranteed to happen before we call `read` - * @type {string} - */ -let origin; - -const initialized = server.init({ - // @ts-expect-error env contains the environment variables we need but also bindings - env, - read: async (file) => { - const url = DEV ? `${origin}${immutable}${file}` : `${origin}/${file}`; - const response = await /** @type {{ ASSETS: { fetch: typeof fetch } }} */ (env).ASSETS.fetch( - url - ); - - if (!response.ok) { - throw new Error( - `read(...) failed: could not fetch ${url} (${response.status} ${response.statusText})` - ); - } - - return response.body; - } -}); - -export default { - /** - * @param {Request} req - * @param {{ ASSETS: { fetch: typeof fetch } }} env - * @param {ExecutionContext} ctx - * @returns {Promise} - */ - async fetch(req, env, ctx) { - if (!origin) { - origin = new URL(req.url).origin; - } - - // always await initialization to prevent race condition with concurrent requests - await initialized; - - // skip cache if "cache-control: no-cache" in request - let pragma = req.headers.get('cache-control') || ''; - let res = !pragma.includes('no-cache') && (await Cache.lookup(req)); - if (res) return res; - - let { pathname, search } = new URL(req.url); - try { - pathname = decodeURIComponent(pathname); - } catch { - // ignore invalid URI - } - - const stripped_pathname = pathname.replace(/\/$/, ''); - - // files in /static, the service worker, and Vite imported server assets - let is_static_asset = false; - const filename = stripped_pathname.slice(base_path.length + 1); - if (filename) { - is_static_asset = - manifest.assets.has(filename) || - manifest.assets.has(filename + '/index.html') || - filename in manifest._.server_assets || - filename + '/index.html' in manifest._.server_assets; - } - - let location = pathname.at(-1) === '/' ? stripped_pathname : pathname + '/'; - - if ( - is_static_asset || - prerendered.has(pathname) || - pathname === version_file || - pathname.startsWith(immutable) - ) { - res = await env.ASSETS.fetch(req); - } else if (location && prerendered.has(location)) { - // trailing slash redirect for prerendered pages - if (search) location += search; - res = new Response('', { - status: 308, - headers: { - location - } - }); - } else { - // dynamically-generated pages - res = await server.respond(req, { - platform: { - env, - ctx, - // @ts-expect-error webworker types from worktop are not compatible with Cloudflare Workers types - caches, - // @ts-expect-error the type is correct but ts is confused because platform.cf uses the type from index.ts while req.cf uses the type from index.d.ts - cf: req.cf - }, - getClientAddress() { - return /** @type {string} */ (req.headers.get('cf-connecting-ip')); - } - }); - } - - // write to `Cache` only if response is not an error, - // let `Cache.save` handle the Cache-Control and Vary headers - pragma = res.headers.get('cache-control') || ''; - return pragma && res.status < 400 ? Cache.save(req, res, ctx) : res; - } -}; - -// Without this, server file changes will invalidate the entire server module graph -import.meta.hot?.accept(); From 4d21e647fcc161840c34d96619d2577416c0d106 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Wed, 25 Mar 2026 03:16:42 +0800 Subject: [PATCH 030/117] leftover --- .../test/apps/workers/src/routes/remote/+page.svelte | 1 - 1 file changed, 1 deletion(-) delete mode 100644 packages/adapter-cloudflare/test/apps/workers/src/routes/remote/+page.svelte diff --git a/packages/adapter-cloudflare/test/apps/workers/src/routes/remote/+page.svelte b/packages/adapter-cloudflare/test/apps/workers/src/routes/remote/+page.svelte deleted file mode 100644 index d9254aadb2de..000000000000 --- a/packages/adapter-cloudflare/test/apps/workers/src/routes/remote/+page.svelte +++ /dev/null @@ -1 +0,0 @@ - From 6859adf8d1797ef4758f1f6f0560ee33fae6101e Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Wed, 25 Mar 2026 03:45:18 +0800 Subject: [PATCH 031/117] split into new pr --- .../client/remote-functions/query.svelte.js | 8 ++++---- packages/kit/src/runtime/shared.js | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/packages/kit/src/runtime/client/remote-functions/query.svelte.js b/packages/kit/src/runtime/client/remote-functions/query.svelte.js index 90fc289162be..cd58a828d9e6 100644 --- a/packages/kit/src/runtime/client/remote-functions/query.svelte.js +++ b/packages/kit/src/runtime/client/remote-functions/query.svelte.js @@ -2,12 +2,12 @@ /** @import { RemoteFunctionResponse } from 'types' */ import * as devalue from 'devalue'; import { DEV } from 'esm-env'; -import { tick, untrack, hydratable } from 'svelte'; +import { tick, untrack } from 'svelte'; import { HttpError, Redirect } from '@sveltejs/kit/internal'; import { get_remote_request_headers, remote_request } from './shared.svelte.js'; import { app, goto, query_map, query_responses } from '../client.js'; import { app_dir, base } from '../../app/paths/internal/client.js'; -import { create_remote_key, stringify_remote_arg } from '../../shared.js'; +import { create_remote_key, stringify_remote_arg, unfriendly_hydratable } from '../../shared.js'; /** * @template T @@ -49,7 +49,7 @@ export function query(id) { return new QueryProxy(id, arg, async (key, payload) => { const url = `${base}/${app_dir}/remote/${id}${payload ? `?payload=${payload}` : ''}`; - const serialized = await hydratable(key, () => + const serialized = await unfriendly_hydratable(key, () => remote_request(url, get_remote_request_headers()) ); @@ -69,7 +69,7 @@ export function query_batch(id) { return (arg) => { return new QueryProxy(id, arg, async (key, payload) => { - const serialized = await hydratable(key, () => { + const serialized = await unfriendly_hydratable(key, () => { return new Promise((resolve, reject) => { // create_remote_function caches identical calls, but in case a refresh to the same query is called multiple times this function // is invoked multiple times with the same payload, so we need to deduplicate here diff --git a/packages/kit/src/runtime/shared.js b/packages/kit/src/runtime/shared.js index dd6895571f94..a38f9249ac7f 100644 --- a/packages/kit/src/runtime/shared.js +++ b/packages/kit/src/runtime/shared.js @@ -1,6 +1,7 @@ /** @import { Transport } from '@sveltejs/kit' */ import * as devalue from 'devalue'; import { base64_decode, base64_encode, text_decoder } from './utils.js'; +import * as svelte from 'svelte'; /** * @param {string} route_id @@ -91,3 +92,17 @@ export function parse_remote_arg(string, transport) { export function create_remote_key(id, payload) { return id + '/' + payload; } + +/** + * @template T + * @param {string} key + * @param {() => T} fn + * @returns {T} + * @deprecated TODO remove in SvelteKit 3.0 + */ +export function unfriendly_hydratable(key, fn) { + if (!svelte.hydratable) { + throw new Error('Remote functions require Svelte 5.44.0 or later'); + } + return svelte.hydratable(key, fn); +} From 4f9d5431f359e426d455e3a7b0419251b0f662c4 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Wed, 25 Mar 2026 03:45:30 +0800 Subject: [PATCH 032/117] no need to generate types for internal --- packages/kit/scripts/generate-dts.js | 2 - packages/kit/types/index.d.ts | 266 +-------------------------- 2 files changed, 9 insertions(+), 259 deletions(-) diff --git a/packages/kit/scripts/generate-dts.js b/packages/kit/scripts/generate-dts.js index f036f3be28a7..aaf4721e1eaf 100644 --- a/packages/kit/scripts/generate-dts.js +++ b/packages/kit/scripts/generate-dts.js @@ -8,8 +8,6 @@ await createBundle({ '@sveltejs/kit/hooks': 'src/exports/hooks/index.js', '@sveltejs/kit/node': 'src/exports/node/index.js', '@sveltejs/kit/vite': 'src/exports/vite/index.js', - '@sveltejs/kit/internal': 'src/exports/internal/index.js', - '@sveltejs/kit/internal/server': 'src/exports/internal/server.js', '$app/environment': 'src/runtime/app/environment/types.d.ts', '$app/forms': 'src/runtime/app/forms.js', '$app/navigation': 'src/runtime/app/navigation.js', diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 268a288a6e42..b3af110dcaac 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -2877,261 +2877,6 @@ declare module '@sveltejs/kit/vite' { export {}; } -declare module '@sveltejs/kit/internal' { - import type { StandardSchemaV1 } from '@standard-schema/spec'; - export class HttpError { - - constructor(status: number, body: { - message: string; - } extends App.Error ? (App.Error | string | undefined) : App.Error); - status: number; - body: App.Error; - toString(): string; - } - export class Redirect { - - constructor(status: 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308, location: string); - status: 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308; - location: string; - } - /** - * An error that was thrown from within the SvelteKit runtime that is not fatal and doesn't result in a 500, such as a 404. - * `SvelteKitError` goes through `handleError`. - * */ - export class SvelteKitError extends Error { - - constructor(status: number, text: string, message: string); - status: number; - text: string; - } - - export class ActionFailure { - - constructor(status: number, data: T); - status: number; - data: T; - } - /** - * Error thrown when form validation fails imperatively - */ - export class ValidationError extends Error { - - constructor(issues: StandardSchemaV1.Issue[]); - issues: StandardSchemaV1.Issue[]; - } - export function init_remote_functions(module: Record, file: string, hash: string): void; - - export {}; -} - -declare module '@sveltejs/kit/internal/server' { - import type { RequestEvent, Config, Handle, HandleServerError, KitConfig, HandleFetch, Reroute, Adapter, ServerInit, Transport, HandleValidationError } from '@sveltejs/kit'; - import type { Span } from '@opentelemetry/api'; - export function merge_tracing(event_like: T, current: import("@opentelemetry/api").Span): T; - /** - * Returns the current `RequestEvent`. Can be used inside server hooks, server `load` functions, actions, and endpoints (and functions called by them). - * - * In environments without [`AsyncLocalStorage`](https://nodejs.org/api/async_context.html#class-asynclocalstorage), this must be called synchronously (i.e. not after an `await`). - * @since 2.20.0 - * - * */ - export function getRequestEvent(): RequestEvent; - export function get_request_store(): RequestStore; - export function try_get_request_store(): RequestStore | null; - - export function with_request_store(store: RequestStore | null, fn: () => T): T; - interface ServerHooks { - handleFetch: HandleFetch; - handle: Handle; - handleError: HandleServerError; - handleValidationError: HandleValidationError; - reroute: Reroute; - transport: Transport; - init?: ServerInit; - } - - interface PrerenderDependency { - response: Response; - body: null | string | Uint8Array; - } - - interface PrerenderOptions { - cache?: string; // including this here is a bit of a hack, but it makes it easy to add - fallback?: boolean; - dependencies: Map; - /** - * For each key the (possibly still pending) result of a prerendered remote function. - * Used to deduplicate requests to the same remote function with the same arguments. - */ - remote_responses: Map>; - /** True for the duration of a call to the `reroute` hook */ - inside_reroute?: boolean; - } - - type RecursiveRequired = { - // Recursive implementation of TypeScript's Required utility type. - // Will recursively continue until it reaches a primitive or Function - // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type - [K in keyof T]-?: Extract extends never // If it does not have a Function type - ? RecursiveRequired // recursively continue through. - : T[K]; // Use the exact type for everything else - }; - - interface SSRComponent { - default: { - render( - props: Record, - opts: { context: Map; csp?: { nonce?: string; hash?: boolean } } - ): { - html: string; - head: string; - css: { - code: string; - map: any; // TODO - }; - /** Until we require all Svelte versions that support hashes, this might not be defined */ - hashes?: { - script: Array<`sha256-${string}`>; - }; - }; - }; - } - - interface SSROptions { - app_template_contains_nonce: boolean; - async: boolean; - csp: ValidatedConfig['kit']['csp']; - csrf_check_origin: boolean; - csrf_trusted_origins: string[]; - embedded: boolean; - env_public_prefix: string; - env_private_prefix: string; - hash_routing: boolean; - hooks: ServerHooks; - root: SSRComponent['default']; - service_worker: boolean; - service_worker_options: RegistrationOptions; - server_error_boundaries: boolean; - templates: { - app(values: { - head: string; - body: string; - assets: string; - nonce: string; - env: Record; - }): string; - error(values: { message: string; status: number }): string; - }; - version_hash: string; - } - type RemotePrerenderInputsGenerator = () => MaybePromise; - - type ValidatedConfig = Config & { - kit: ValidatedKitConfig; - extensions: string[]; - }; - - type ValidatedKitConfig = Omit, 'adapter'> & { - adapter?: Adapter; - }; - - type BinaryFormMeta = { - remote_refreshes?: string[]; - validate_only?: boolean; - }; - - interface BaseRemoteInternals { - type: string; - id: string; - name: string; - } - - interface RemoteQueryInternals extends BaseRemoteInternals { - type: 'query'; - } - interface RemoteQueryLiveInternals extends BaseRemoteInternals { - type: 'query_live'; - run( - event: RequestEvent, - state: RequestState, - arg: any - ): Promise<{ iterator: AsyncIterator; cancel: () => void }>; - } - - interface RemoteQueryBatchInternals extends BaseRemoteInternals { - type: 'query_batch'; - run: (args: any[], options: SSROptions) => Promise; - } - - interface RemoteCommandInternals extends BaseRemoteInternals { - type: 'command'; - } - - interface RemoteFormInternals extends BaseRemoteInternals { - type: 'form'; - fn(body: Record, meta: BinaryFormMeta, form_data: FormData | null): Promise; - } - - interface RemotePrerenderInternals extends BaseRemoteInternals { - type: 'prerender'; - has_arg: boolean; - dynamic?: boolean; - inputs?: RemotePrerenderInputsGenerator; - } - - type RemoteInternals = - | RemoteQueryInternals - | RemoteQueryLiveInternals - | RemoteQueryBatchInternals - | RemoteCommandInternals - | RemoteFormInternals - | RemotePrerenderInternals; - - type RecordSpan = (options: { - name: string; - attributes: Record; - fn: (current: Span) => Promise; - }) => Promise; - - /** - * Internal state associated with the current `RequestEvent`, - * used for tracking things like remote function calls - */ - interface RequestState { - readonly prerendering: PrerenderOptions | undefined; - readonly transport: ServerHooks['transport']; - readonly handleValidationError: ServerHooks['handleValidationError']; - readonly tracing: { - record_span: RecordSpan; - }; - readonly remote: { - data: null | Map< - RemoteInternals, - Record }> - >; - forms: null | Map; - refreshes: null | Record>; - }; - readonly is_in_remote_function: boolean; - readonly is_in_render: boolean; - readonly is_in_universal_load: boolean; - } - - interface RequestStore { - event: RequestEvent; - state: RequestState; - } - type MaybePromise = T | Promise; - - export {}; -} - declare module '$app/environment' { /** * `true` if the app is running in the browser. @@ -3443,9 +3188,8 @@ declare module '$app/paths' { } declare module '$app/server' { - import type { RemoteCommand, RemoteForm, RemoteFormInput, InvalidField, RemotePrerenderFunction, RemoteQueryFunction } from '@sveltejs/kit'; + import type { RequestEvent, RemoteCommand, RemoteForm, RemoteFormInput, InvalidField, RemotePrerenderFunction, RemoteQueryFunction } from '@sveltejs/kit'; import type { StandardSchemaV1 } from '@standard-schema/spec'; - export { getRequestEvent } from '@sveltejs/kit/internal/server'; /** * Read the contents of an imported asset from the filesystem * @example @@ -3459,6 +3203,14 @@ declare module '$app/server' { * @since 2.4.0 */ export function read(asset: string): Response; + /** + * Returns the current `RequestEvent`. Can be used inside server hooks, server `load` functions, actions, and endpoints (and functions called by them). + * + * In environments without [`AsyncLocalStorage`](https://nodejs.org/api/async_context.html#class-asynclocalstorage), this must be called synchronously (i.e. not after an `await`). + * @since 2.20.0 + * + * */ + export function getRequestEvent(): RequestEvent; /** * Creates a remote command. When called from the browser, the function will be invoked on the server via a `fetch` call. * From 48a1d41c8c7be9d96a87a4df68ca4b24aa5b4c32 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Wed, 25 Mar 2026 04:29:49 +0800 Subject: [PATCH 033/117] fix up paths in error stack traces --- packages/kit/src/exports/vite/dev/server.js | 2 +- packages/kit/src/exports/vite/index.js | 9 +++--- packages/kit/src/exports/vite/module_ids.js | 2 +- packages/kit/src/runtime/server/utils.js | 24 +++++++--------- packages/kit/src/types/virtual.d.ts | 4 ++- packages/kit/src/utils/path.js | 15 ++++++++++ packages/kit/src/utils/path.spec.js | 32 +++++++++++++++++++++ 7 files changed, 68 insertions(+), 20 deletions(-) create mode 100644 packages/kit/src/utils/path.js create mode 100644 packages/kit/src/utils/path.spec.js diff --git a/packages/kit/src/exports/vite/dev/server.js b/packages/kit/src/exports/vite/dev/server.js index 933528e88ae9..252f861b7bce 100644 --- a/packages/kit/src/exports/vite/dev/server.js +++ b/packages/kit/src/exports/vite/dev/server.js @@ -1,6 +1,6 @@ import fs from 'node:fs'; import path from 'node:path'; -import { Server } from '__sveltekit/dev'; +import { Server } from '__sveltekit/server'; import { env, manifest } from '__sveltekit/ssr-manifest'; import { createReadableStream } from '@sveltejs/kit/node'; import { from_fs } from '../filesystem.js'; diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 495d964e7c04..1b02c42a1da4 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -49,7 +49,7 @@ import { env_static_private, env_static_public, service_worker, - sveltekit_dev, + sveltekit_server, sveltekit_ipc, sveltekit_remotes, sveltekit_server_assets, @@ -624,7 +624,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { exactRegex(env_dynamic_private), exactRegex(env_dynamic_public), exactRegex(service_worker), - exactRegex(sveltekit_dev), + exactRegex(sveltekit_server), exactRegex(sveltekit_ssr_manifest), exactRegex(sveltekit_server_assets), exactRegex(sveltekit_remotes), @@ -710,6 +710,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { export const base_path = ${s(kit.paths.base)}; export const prerendered = new Set(); export const env = ${s(env)}; + export const root = ${s(root)}; const nodes = ${s(devalue.uneval(manifest_data.nodes, revive_functions))} @@ -948,7 +949,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { `; } - case sveltekit_dev: { + case sveltekit_server: { if (!dev_environment) return; const runtime_base = get_runtime_base(root); @@ -964,7 +965,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { const async_local_storage = new AsyncLocalStorage(); - const adapter = ${adapter ? devalue.uneval({ name: adapter.name, supports: adapter.supports }, revive_functions) : null}; + const adapter = ${adapter ? s(devalue.uneval({ name: adapter.name, supports: adapter.supports }, revive_functions)) : null}; globalThis.__SVELTEKIT_TRACK__ = (label) => { const context = async_local_storage.getStore(); diff --git a/packages/kit/src/exports/vite/module_ids.js b/packages/kit/src/exports/vite/module_ids.js index ccf30f06d634..d2918d9a1a06 100644 --- a/packages/kit/src/exports/vite/module_ids.js +++ b/packages/kit/src/exports/vite/module_ids.js @@ -8,7 +8,7 @@ export const env_dynamic_public = '\0virtual:env/dynamic/public'; export const service_worker = '\0virtual:service-worker'; -export const sveltekit_dev = '\0virtual:__sveltekit/dev'; +export const sveltekit_server = '\0virtual:__sveltekit/server'; export const sveltekit_ssr_manifest = '\0virtual:__sveltekit/ssr-manifest'; export const sveltekit_server_assets = '\0virtual:__sveltekit/server-assets'; export const sveltekit_remotes = '\0virtual:__sveltekit/remotes'; diff --git a/packages/kit/src/runtime/server/utils.js b/packages/kit/src/runtime/server/utils.js index dc18138df894..6674da8d2322 100644 --- a/packages/kit/src/runtime/server/utils.js +++ b/packages/kit/src/runtime/server/utils.js @@ -6,6 +6,7 @@ import { coalesce_to_error, get_message, get_status } from '../../utils/error.js import { negotiate } from '../../utils/http.js'; import { ENDPOINT_METHODS } from '../../constants.js'; import { escape_html } from '../../utils/escape.js'; +import * as path from '../../utils/path.js'; /** @param {any} body */ export function is_pojo(body) { @@ -209,23 +210,20 @@ export function format_server_error(status, error, event) { return `${formatted_text}\n${DEV ? clean_up_stack_trace(error) : error.stack}`; } -// TODO: do we still need to clean up stack traces when using the environment API? /** * In dev, tidy up stack traces by making paths relative to the current project directory * @param {string} file */ -const relative = (file) => file; - -// if (DEV) { -// try { -// // TODO: node:path not available in workerd -// const path = await import('node:path'); - -// relative = (file) => path.relative('.', file); -// } catch { -// // do nothing -// } -// } +let relative = (file) => file; + +if (DEV) { + try { + const { root } = await import('__sveltekit/ssr-manifest'); + relative = (file) => path.relative(root, file); + } catch { + // do nothing + } +} /** * Provides a refined stack trace by excluding lines following the last occurrence of a line containing +page. +layout. or +server. diff --git a/packages/kit/src/types/virtual.d.ts b/packages/kit/src/types/virtual.d.ts index 8c6e07940d92..67afea6c688c 100644 --- a/packages/kit/src/types/virtual.d.ts +++ b/packages/kit/src/types/virtual.d.ts @@ -6,9 +6,11 @@ declare module '__sveltekit/ssr-manifest' { export const remote_address: string | undefined; export const base_path: string; export const prerendered: Set; + /** Allows us to fix up stack traces during development */ + export const root: string; } -declare module '__sveltekit/dev' { +declare module '__sveltekit/server' { import { InternalServer } from 'types'; export { InternalServer as Server }; diff --git a/packages/kit/src/utils/path.js b/packages/kit/src/utils/path.js new file mode 100644 index 000000000000..a8cefe15f039 --- /dev/null +++ b/packages/kit/src/utils/path.js @@ -0,0 +1,15 @@ +// This file contains Node-agnostic path utilities so that it can be used in +// environments that do not have access to `node:path` (e.g. Cloudflare Workers). + +/** + * @param {string} from + * @param {string} to + * @returns {string} + */ +export function relative(from, to) { + const from_parts = from.split('/').filter(Boolean); + const to_parts = to.split('/').filter(Boolean); + let i = 0; + while (i < from_parts.length && i < to_parts.length && from_parts[i] === to_parts[i]) i++; + return [...Array(from_parts.length - i).fill('..'), ...to_parts.slice(i)].join('/') || '.'; +} diff --git a/packages/kit/src/utils/path.spec.js b/packages/kit/src/utils/path.spec.js new file mode 100644 index 000000000000..cfadb5a62247 --- /dev/null +++ b/packages/kit/src/utils/path.spec.js @@ -0,0 +1,32 @@ +import { assert, describe } from 'vitest'; +import { relative } from './path.js'; + +describe('relative', (test) => { + test('same path returns .', () => { + assert.equal(relative('/a/b/c', '/a/b/c'), '.'); + }); + + test('sibling path', () => { + assert.equal(relative('/a/b', '/a/c'), '../c'); + }); + + test('child path', () => { + assert.equal(relative('/a', '/a/b/c'), 'b/c'); + }); + + test('parent path', () => { + assert.equal(relative('/a/b/c', '/a'), '../..'); + }); + + test('unrelated paths', () => { + assert.equal(relative('/a/b', '/c/d'), '../../c/d'); + }); + + test('root to child', () => { + assert.equal(relative('/', '/a/b'), 'a/b'); + }); + + test('child to root', () => { + assert.equal(relative('/a/b', '/'), '../..'); + }); +}); From 195ddd78ac5ff647acf807d219b74b54c9414c39 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Thu, 26 Mar 2026 07:10:07 +0800 Subject: [PATCH 034/117] fix relative paths on server --- packages/kit/src/exports/vite/index.js | 4 ++-- packages/kit/src/runtime/server/utils.js | 2 +- packages/kit/src/types/global-private.d.ts | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 1b02c42a1da4..78321de29266 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -506,7 +506,8 @@ function kit({ svelte_config, adapter_in_vite_config }) { __SVELTEKIT_APP_VERSION_POLL_INTERVAL__: '0', __SVELTEKIT_PAYLOAD__: 'globalThis.__sveltekit_dev', __SVELTEKIT_HAS_SERVER_LOAD__: 'true', - __SVELTEKIT_HAS_UNIVERSAL_LOAD__: 'true' + __SVELTEKIT_HAS_UNIVERSAL_LOAD__: 'true', + __SVELTEKIT_ROOT__: s(root) }; // @ts-ignore this prevents a reference error if `client.js` is imported on the server @@ -710,7 +711,6 @@ function kit({ svelte_config, adapter_in_vite_config }) { export const base_path = ${s(kit.paths.base)}; export const prerendered = new Set(); export const env = ${s(env)}; - export const root = ${s(root)}; const nodes = ${s(devalue.uneval(manifest_data.nodes, revive_functions))} diff --git a/packages/kit/src/runtime/server/utils.js b/packages/kit/src/runtime/server/utils.js index 6674da8d2322..57d533b3f5d7 100644 --- a/packages/kit/src/runtime/server/utils.js +++ b/packages/kit/src/runtime/server/utils.js @@ -218,7 +218,7 @@ let relative = (file) => file; if (DEV) { try { - const { root } = await import('__sveltekit/ssr-manifest'); + const root = typeof __SVELTEKIT_ROOT__ === 'string' ? __SVELTEKIT_ROOT__ : ''; relative = (file) => path.relative(root, file); } catch { // do nothing diff --git a/packages/kit/src/types/global-private.d.ts b/packages/kit/src/types/global-private.d.ts index 4aa49da38679..358f6feede47 100644 --- a/packages/kit/src/types/global-private.d.ts +++ b/packages/kit/src/types/global-private.d.ts @@ -43,6 +43,8 @@ declare global { /** Resolve a placeholder promise */ resolve?: (data: { id: number; data: any; error: any }) => void; }; + /** Allows us to resolve paths relative to the Vite root setting during development */ + const __SVELTEKIT_ROOT__: string; /** * This makes the use of specific features visible at both dev and build time, in such a * way that we can error when they are not supported by the target platform. From 8528b9931011b95de565852b3413298639813fef Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Thu, 26 Mar 2026 07:17:05 +0800 Subject: [PATCH 035/117] promise with resolvers --- packages/kit/src/exports/vite/index.js | 40 ++++++++++++++------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 78321de29266..f1e466f4c7fa 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -827,17 +827,19 @@ function kit({ svelte_config, adapter_in_vite_config }) { const event = 'sveltekit:inline-styles-node-${index}'; result.inline_styles = async () => { - return new Promise((resolve) => { - if (!import.meta.hot) return; + if (!import.meta.hot) return; - const listener = (data) => { - import.meta.hot.off(event, listener); - resolve(data); - }; + const { promise, resolve } = Promise.withResolvers(); - import.meta.hot.on(event, listener); - import.meta.hot.send('sveltekit:inline-styles', { urls, node: result.index }); - }); + const listener = (data) => { + import.meta.hot.off(event, listener); + resolve(data); + }; + + import.meta.hot.on(event, listener); + import.meta.hot.send('sveltekit:inline-styles', { urls, node: result.index }); + + return promise; } return result; @@ -882,18 +884,20 @@ function kit({ svelte_config, adapter_in_vite_config }) { }) ).join(',\n')}], matchers: async () => { + if (!import.meta.hot) return; + const event = 'sveltekit:matchers-response'; - return new Promise((resolve) => { - if (!import.meta.hot) return; + const { promise, resolve } = Promise.withResolvers(); - const listener = (data) => { - import.meta.hot.off(event, listener); - resolve(data); - }; + const listener = (data) => { + import.meta.hot.off(event, listener); + resolve(data); + }; + + import.meta.hot.on(event, listener); + import.meta.hot.send('sveltekit:matchers-request'); - import.meta.hot.on(event, listener); - import.meta.hot.send('sveltekit:matchers-request'); - }); + return promise; } } }; From 4f5fa314ce981c29c8cafb6641f01c0d71d53382 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Thu, 26 Mar 2026 10:33:07 +0800 Subject: [PATCH 036/117] fix remotes detection --- packages/kit/src/exports/vite/index.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index f1e466f4c7fa..361f35fc8dfd 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -712,7 +712,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { export const prerendered = new Set(); export const env = ${s(env)}; - const nodes = ${s(devalue.uneval(manifest_data.nodes, revive_functions))} + const nodes = ${devalue.uneval(manifest_data.nodes, revive_functions)} export const manifest = { appDir: ${s(kit.appDir)}, @@ -969,7 +969,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { const async_local_storage = new AsyncLocalStorage(); - const adapter = ${adapter ? s(devalue.uneval({ name: adapter.name, supports: adapter.supports }, revive_functions)) : null}; + const adapter = ${adapter ? devalue.uneval({ name: adapter.name, supports: adapter.supports }, revive_functions) : null}; globalThis.__SVELTEKIT_TRACK__ = (label) => { const context = async_local_storage.getStore(); @@ -1228,6 +1228,8 @@ function kit({ svelte_config, adapter_in_vite_config }) { file }; + if (this.environment.name === 'ssr') remotes.push(remote); + 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 @@ -1292,7 +1294,6 @@ function kit({ svelte_config, adapter_in_vite_config }) { // being called again with `opts.ssr === true` if the module isn't // already loaded) so we can determine what it exports if (dev_environment?.vite) { - remotes.push(remote); dev_environment.vite.environments.ssr.hot.send(`sveltekit:remotes`, remote); invalidate_module(dev_environment.vite, sveltekit_remotes); From c15f3c2cbdd855d25bc4cf2243385f861f4569f1 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Thu, 26 Mar 2026 18:15:39 +0800 Subject: [PATCH 037/117] fix remotes population --- packages/kit/src/exports/vite/index.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 361f35fc8dfd..c1363cb783d4 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -1166,7 +1166,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { }; /** @type {Array<{ hash: string, file: string }>} */ - const remotes = []; + let remotes = []; /** @type {Map} Maps remote hash -> original module id */ const remote_original_by_hash = new Map(); @@ -1228,7 +1228,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { file }; - if (this.environment.name === 'ssr') remotes.push(remote); + remotes.push(remote); if (this.environment.config.consumer !== 'client') { // we need to add an `await Promise.resolve()` because if the user imports this function @@ -1986,6 +1986,9 @@ function kit({ svelte_config, adapter_in_vite_config }) { } } + // deduplicate remotes because the same hash may be pushed from both the client and server builds + remotes = Array.from(new Set(remotes)); + // regenerate manifest now that we have client entry... fs.writeFileSync( manifest_path, From 6a7bdf1c123922cb4d3e183b6f200ee14814c4ba Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Thu, 26 Mar 2026 18:37:06 +0800 Subject: [PATCH 038/117] fix dev remotes analysis --- packages/kit/src/exports/vite/index.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index c1363cb783d4..efb159d278ec 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -852,7 +852,9 @@ function kit({ svelte_config, adapter_in_vite_config }) { return Object.fromEntries( remotes.map((remote) => [ remote.hash, - () => import(/* @vite-ignore */(\`${root}/\${remote.file}\`)).then((module) => ({ default: module })) + () => import(/* @vite-ignore */(\`${root}/\${remote.file}\`)).then( + (module) => ({ default: module }) + ) ]) ); }, @@ -1229,6 +1231,10 @@ function kit({ svelte_config, adapter_in_vite_config }) { }; remotes.push(remote); + if (dev_environment) { + dev_environment.vite.environments.ssr.hot.send(`sveltekit:remotes`, remote); + invalidate_module(dev_environment.vite, sveltekit_remotes); + } if (this.environment.config.consumer !== 'client') { // we need to add an `await Promise.resolve()` because if the user imports this function @@ -1294,9 +1300,6 @@ function kit({ svelte_config, adapter_in_vite_config }) { // being called again with `opts.ssr === true` if the module isn't // already loaded) so we can determine what it exports if (dev_environment?.vite) { - dev_environment.vite.environments.ssr.hot.send(`sveltekit:remotes`, remote); - invalidate_module(dev_environment.vite, sveltekit_remotes); - await dev_environment.vite.environments.ssr.transformRequest(id); const exports = await get_module_data(dev_environment.vite, `remote-${remote.hash}`); From 1a423ebd7da42d734a5336c8efea6d49287a0d91 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Fri, 27 Mar 2026 03:08:50 +0800 Subject: [PATCH 039/117] fix reading files outside app root in dev --- packages/kit/src/exports/vite/index.js | 36 ++++++++++++++------------ 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index efb159d278ec..ccd9d7a28a65 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -698,6 +698,22 @@ function kit({ svelte_config, adapter_in_vite_config }) { `; } + case sveltekit_ipc: { + return dedent` + export function send(event_id, data) { + if (!import.meta.hot) return; + + const event = 'sveltekit:' + event_id; + const listener = () => { + import.meta.hot.send(event, data); + import.meta.hot.off(event, listener); + }; + + import.meta.hot.on(event, listener); + } + `; + } + case sveltekit_ssr_manifest: { if (!dev_environment) return; @@ -1005,22 +1021,6 @@ function kit({ svelte_config, adapter_in_vite_config }) { } `; } - - case sveltekit_ipc: { - return dedent` - export function send(event_id, data) { - if (!import.meta.hot) return; - - const event = 'sveltekit:' + event_id; - const listener = () => { - import.meta.hot.send(event, data); - import.meta.hot.off(event, listener); - }; - - import.meta.hot.on(event, listener); - } - `; - } } } } @@ -2116,7 +2116,9 @@ function kit({ svelte_config, adapter_in_vite_config }) { (searchParams.has('url') || vite_config.assetsInclude(pathname)) && fs.existsSync(pathname) ) { - const filepath = path.relative(root, pathname); + const filepath = pathname.startsWith(root) + ? path.relative(root, pathname) + : to_fs(pathname); const size = fs.statSync(pathname).size; // update it immediately From 81c18cd861d16930cbff6af5f5c89ec69b3555a4 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Fri, 27 Mar 2026 04:18:37 +0800 Subject: [PATCH 040/117] fix vite globals being defined late --- packages/kit/src/exports/vite/index.js | 82 +++++++++---------- .../remote-functions/prerender.svelte.js | 2 +- .../kit/src/runtime/server/page/render.js | 3 + 3 files changed, 45 insertions(+), 42 deletions(-) diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index ccd9d7a28a65..42f97264bc58 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -568,6 +568,47 @@ function kit({ svelte_config, adapter_in_vite_config }) { /** @type {Record} */ let server_assets = {}; + /** + * Allows us to access the filesystem from an environment that doesn't have `node:fs` + * @type {import('vite').Plugin} + */ + const plugin_server_filesystem = { + name: 'vite-plugin-sveltekit-server-filesystem', + apply: 'serve', + applyToEnvironment(environment) { + return environment.name !== 'client' && environment.name !== 'serviceWorker'; + }, + configureServer() { + server_assets = {}; + }, + load: { + order: 'pre', + handler(id) { + if (!dev_environment) return; + + const { pathname, searchParams } = new URL(id, 'file://'); + if ( + (searchParams.has('url') || vite_config.assetsInclude(pathname)) && + fs.existsSync(pathname) + ) { + const filepath = pathname.startsWith(root) + ? path.relative(root, pathname) + : to_fs(pathname); + const size = fs.statSync(pathname).size; + + // update it immediately + dev_environment.vite.environments.ssr.hot.send(`sveltekit:server-assets`, { + [filepath]: size + }); + + // persist changes in case of server reload + server_assets[filepath] = size; + invalidate_module(dev_environment.vite, sveltekit_server_assets); + } + } + } + }; + /** @type {import('vite').Plugin} */ const plugin_virtual_modules = { name: 'vite-plugin-sveltekit-virtual-modules', @@ -2093,47 +2134,6 @@ function kit({ svelte_config, adapter_in_vite_config }) { } }; - /** - * Allows us to access the filesystem from an environment that doesn't have `node:fs` - * @type {import('vite').Plugin} - */ - const plugin_server_filesystem = { - name: 'vite-plugin-sveltekit-server-filesystem', - apply: 'serve', - applyToEnvironment(environment) { - return environment.name !== 'client' && environment.name !== 'serviceWorker'; - }, - configureServer() { - server_assets = {}; - }, - load: { - order: 'pre', - handler(id) { - if (!dev_environment) return; - - const { pathname, searchParams } = new URL(id, 'file://'); - if ( - (searchParams.has('url') || vite_config.assetsInclude(pathname)) && - fs.existsSync(pathname) - ) { - const filepath = pathname.startsWith(root) - ? path.relative(root, pathname) - : to_fs(pathname); - const size = fs.statSync(pathname).size; - - // update it immediately - dev_environment.vite.environments.ssr.hot.send(`sveltekit:server-assets`, { - [filepath]: size - }); - - // persist changes in case of server reload - server_assets[filepath] = size; - invalidate_module(dev_environment.vite, sveltekit_server_assets); - } - } - } - }; - /** @type {import('vite').Plugin} */ const plugin_generated = { name: 'vite-plugin-sveltekit-resolve-generated', diff --git a/packages/kit/src/runtime/client/remote-functions/prerender.svelte.js b/packages/kit/src/runtime/client/remote-functions/prerender.svelte.js index 335c580f428d..3d461028690b 100644 --- a/packages/kit/src/runtime/client/remote-functions/prerender.svelte.js +++ b/packages/kit/src/runtime/client/remote-functions/prerender.svelte.js @@ -3,7 +3,7 @@ import * as devalue from 'devalue'; import { DEV } from 'esm-env'; import { get_remote_request_headers, remote_request } from './shared.svelte.js'; import { app, prerender_responses } from '../client.js'; -import { version } from '../../app/environment/index.js'; +import { version } from '../../app/environment/internal.js'; import { app_dir, base } from '../../app/paths/internal/client.js'; import { create_remote_key, stringify_remote_arg } from '../../shared.js'; diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index 4c0486d3b28e..3316e5cf9e8e 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -614,8 +614,11 @@ export async function render_response({ }`); } + // we need to eagerly import the Vite client module in development to ensure + // that Vite global constant replacements are initialised before our code runs const init_app = ` { + ${DEV ? `import('/@vite/client')` : ''} ${blocks.join('\n\n\t\t\t\t\t')} } `; From bc1bae75cbde23c616d93f36bebb9a861c920f39 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Fri, 27 Mar 2026 04:41:35 +0800 Subject: [PATCH 041/117] todo --- packages/kit/test/apps/basics/test/test.js | 24 +--------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/packages/kit/test/apps/basics/test/test.js b/packages/kit/test/apps/basics/test/test.js index 768a69f8039d..b2b64ae9a7e7 100644 --- a/packages/kit/test/apps/basics/test/test.js +++ b/packages/kit/test/apps/basics/test/test.js @@ -9,29 +9,7 @@ test.skip(() => process.env.KIT_E2E_BROWSER === 'webkit'); test.describe.configure({ mode: 'parallel' }); test.describe('adapter', () => { - test('populates event.platform for dynamic SSR', async ({ page }) => { - await page.goto('/adapter/dynamic'); - const json = JSON.parse((await page.textContent('pre')) ?? ''); - - expect(json).toEqual({ - config: { - message: 'hello from dynamic page' - }, - prerender: false - }); - }); - - test('populates event.platform for prerendered page', async ({ page }) => { - await page.goto('/adapter/prerendered'); - const json = JSON.parse((await page.textContent('pre')) ?? ''); - - expect(json).toEqual({ - config: { - message: 'hello from prerendered page' - }, - prerender: true - }); - }); + // TODO: adapter vite environment api tests? }); test.describe('Imports', () => { From fb82c525f537a29abcad9e1a3dd4dac4d43c1131 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Sat, 28 Mar 2026 02:01:48 +0800 Subject: [PATCH 042/117] use file protocol --- packages/kit/src/exports/vite/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 42f97264bc58..9ddca129540c 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -932,7 +932,8 @@ function kit({ svelte_config, adapter_in_vite_config }) { ? dedent` async () => { const url = ${s(path.resolve(root, endpoint.file))}; - return await loud_ssr_load_module(url); + const { module } = await resolve(url); + return module; } ` : null From 4865f819497db11acb3af71f75545e9dcc0bd459 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Sat, 28 Mar 2026 05:25:08 +0800 Subject: [PATCH 043/117] forgot to stringify this --- packages/kit/src/exports/vite/index.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 9ddca129540c..eeb0c72c2b55 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -788,11 +788,13 @@ function kit({ svelte_config, adapter_in_vite_config }) { ${ kit.router.resolution === 'client' ? undefined - : manifest_data.nodes.map((node, i) => { - if (node.component || node.universal) { - return `${kit.paths.base}${to_fs(kit.outDir)}/generated/client/nodes/${i}.js`; - } - }) + : s( + manifest_data.nodes.map((node, i) => { + if (node.component || node.universal) { + return `${kit.paths.base}${to_fs(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: From 08770ae25801e4d5478353b20eb192c2ad407f4e Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Sat, 28 Mar 2026 06:07:20 +0800 Subject: [PATCH 044/117] timing issue --- packages/kit/src/exports/vite/dev/index.js | 27 ---------- packages/kit/src/exports/vite/index.js | 59 +++++++-------------- packages/kit/src/exports/vite/module_ids.js | 1 - 3 files changed, 18 insertions(+), 69 deletions(-) diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 3ab978b220cf..21128b808b1e 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -392,33 +392,6 @@ export function invalidate_module(vite, id) { } } -/** - * @overload - * @param {import('vite').ViteDevServer} dev - * @param {`remote-${string}`} id - * @returns {Promise>} - */ -/** - * Retrieves data from a module that's been loaded into an environment. The module - * must import and call `send` from `__sveltekit/ipc` for this function to receive it - * @param {import('vite').ViteDevServer} dev - * @param {string} event_id - * @returns {Promise} - */ -export function get_module_data(dev, event_id) { - const event = `sveltekit:${event_id}`; - - return new Promise((resolve) => { - /** @param {unknown} data */ - const listener = (data) => { - dev.environments.ssr.hot.off(event, listener); - resolve(data); - }; - dev.environments.ssr.hot.on(event, listener); - dev.environments.ssr.hot.send(event); - }); -} - /** * @param {import('types').ManifestData} manifest_data * @param {import('vite/module-runner').ModuleRunner} runner diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index eeb0c72c2b55..a0033c5582e3 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -20,13 +20,7 @@ import { runtime_directory, logger, get_runtime_base, get_mime_lookup } from '.. import { generate_manifest } from '../../core/generate_manifest/index.js'; import { build_server_nodes } from './build/build_server.js'; import { assets_base, find_deps, resolve_symlinks } from './build/utils.js'; -import { - dev, - invalidate_module, - get_module_data, - get_matchers, - get_inline_css -} from './dev/index.js'; +import { dev, invalidate_module, get_matchers, get_inline_css } from './dev/index.js'; import { preview } from './preview/index.js'; import { error_for_missing_config, @@ -50,7 +44,6 @@ import { env_static_public, service_worker, sveltekit_server, - sveltekit_ipc, sveltekit_remotes, sveltekit_server_assets, sveltekit_ssr_manifest @@ -669,8 +662,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { exactRegex(sveltekit_server), exactRegex(sveltekit_ssr_manifest), exactRegex(sveltekit_server_assets), - exactRegex(sveltekit_remotes), - exactRegex(sveltekit_ipc) + exactRegex(sveltekit_remotes) ] }, handler(id) { @@ -739,22 +731,6 @@ function kit({ svelte_config, adapter_in_vite_config }) { `; } - case sveltekit_ipc: { - return dedent` - export function send(event_id, data) { - if (!import.meta.hot) return; - - const event = 'sveltekit:' + event_id; - const listener = () => { - import.meta.hot.send(event, data); - import.meta.hot.off(event, listener); - }; - - import.meta.hot.on(event, listener); - } - `; - } - case sveltekit_ssr_manifest: { if (!dev_environment) return; @@ -884,7 +860,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { // in dev we inline all styles to avoid FOUC. this gets populated lazily so that // components/stylesheets loaded via import() during \`load\` are included - const event = 'sveltekit:inline-styles-node-${index}'; + const event = 'sveltekit:inline-styles-node-${index}-response'; result.inline_styles = async () => { if (!import.meta.hot) return; @@ -896,7 +872,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { }; import.meta.hot.on(event, listener); - import.meta.hot.send('sveltekit:inline-styles', { urls, node: result.index }); + import.meta.hot.send('sveltekit:inline-styles-request', { urls, node: result.index }); return promise; } @@ -1007,7 +983,6 @@ function kit({ svelte_config, adapter_in_vite_config }) { /** @param {string} id */ async function resolve(id) { - // TODO: doesn't work for files symlinked to kit package workspace? const url = id.startsWith('..') ? to_fs(id) : \`file:///\${id}\`; const module = await loud_ssr_load_module(url); return { module, url }; @@ -1305,15 +1280,11 @@ function kit({ svelte_config, adapter_in_vite_config }) { ${ dev_environment?.vite ? dedent` - import { send } from '__sveltekit/ipc'; - - send('remote-${remote.hash}', (() => { - const exports = new Map(); - for (const name in $$_self_$$) { - exports.set(name, { type: $$_self_$$[name].__.type }); - } - return Object.fromEntries(exports); - })()); + const exports = new Map(); + for (const name in $$_self_$$) { + exports.set(name, { type: $$_self_$$[name].__.type }); + } + import.meta.hot.send('sveltekit:' + 'remote-${remote.hash}', Object.fromEntries(exports)); ` : '' } @@ -1344,9 +1315,15 @@ function kit({ svelte_config, adapter_in_vite_config }) { // being called again with `opts.ssr === true` if the module isn't // already loaded) so we can determine what it exports if (dev_environment?.vite) { + const { promise, resolve } = Promise.withResolvers(); + + const event = `sveltekit:remote-${remote.hash}`; + dev_environment.vite.environments.ssr.hot.on(event, resolve); + await dev_environment.vite.environments.ssr.transformRequest(id); - const exports = await get_module_data(dev_environment.vite, `remote-${remote.hash}`); + const exports = await promise; + dev_environment.vite.environments.ssr.hot.off(event, resolve); for (const [name, value] of Object.entries(exports)) { const type = value.type; @@ -1776,9 +1753,9 @@ function kit({ svelte_config, adapter_in_vite_config }) { ); }); - vite.environments.ssr.hot.on('sveltekit:inline-styles', async ({ urls, node }) => { + vite.environments.ssr.hot.on('sveltekit:inline-styles-request', async ({ urls, node }) => { vite.environments.ssr.hot.send( - `sveltekit:inline-styles-node-${node}`, + `sveltekit:inline-styles-node-${node}-response`, await get_inline_css(vite, module_runner, urls) ); }); diff --git a/packages/kit/src/exports/vite/module_ids.js b/packages/kit/src/exports/vite/module_ids.js index d2918d9a1a06..6de116596043 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 sveltekit_server = '\0virtual:__sveltekit/server'; export const sveltekit_ssr_manifest = '\0virtual:__sveltekit/ssr-manifest'; export const sveltekit_server_assets = '\0virtual:__sveltekit/server-assets'; export const sveltekit_remotes = '\0virtual:__sveltekit/remotes'; -export const sveltekit_ipc = '\0virtual:__sveltekit/ipc'; export const app_server = posixify( fileURLToPath(new URL('../../runtime/app/server/index.js', import.meta.url)) From 26d68b934ef2d58794a29ea55bf22d384bd60d62 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Sat, 28 Mar 2026 06:40:10 +0800 Subject: [PATCH 045/117] try to fix timing issues --- packages/kit/src/exports/vite/index.js | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index a0033c5582e3..c97174734a78 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -709,11 +709,9 @@ function kit({ svelte_config, adapter_in_vite_config }) { .join(',\n')} }; - if (import.meta.hot) { - import.meta.hot.on('sveltekit:server-assets', (data) => { - Object.assign(server_assets, data); - }); - } + import.meta.hot?.on('sveltekit:server-assets', (data) => { + Object.assign(server_assets, data); + }); `; } @@ -723,11 +721,9 @@ function kit({ svelte_config, adapter_in_vite_config }) { return dedent` export const remotes = ${s(remotes)}; - if (import.meta.hot) { - import.meta.hot.on('sveltekit:remotes', (data) => { - remotes.push(data); - }); - } + import.meta.hot?.on('sveltekit:remotes', (data) => { + remotes.push(data); + }); `; } @@ -1284,7 +1280,12 @@ function kit({ svelte_config, adapter_in_vite_config }) { for (const name in $$_self_$$) { exports.set(name, { type: $$_self_$$[name].__.type }); } - import.meta.hot.send('sveltekit:' + 'remote-${remote.hash}', Object.fromEntries(exports)); + const data = Object.fromEntries(exports); + const event = 'sveltekit:remote-${remote.hash}-response'; + import.meta.hot.send(event, data); + import.meta.hot.on('sveltekit:remote-${remote.hash}-request', async () => { + import.meta.hot.send(event, data); + }); ` : '' } @@ -1317,11 +1318,12 @@ function kit({ svelte_config, adapter_in_vite_config }) { if (dev_environment?.vite) { const { promise, resolve } = Promise.withResolvers(); - const event = `sveltekit:remote-${remote.hash}`; + const event = `sveltekit:remote-${remote.hash}-response`; dev_environment.vite.environments.ssr.hot.on(event, resolve); await dev_environment.vite.environments.ssr.transformRequest(id); + dev_environment.vite.environments.ssr.hot.send(`sveltekit:remote-${remote.hash}-request`); const exports = await promise; dev_environment.vite.environments.ssr.hot.off(event, resolve); From 810efdbd8f5238d85fb57e5363409a31baf63896 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Sat, 28 Mar 2026 06:45:53 +0800 Subject: [PATCH 046/117] scope hot code --- packages/kit/src/exports/vite/index.js | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index c97174734a78..c50856249dbd 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -1276,16 +1276,18 @@ function kit({ svelte_config, adapter_in_vite_config }) { ${ dev_environment?.vite ? dedent` - const exports = new Map(); - for (const name in $$_self_$$) { - exports.set(name, { type: $$_self_$$[name].__.type }); - } - const data = Object.fromEntries(exports); - const event = 'sveltekit:remote-${remote.hash}-response'; - import.meta.hot.send(event, data); - import.meta.hot.on('sveltekit:remote-${remote.hash}-request', async () => { + if (import.meta.hot) { + const exports = new Map(); + for (const name in $$_self_$$) { + exports.set(name, { type: $$_self_$$[name].__.type }); + } + const data = Object.fromEntries(exports); + const event = 'sveltekit:remote-${remote.hash}-response'; import.meta.hot.send(event, data); - }); + import.meta.hot.on('sveltekit:remote-${remote.hash}-request', async () => { + import.meta.hot.send(event, data); + }); + } ` : '' } From 66d15b2e23de7e2a1c383ba779883011dc219f19 Mon Sep 17 00:00:00 2001 From: Tee Ming Chew Date: Sat, 28 Mar 2026 18:43:17 +0800 Subject: [PATCH 047/117] fix windows posix and drive letter issue --- packages/kit/src/exports/vite/index.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index c50856249dbd..fdd8bbdaf0a2 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -579,13 +579,15 @@ function kit({ svelte_config, adapter_in_vite_config }) { handler(id) { if (!dev_environment) return; - const { pathname, searchParams } = new URL(id, 'file://'); + const { searchParams, search } = new URL(id, `file://`); + const pathname = id.replace(search, ''); + if ( (searchParams.has('url') || vite_config.assetsInclude(pathname)) && fs.existsSync(pathname) ) { const filepath = pathname.startsWith(root) - ? path.relative(root, pathname) + ? posixify(path.relative(root, pathname)) : to_fs(pathname); const size = fs.statSync(pathname).size; From 5bbf99879399cf08945f9650824a67c2c451d632 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Sat, 28 Mar 2026 18:45:52 +0800 Subject: [PATCH 048/117] add comments --- packages/kit/src/exports/vite/dev/index.js | 1 + packages/kit/src/exports/vite/index.js | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 21128b808b1e..b8a238e8217d 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -62,6 +62,7 @@ export function dev(vite, vite_config, svelte_config, root, dev_environment) { return; } + // TODO: update manifest through hot channel? invalidate_module(vite, sveltekit_ssr_manifest); } diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index fdd8bbdaf0a2..ce87c889cf7f 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -743,7 +743,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { export const prerendered = new Set(); export const env = ${s(env)}; - const nodes = ${devalue.uneval(manifest_data.nodes, revive_functions)} + const nodes = ${devalue.uneval(manifest_data.nodes, revive_functions)}; export const manifest = { appDir: ${s(kit.appDir)}, @@ -1285,7 +1285,11 @@ function kit({ svelte_config, adapter_in_vite_config }) { } const data = Object.fromEntries(exports); const event = 'sveltekit:remote-${remote.hash}-response'; + + // we need to send the data immediately in case of preloads import.meta.hot.send(event, data); + + // otherwise, we send it when requested import.meta.hot.on('sveltekit:remote-${remote.hash}-request', async () => { import.meta.hot.send(event, data); }); From 8fb8b8b03250a74f84d2f8560aed1d659df028e7 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Tue, 31 Mar 2026 19:44:18 +0800 Subject: [PATCH 049/117] invalidate ssr manifest on file changes --- packages/kit/src/exports/vite/dev/index.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index b8a238e8217d..59584aedc4ab 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -45,13 +45,13 @@ export function dev(vite, vite_config, svelte_config, root, dev_environment) { 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', @@ -62,8 +62,7 @@ export function dev(vite, vite_config, svelte_config, root, dev_environment) { return; } - // TODO: update manifest through hot channel? - invalidate_module(vite, sveltekit_ssr_manifest); + void invalidate_module(vite, sveltekit_ssr_manifest); } update_manifest(); @@ -110,6 +109,8 @@ export function dev(vite, vite_config, svelte_config, root, dev_environment) { // Unless it's a file where the trailing slash page option might have changed if (timeout || restarting || !/\+(page|layout|server).*$/.test(file)) return; sync.update(svelte_config, manifest_data, file, root); + // TODO: perform a partial update instead of invalidating the whole virtual module? + void invalidate_module(vite, sveltekit_ssr_manifest); }); const { appTemplate, errorTemplate, serviceWorker, hooks } = svelte_config.kit.files; From fda77ef7be314e3aaefdd2f1fa60f27cd68fba51 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Wed, 1 Apr 2026 01:22:28 +0800 Subject: [PATCH 050/117] Apply suggestion from @teemingc --- .changeset/short-chefs-stare.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/short-chefs-stare.md b/.changeset/short-chefs-stare.md index 74e7e3321a8a..0ffb0453ccb5 100644 --- a/.changeset/short-chefs-stare.md +++ b/.changeset/short-chefs-stare.md @@ -2,4 +2,4 @@ '@sveltejs/kit': major --- -breaking: use the Vite Environment API in dev and preview +breaking: use the Vite Environment API in development From 011698a5cd5c66817716a879172db81de3b37d03 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Wed, 1 Apr 2026 01:22:45 +0800 Subject: [PATCH 051/117] Apply suggestion from @teemingc --- .changeset/short-chefs-stare.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/short-chefs-stare.md b/.changeset/short-chefs-stare.md index 0ffb0453ccb5..2d2b4828693e 100644 --- a/.changeset/short-chefs-stare.md +++ b/.changeset/short-chefs-stare.md @@ -2,4 +2,4 @@ '@sveltejs/kit': major --- -breaking: use the Vite Environment API in development +feat: use the Vite Environment API in development From 7a90e033cd10bdbdd551f3ac3bbe091705f7230a Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Wed, 1 Apr 2026 01:41:12 +0800 Subject: [PATCH 052/117] move applyEnvironment hook --- packages/kit/src/exports/vite/index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index ce87c889cf7f..50cc24d69055 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -1464,10 +1464,6 @@ function kit({ svelte_config, adapter_in_vite_config }) { const plugin_compile = { name: 'vite-plugin-sveltekit-compile', - applyToEnvironment(environment) { - return environment.name !== 'serviceWorker'; - }, - // TODO: add `order: pre` to avoid false-positive warnings of overridden config options set by Vitest /** * Build the SvelteKit-provided Vite config to be merged with the user's vite.config.js file. @@ -1782,6 +1778,10 @@ function kit({ svelte_config, adapter_in_vite_config }) { return preview(vite, vite_config, svelte_config); }, + applyToEnvironment(environment) { + return environment.name !== 'serviceWorker'; + }, + renderChunk(code, chunk) { if (code.includes('__SVELTEKIT_TRACK__')) { return { From 886bdfc5c24acf6b2ee06fe9b8ef2ca69de86a77 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Wed, 1 Apr 2026 02:15:24 +0800 Subject: [PATCH 053/117] revert --- packages/kit/scripts/generate-dts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/scripts/generate-dts.js b/packages/kit/scripts/generate-dts.js index aaf4721e1eaf..60c7fe7d103f 100644 --- a/packages/kit/scripts/generate-dts.js +++ b/packages/kit/scripts/generate-dts.js @@ -23,7 +23,7 @@ await createBundle({ const types = readFileSync('./types/index.d.ts', 'utf-8'); if (types.includes('__sveltekit/')) { throw new Error( - 'Found __sveltekit/ in types/index.d.ts - make sure to hide internal modules by not just re-exporting them. Contents:\n\n' + + 'Found __sveltekit/ in types/index.d.ts - make sure to hide internal modules by not just reexporting them. Contents:\n\n' + types ); } From 9e69f8aa74b870810a33e72ee54fc43986b61a1e Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Wed, 1 Apr 2026 02:18:43 +0800 Subject: [PATCH 054/117] don't think we need to change the preview server --- packages/kit/src/exports/vite/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 50cc24d69055..9b4c0e1d3aaf 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -1774,7 +1774,6 @@ function kit({ svelte_config, adapter_in_vite_config }) { * @see https://vitejs.dev/guide/api-plugin.html#configurepreviewserver */ configurePreviewServer(vite) { - // TODO: run the build output through the environment return preview(vite, vite_config, svelte_config); }, From 9bb226faf52acb5ec7c38af24308801626fec53a Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Wed, 1 Apr 2026 02:41:00 +0800 Subject: [PATCH 055/117] fix loud ssr load module --- packages/kit/src/exports/vite/index.js | 85 +++++++++++++++----------- 1 file changed, 50 insertions(+), 35 deletions(-) diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 9b4c0e1d3aaf..df00e6f8df8c 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -6,6 +6,7 @@ import { styleText } from 'node:util'; import * as devalue from 'devalue'; import { exactRegex, prefixRegex } from 'rolldown/filter'; import { + buildErrorMessage, createFetchableDevEnvironment, createServerHotChannel, createServerModuleRunner, @@ -938,42 +939,20 @@ function kit({ svelte_config, adapter_in_vite_config }) { } }; - // TODO: do we even need this or will Vite handle import errors for us? /** * @param {string} url */ async function loud_ssr_load_module(url) { try { return await import(/* @vite-ignore */ url); - } catch (/** @type {any} */ err) { - // TODO: move this to the vite process by calling import.meta.hot? - - // const msg = buildErrorMessage(err, [styleText('red', \`Internal server error: \${err.message}\`)]); - const msg = \`Internal server error: \${err.message}\`; - - // if (!server.config.logger.hasErrorLogged(err)) { - // server.config.logger.error(msg, { error: err }); - // } - console.error(msg); - - // server.ws.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 - // } - // }); - - // import.meta.hot?.send('vite:error', { - // ...err, - // // these properties are non-enumerable and will - // // not be serialized unless we explicitly include them - // message: err.message, - // stack: err.stack - // }); + } catch (err) { + import.meta.hot?.send('sveltekit:ssr-load-module', { + ...err, + // these properties are non-enumerable and will not be + // serialized unless we explicitly include them + message: err.message, + stack: err.stack + }); throw err; } @@ -1460,6 +1439,13 @@ function kit({ svelte_config, adapter_in_vite_config }) { } }; + /** @type {() => Promise} */ + let handle_matchers; + /** @type {(payload: { urls: string[]; node: number; }) => Promise} */ + let handle_inline_styles; + /** @type {(error: Error) => void} */ + let handle_ssr_load_module; + /** @type {import('vite').Plugin} */ const plugin_compile = { name: 'vite-plugin-sveltekit-compile', @@ -1740,7 +1726,12 @@ function kit({ svelte_config, adapter_in_vite_config }) { * Adds the SvelteKit middleware to do SSR in dev mode. * @see https://vitejs.dev/guide/api-plugin.html#configureserver */ - configureServer(vite) { + async configureServer(vite) { + await runner?.close(); + vite.environments.ssr.hot.off('sveltekit:matchers-request', handle_matchers); + vite.environments.ssr.hot.off('sveltekit:inline-styles-request', handle_inline_styles); + vite.environments.ssr.hot.off('sveltekit:ssr-load-module', handle_ssr_load_module); + manifest_data = sync.all(svelte_config, vite_config_env.mode, root).manifest_data; // other properties will be populated during the `dev` function @@ -1752,19 +1743,43 @@ function kit({ svelte_config, adapter_in_vite_config }) { const module_runner = (runner = createServerModuleRunner(vite.environments.ssr)); - vite.environments.ssr.hot.on('sveltekit:matchers-request', async () => { + handle_matchers ??= async () => { vite.environments.ssr.hot.send( 'sveltekit:matchers-response', await get_matchers(info.manifest_data, module_runner, root) ); - }); + }; + vite.environments.ssr.hot.on('sveltekit:matchers-request', handle_matchers); - vite.environments.ssr.hot.on('sveltekit:inline-styles-request', async ({ urls, node }) => { + handle_inline_styles ??= async ({ urls, node }) => { vite.environments.ssr.hot.send( `sveltekit:inline-styles-node-${node}-response`, await get_inline_css(vite, module_runner, urls) ); - }); + }; + vite.environments.ssr.hot.on('sveltekit:inline-styles-request', handle_inline_styles); + + handle_ssr_load_module ??= (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 }); + } + + vite.ws.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 ?? '' + } + }); + }; + vite.environments.ssr.hot.on('sveltekit:ssr-load-module', handle_ssr_load_module); return dev(vite, vite_config, svelte_config, root, dev_environment); }, From 08a9f6232ffe964975e047956f89360386d0de11 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Wed, 1 Apr 2026 03:17:10 +0800 Subject: [PATCH 056/117] format --- packages/kit/src/runtime/components/components.spec.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/kit/src/runtime/components/components.spec.js b/packages/kit/src/runtime/components/components.spec.js index 521dadb82390..513adaaabd95 100644 --- a/packages/kit/src/runtime/components/components.spec.js +++ b/packages/kit/src/runtime/components/components.spec.js @@ -3,7 +3,6 @@ import { join } from 'node:path'; import { compile } from 'svelte/compiler'; import { assert, test } from 'vitest'; - test.each(['layout.svelte', 'error.svelte'])('%s compiles in runes mode', (component) => { const source = readFileSync(join(import.meta.dirname, component), 'utf-8'); From c773b76760bfd998b5ac97dece93d8029d70fb80 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Wed, 1 Apr 2026 04:20:35 +0800 Subject: [PATCH 057/117] fix prerendering? --- packages/kit/src/exports/vite/index.js | 4 ++-- packages/kit/src/runtime/server/page/render.js | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index df00e6f8df8c..8b204015bc35 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -1653,10 +1653,10 @@ function kit({ svelte_config, adapter_in_vite_config }) { } : // but if the Vite base is absolute, we just need to ensure // client paths are relative rather than absolute - (filename, { ssr, hostType }) => { + (filename, { ssr, type }) => { if (ssr) return; - if (hostType === 'js') { + if (type === 'asset' && filename.endsWith('.js')) { // We could always use a relative asset base path here, but it's better for performance not to. // E.g. Vite generates `new URL('/asset.png', import.meta).href` for a relative path vs just '/asset.png'. // That's larger and takes longer to run and also causes an HTML diff between SSR and client diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index 3316e5cf9e8e..ad410bd2c6a6 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -365,9 +365,10 @@ export async function render_response({ } if (!client.inline) { - const included_modulepreloads = Array.from(modulepreloads, (dep) => prefixed(dep)).filter( - (path) => resolve_opts.preload({ type: 'js', path }) - ); + // client import paths have no leading slash `_app/immutable/*` so we need to add one + const included_modulepreloads = Array.from(modulepreloads, (dep) => + prefixed('/' + dep) + ).filter((path) => resolve_opts.preload({ type: 'js', path })); for (const path of included_modulepreloads) { link_headers.add(`<${encodeURI(path)}>; rel="modulepreload"; nopush`); From 2e9f7a7484ab9b307ac951a8deb670da1f72fbbd Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Wed, 1 Apr 2026 04:49:53 +0800 Subject: [PATCH 058/117] 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 8b204015bc35..df00e6f8df8c 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -1653,10 +1653,10 @@ function kit({ svelte_config, adapter_in_vite_config }) { } : // but if the Vite base is absolute, we just need to ensure // client paths are relative rather than absolute - (filename, { ssr, type }) => { + (filename, { ssr, hostType }) => { if (ssr) return; - if (type === 'asset' && filename.endsWith('.js')) { + if (hostType === 'js') { // We could always use a relative asset base path here, but it's better for performance not to. // E.g. Vite generates `new URL('/asset.png', import.meta).href` for a relative path vs just '/asset.png'. // That's larger and takes longer to run and also causes an HTML diff between SSR and client From ea3034a0ff12d701af454df3e4ded86a1b0c1f3c Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Thu, 2 Apr 2026 17:46:19 +0800 Subject: [PATCH 059/117] split out these changes for 15250 --- packages/kit/kit.vitest.config.js | 4 + packages/kit/src/core/sync/sync.js | 2 +- packages/kit/src/core/sync/write_server.js | 9 +- packages/kit/src/exports/public.d.ts | 9 +- packages/kit/src/exports/vite/dev/index.js | 4 +- packages/kit/src/exports/vite/index.js | 5 +- packages/kit/src/exports/vite/module_ids.js | 1 + .../kit/src/runtime/app/environment/index.js | 3 +- .../src/runtime/app/environment/internal.js | 12 -- .../src/runtime/app/paths/internal/server.js | 11 +- packages/kit/src/runtime/app/paths/server.js | 6 +- packages/kit/src/runtime/app/server/index.js | 4 +- .../runtime/app/server/remote/prerender.js | 2 +- .../src/runtime/app/server/remote/query.js | 6 +- .../client/remote-functions/command.svelte.js | 4 +- .../client/remote-functions/form.svelte.js | 8 +- .../remote-functions/prerender.svelte.js | 6 +- .../client/remote-functions/query.svelte.js | 8 +- packages/kit/src/runtime/client/utils.js | 2 +- packages/kit/src/runtime/server/ambient.d.ts | 4 + packages/kit/src/runtime/server/external.js | 29 ---- packages/kit/src/runtime/server/fetch.js | 4 +- packages/kit/src/runtime/server/generated.js | 128 ------------------ packages/kit/src/runtime/server/index.js | 10 +- .../kit/src/runtime/server/page/render.js | 26 ++-- .../src/runtime/server/page/server_routing.js | 2 +- packages/kit/src/runtime/server/remote.js | 2 +- packages/kit/src/runtime/server/respond.js | 2 +- packages/kit/src/types/ambient-private.d.ts | 19 +++ packages/kit/src/types/global-private.d.ts | 1 - packages/kit/src/utils/os.js | 3 + packages/kit/test/apps/basics/test/test.js | 4 - packages/kit/test/mocks/path.js | 3 + packages/kit/tsconfig.json | 6 +- packages/kit/types/index.d.ts | 2 +- 35 files changed, 103 insertions(+), 248 deletions(-) delete mode 100644 packages/kit/src/runtime/app/environment/internal.js create mode 100644 packages/kit/src/runtime/server/ambient.d.ts delete mode 100644 packages/kit/src/runtime/server/external.js delete mode 100644 packages/kit/src/runtime/server/generated.js create mode 100644 packages/kit/test/mocks/path.js diff --git a/packages/kit/kit.vitest.config.js b/packages/kit/kit.vitest.config.js index 641cbf0c8327..6683a04297c4 100644 --- a/packages/kit/kit.vitest.config.js +++ b/packages/kit/kit.vitest.config.js @@ -1,3 +1,4 @@ +import { fileURLToPath } from 'node:url'; import { defineConfig } from 'vitest/config'; // this file needs a custom name so that the numerous test subprojects don't all pick it up @@ -11,6 +12,9 @@ export default defineConfig({ } }, test: { + alias: { + '__sveltekit/paths': fileURLToPath(new URL('./test/mocks/path.js', import.meta.url)) + }, pool: 'threads', maxWorkers: 1, include: ['src/**/*.spec.js'], diff --git a/packages/kit/src/core/sync/sync.js b/packages/kit/src/core/sync/sync.js index 1e233f2f9f24..839e877716d0 100644 --- a/packages/kit/src/core/sync/sync.js +++ b/packages/kit/src/core/sync/sync.js @@ -94,7 +94,7 @@ export function all_types(config, mode) { } /** - * Regenerate `${output}/server/internal.js` in response to `src/{app.html,error.html,service-worker.js}` changing + * Regenerate __SERVER__/internal.js in response to src/{app.html,error.html,service-worker.js} changing * @param {import('types').ValidatedConfig} config * @param {string} root The project root directory */ diff --git a/packages/kit/src/core/sync/write_server.js b/packages/kit/src/core/sync/write_server.js index fde234092037..ebc1db336184 100644 --- a/packages/kit/src/core/sync/write_server.js +++ b/packages/kit/src/core/sync/write_server.js @@ -1,13 +1,12 @@ import path from 'node:path'; import { styleText } from 'node:util'; import { hash } from '../../utils/hash.js'; -import { resolve_entry } from '../../utils/filesystem.js'; +import { posixify, resolve_entry } from '../../utils/filesystem.js'; import { s } from '../../utils/misc.js'; import { load_error_page, load_template } from '../config/index.js'; import { runtime_directory } from '../utils.js'; import { write_if_changed } from './utils.js'; import { escape_html } from '../../utils/escape.js'; -import { posixify } from '../../utils/os.js'; /** * @param {{ @@ -30,9 +29,9 @@ const server_template = ({ error_page }) => ` import root from '../root.js'; -import { set_building, set_prerendering } from '${runtime_directory}/app/environment/internal.js'; -import { set_assets } from '${runtime_directory}/app/paths/internal/server.js'; -import { set_manifest, set_read_implementation } from '${runtime_directory}/server/external.js'; +import { set_building, set_prerendering } from '__sveltekit/environment'; +import { set_assets } from '$app/paths/internal/server'; +import { set_manifest, set_read_implementation } from '__sveltekit/server'; import { set_private_env, set_public_env } from '${runtime_directory}/shared-server.js'; export const options = { diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index 792daa1ca725..891decb201ba 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -2,10 +2,6 @@ import 'svelte'; // pick up `declare module "*.svelte"` import 'vite/client'; // pick up `declare module "*.jpg"`, etc. import '../types/ambient.js'; -import { SvelteConfig } from '@sveltejs/vite-plugin-svelte'; -import { StandardSchemaV1 } from '@standard-schema/spec'; -import { PluginOption } from 'vite'; - import { AdapterEntry, CspDirectives, @@ -24,6 +20,9 @@ import { IsAny } from '../types/private.js'; import { BuildData, SSRNodeLoader, SSRRoute, ValidatedConfig } from 'types'; +import { SvelteConfig } from '@sveltejs/vite-plugin-svelte'; +import { StandardSchemaV1 } from '@standard-schema/spec'; +import { PluginOption } from 'vite'; import { RouteId as AppRouteId, LayoutParams as AppLayoutParams, @@ -1596,7 +1595,7 @@ export interface ServerInitOptions { } /** - * Powers the server + * Required to instantiate `Server` with project specific information */ export interface SSRManifest { appDir: string; diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 59584aedc4ab..8d3b721f0d67 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -2,11 +2,9 @@ import fs from 'node:fs'; import path from 'node:path'; import { URL } from 'node:url'; import { styleText } from 'node:util'; - import sirv from 'sirv'; import { isCSSRequest, isFetchableDevEnvironment } from 'vite'; - -import { getRequest, setResponse } from '@sveltejs/kit/node'; +import { getRequest, setResponse } from '../../../exports/node/index.js'; import { coalesce_to_error } from '../../../utils/error.js'; import { resolve_entry } from '../../../utils/filesystem.js'; import { posixify } from '../../../utils/os.js'; diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 83d0ff9c7e21..7abcc29b141e 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -49,12 +49,12 @@ import { sveltekit_server_assets, sveltekit_ssr_manifest } from './module_ids.js'; +import { to_fs } from './filesystem.js'; import { import_peer } from '../../utils/import.js'; import { compact } from '../../utils/array.js'; +import { posixify } from '../../utils/os.js'; import { should_ignore, has_children } from './static_analysis/utils.js'; import { load_config } from '../../core/config/index.js'; -import { to_fs } from './filesystem.js'; -import { posixify } from '../../utils/os.js'; const cwd = process.cwd(); @@ -473,7 +473,6 @@ function kit({ svelte_config, adapter_in_vite_config }) { const define = { __SVELTEKIT_APP_DIR__: s(kit.appDir), - __SVELTEKIT_APP_VERSION__: s(kit.version.name), __SVELTEKIT_EMBEDDED__: s(kit.embedded), __SVELTEKIT_FORK_PRELOADS__: s(kit.experimental.forkPreloads), __SVELTEKIT_PATHS_ASSETS__: s(kit.paths.assets), diff --git a/packages/kit/src/exports/vite/module_ids.js b/packages/kit/src/exports/vite/module_ids.js index 6de116596043..0580f0c8bbb2 100644 --- a/packages/kit/src/exports/vite/module_ids.js +++ b/packages/kit/src/exports/vite/module_ids.js @@ -8,6 +8,7 @@ export const env_dynamic_public = '\0virtual:env/dynamic/public'; export const service_worker = '\0virtual:service-worker'; +export const sveltekit_environment = '\0virtual:__sveltekit/environment'; export const sveltekit_server = '\0virtual:__sveltekit/server'; export const sveltekit_ssr_manifest = '\0virtual:__sveltekit/ssr-manifest'; export const sveltekit_server_assets = '\0virtual:__sveltekit/server-assets'; diff --git a/packages/kit/src/runtime/app/environment/index.js b/packages/kit/src/runtime/app/environment/index.js index 87a7b3521a10..10fa912a6c56 100644 --- a/packages/kit/src/runtime/app/environment/index.js +++ b/packages/kit/src/runtime/app/environment/index.js @@ -1,2 +1,3 @@ export { BROWSER as browser, DEV as dev } from 'esm-env'; -export { building, version } from './internal.js'; +// TODO: write these to disk +export { building, version } from '__sveltekit/environment'; diff --git a/packages/kit/src/runtime/app/environment/internal.js b/packages/kit/src/runtime/app/environment/internal.js deleted file mode 100644 index bb84c30951e7..000000000000 --- a/packages/kit/src/runtime/app/environment/internal.js +++ /dev/null @@ -1,12 +0,0 @@ -export const version = - typeof __SVELTEKIT_APP_VERSION__ === 'string' ? __SVELTEKIT_APP_VERSION__ : 'unknown'; -export let building = false; -export let prerendering = false; - -export function set_building() { - building = true; -} - -export function set_prerendering() { - prerendering = true; -} diff --git a/packages/kit/src/runtime/app/paths/internal/server.js b/packages/kit/src/runtime/app/paths/internal/server.js index 954265c26e39..cd71e77db073 100644 --- a/packages/kit/src/runtime/app/paths/internal/server.js +++ b/packages/kit/src/runtime/app/paths/internal/server.js @@ -1,10 +1,7 @@ -export let base = typeof __SVELTEKIT_PATHS_BASE__ !== 'undefined' ? __SVELTEKIT_PATHS_BASE__ : ''; -export let assets = - typeof __SVELTEKIT_PATHS_ASSETS__ !== 'undefined' ? __SVELTEKIT_PATHS_ASSETS__ : base; -export const app_dir = - typeof __SVELTEKIT_APP_DIR__ !== 'undefined' ? __SVELTEKIT_APP_DIR__ : '_app'; -export const relative = - typeof __SVELTEKIT_PATHS_RELATIVE__ !== 'undefined' ? __SVELTEKIT_PATHS_RELATIVE__ : true; +export let base = __SVELTEKIT_PATHS_BASE__; +export let assets = __SVELTEKIT_PATHS_ASSETS__ || base; +export const app_dir = __SVELTEKIT_APP_DIR__; +export const relative = __SVELTEKIT_PATHS_RELATIVE__; const initial = { base, assets }; diff --git a/packages/kit/src/runtime/app/paths/server.js b/packages/kit/src/runtime/app/paths/server.js index 8669a5915105..5fd50a890c1e 100644 --- a/packages/kit/src/runtime/app/paths/server.js +++ b/packages/kit/src/runtime/app/paths/server.js @@ -1,9 +1,9 @@ -import { try_get_request_store } from '@sveltejs/kit/internal/server'; import { base, assets, relative, initial_base } from './internal/server.js'; import { resolve_route, find_route } from '../../../utils/routing.js'; import { decode_pathname } from '../../../utils/url.js'; -import { manifest } from '../../server/external.js'; -import { get_hooks } from '../../server/generated.js'; +import { try_get_request_store } from '@sveltejs/kit/internal/server'; +import { manifest } from '__sveltekit/server'; +import { get_hooks } from '__SERVER__/internal.js'; /** @type {import('./client.js').asset} */ export function asset(file) { diff --git a/packages/kit/src/runtime/app/server/index.js b/packages/kit/src/runtime/app/server/index.js index 2df6d1c02c9e..efdc1bbf8307 100644 --- a/packages/kit/src/runtime/app/server/index.js +++ b/packages/kit/src/runtime/app/server/index.js @@ -1,6 +1,6 @@ +import { read_implementation, manifest } from '__sveltekit/server'; +import { assets } from '$app/paths/internal/server'; import { DEV } from 'esm-env'; -import { assets } from '../paths/internal/server.js'; -import { read_implementation, manifest } from '../../server/external.js'; import { base64_decode } from '../../utils.js'; /** diff --git a/packages/kit/src/runtime/app/server/remote/prerender.js b/packages/kit/src/runtime/app/server/remote/prerender.js index 83213b1d8256..405014cc69cc 100644 --- a/packages/kit/src/runtime/app/server/remote/prerender.js +++ b/packages/kit/src/runtime/app/server/remote/prerender.js @@ -5,7 +5,7 @@ import { error, json } from '@sveltejs/kit'; import { DEV } from 'esm-env'; import { get_request_store } from '@sveltejs/kit/internal/server'; import { stringify, stringify_remote_arg } from '../../../shared.js'; -import { app_dir, base } from '../../paths/internal/server.js'; +import { app_dir, base } from '$app/paths/internal/server'; import { create_validator, get_cache, diff --git a/packages/kit/src/runtime/app/server/remote/query.js b/packages/kit/src/runtime/app/server/remote/query.js index 7f19d51a32c3..57c11c65056a 100644 --- a/packages/kit/src/runtime/app/server/remote/query.js +++ b/packages/kit/src/runtime/app/server/remote/query.js @@ -1,12 +1,12 @@ /** @import { RemoteQuery, RemoteQueryFunction } from '@sveltejs/kit' */ /** @import { RemoteInternals, MaybePromise, RequestState, RemoteQueryBatchInternals, RemoteQueryInternals } from 'types' */ /** @import { StandardSchemaV1 } from '@standard-schema/spec' */ -import { HttpError, SvelteKitError } from '@sveltejs/kit/internal'; import { get_request_store } from '@sveltejs/kit/internal/server'; -import { create_validator, get_cache, get_response, run_remote_function } from './shared.js'; import { create_remote_key, stringify, stringify_remote_arg } from '../../../shared.js'; -import { prerendering } from '../../environment/internal.js'; +import { prerendering } from '__sveltekit/environment'; +import { create_validator, get_cache, get_response, run_remote_function } from './shared.js'; import { handle_error_and_jsonify } from '../../../server/utils.js'; +import { HttpError, SvelteKitError } from '@sveltejs/kit/internal'; /** * Creates a remote query. When called from the browser, the function will be invoked on the server via a `fetch` call. diff --git a/packages/kit/src/runtime/client/remote-functions/command.svelte.js b/packages/kit/src/runtime/client/remote-functions/command.svelte.js index 76e378ff2bb4..660220c5fa9e 100644 --- a/packages/kit/src/runtime/client/remote-functions/command.svelte.js +++ b/packages/kit/src/runtime/client/remote-functions/command.svelte.js @@ -1,12 +1,12 @@ /** @import { RemoteCommand, RemoteQueryOverride } from '@sveltejs/kit' */ /** @import { RemoteFunctionResponse } from 'types' */ /** @import { Query } from './query.svelte.js' */ +import { app_dir, base } from '$app/paths/internal/client'; import * as devalue from 'devalue'; import { HttpError } from '@sveltejs/kit/internal'; -import { get_remote_request_headers, refresh_queries, release_overrides } from './shared.svelte.js'; import { app } from '../client.js'; -import { app_dir, base } from '../../app/paths/internal/client.js'; import { stringify_remote_arg } from '../../shared.js'; +import { get_remote_request_headers, refresh_queries, release_overrides } from './shared.svelte.js'; /** * Client-version of the `command` function from `$app/server`. diff --git a/packages/kit/src/runtime/client/remote-functions/form.svelte.js b/packages/kit/src/runtime/client/remote-functions/form.svelte.js index 555c3663e2e3..6ae0904cc896 100644 --- a/packages/kit/src/runtime/client/remote-functions/form.svelte.js +++ b/packages/kit/src/runtime/client/remote-functions/form.svelte.js @@ -2,14 +2,14 @@ /** @import { RemoteFormInput, RemoteForm, RemoteQueryOverride } from '@sveltejs/kit' */ /** @import { InternalRemoteFormIssue, RemoteFunctionResponse } from 'types' */ /** @import { Query } from './query.svelte.js' */ +import { app_dir, base } from '$app/paths/internal/client'; import * as devalue from 'devalue'; import { DEV } from 'esm-env'; -import { tick } from 'svelte'; -import { createAttachmentKey } from 'svelte/attachments'; import { HttpError } from '@sveltejs/kit/internal'; -import { refresh_queries, release_overrides } from './shared.svelte.js'; import { app, query_responses, _goto, set_nearest_error_page, invalidateAll } from '../client.js'; -import { app_dir, base } from '../../app/paths/internal/client.js'; +import { tick } from 'svelte'; +import { refresh_queries, release_overrides } from './shared.svelte.js'; +import { createAttachmentKey } from 'svelte/attachments'; import { convert_formdata, flatten_issues, diff --git a/packages/kit/src/runtime/client/remote-functions/prerender.svelte.js b/packages/kit/src/runtime/client/remote-functions/prerender.svelte.js index 3d461028690b..3ab177c090b9 100644 --- a/packages/kit/src/runtime/client/remote-functions/prerender.svelte.js +++ b/packages/kit/src/runtime/client/remote-functions/prerender.svelte.js @@ -1,10 +1,10 @@ /** @import { RemotePrerenderFunction } from '@sveltejs/kit' */ +import { app_dir, base } from '$app/paths/internal/client'; +import { version } from '__sveltekit/environment'; import * as devalue from 'devalue'; import { DEV } from 'esm-env'; -import { get_remote_request_headers, remote_request } from './shared.svelte.js'; import { app, prerender_responses } from '../client.js'; -import { version } from '../../app/environment/internal.js'; -import { app_dir, base } from '../../app/paths/internal/client.js'; +import { get_remote_request_headers, remote_request } from './shared.svelte.js'; import { create_remote_key, stringify_remote_arg } from '../../shared.js'; // Initialize Cache API for prerender functions diff --git a/packages/kit/src/runtime/client/remote-functions/query.svelte.js b/packages/kit/src/runtime/client/remote-functions/query.svelte.js index 90fc289162be..24dc758f68a4 100644 --- a/packages/kit/src/runtime/client/remote-functions/query.svelte.js +++ b/packages/kit/src/runtime/client/remote-functions/query.svelte.js @@ -1,12 +1,12 @@ /** @import { RemoteQueryFunction } from '@sveltejs/kit' */ /** @import { RemoteFunctionResponse } from 'types' */ +import { app_dir, base } from '$app/paths/internal/client'; +import { app, goto, query_map, query_responses } from '../client.js'; +import { get_remote_request_headers, remote_request } from './shared.svelte.js'; import * as devalue from 'devalue'; +import { HttpError, Redirect } from '@sveltejs/kit/internal'; import { DEV } from 'esm-env'; import { tick, untrack, hydratable } from 'svelte'; -import { HttpError, Redirect } from '@sveltejs/kit/internal'; -import { get_remote_request_headers, remote_request } from './shared.svelte.js'; -import { app, goto, query_map, query_responses } from '../client.js'; -import { app_dir, base } from '../../app/paths/internal/client.js'; import { create_remote_key, stringify_remote_arg } from '../../shared.js'; /** diff --git a/packages/kit/src/runtime/client/utils.js b/packages/kit/src/runtime/client/utils.js index a4e355fc4d30..cd351f9fb4cf 100644 --- a/packages/kit/src/runtime/client/utils.js +++ b/packages/kit/src/runtime/client/utils.js @@ -1,7 +1,7 @@ import { BROWSER, DEV } from 'esm-env'; import { writable } from 'svelte/store'; import { assets } from '$app/paths'; -import { version } from '../app/environment/internal.js'; +import { version } from '__sveltekit/environment'; import { PRELOAD_PRIORITIES } from './constants.js'; export const origin = BROWSER ? location.origin : ''; diff --git a/packages/kit/src/runtime/server/ambient.d.ts b/packages/kit/src/runtime/server/ambient.d.ts new file mode 100644 index 000000000000..2f38261123dc --- /dev/null +++ b/packages/kit/src/runtime/server/ambient.d.ts @@ -0,0 +1,4 @@ +declare module '__SERVER__/internal.js' { + export const options: import('types').SSROptions; + export const get_hooks: () => Promise>; +} diff --git a/packages/kit/src/runtime/server/external.js b/packages/kit/src/runtime/server/external.js deleted file mode 100644 index 98628e5ff756..000000000000 --- a/packages/kit/src/runtime/server/external.js +++ /dev/null @@ -1,29 +0,0 @@ -/** @import { SSRManifest } from '@sveltejs/kit' */ - -/** @typedef {(path: string) => ReadableStream} ReadImplementation */ - -/** - * @type {ReadImplementation | null} - */ -export let read_implementation = null; - -/** - * The manifest will be set when the server starts. Exporting it this way allows - * us to access its value without having to pass it around as an argument. - * This is especially useful for public APIs such as `read` and `match` - */ -export let manifest = /** @type {SSRManifest} */ (/** @type {unknown} */ (null)); - -/** - * @param {ReadImplementation} fn - */ -export function set_read_implementation(fn) { - read_implementation = fn; -} - -/** - * @param {SSRManifest} _ - */ -export function set_manifest(_) { - manifest = _; -} diff --git a/packages/kit/src/runtime/server/fetch.js b/packages/kit/src/runtime/server/fetch.js index 9c74a0646c4d..e05f9c1dfd69 100644 --- a/packages/kit/src/runtime/server/fetch.js +++ b/packages/kit/src/runtime/server/fetch.js @@ -1,8 +1,8 @@ import { parseSetCookie } from 'cookie'; import { respond } from './respond.js'; -import { read_implementation } from './external.js'; +import * as paths from '$app/paths/internal/server'; +import { read_implementation } from '__sveltekit/server'; import { has_prerendered_path } from './utils.js'; -import * as paths from '../app/paths/internal/server.js'; /** * @param {{ diff --git a/packages/kit/src/runtime/server/generated.js b/packages/kit/src/runtime/server/generated.js deleted file mode 100644 index 14c65ac953eb..000000000000 --- a/packages/kit/src/runtime/server/generated.js +++ /dev/null @@ -1,128 +0,0 @@ -// This module is a stub and will not be used once Vite takes over module loading - -import { set_building, set_prerendering } from '../app/environment/internal.js'; -import { set_assets } from '../app/paths/internal/server.js'; -import { set_manifest, set_read_implementation } from './external.js'; -import { set_private_env, set_public_env } from '../shared-server.js'; - -/** @type {import('types').SSROptions} */ -export const options = { - app_template_contains_nonce: false, - async: false, - csp: { - mode: 'auto', - directives: { - 'child-src': [], - 'default-src': [], - 'frame-src': [], - 'worker-src': [], - 'connect-src': [], - 'font-src': [], - 'img-src': [], - 'manifest-src': [], - 'media-src': [], - 'object-src': [], - 'prefetch-src': [], - 'script-src': [], - 'script-src-elem': [], - 'script-src-attr': [], - 'style-src': [], - 'style-src-elem': [], - 'style-src-attr': [], - 'base-uri': [], - sandbox: [], - 'form-action': [], - 'frame-ancestors': [], - 'navigate-to': [], - 'report-uri': [], - 'report-to': [], - 'require-trusted-types-for': [], - 'trusted-types': [], - 'upgrade-insecure-requests': false, - 'require-sri-for': [], - 'block-all-mixed-content': false, - 'plugin-types': [], - referrer: [] - }, - reportOnly: { - 'child-src': [], - 'default-src': [], - 'frame-src': [], - 'worker-src': [], - 'connect-src': [], - 'font-src': [], - 'img-src': [], - 'manifest-src': [], - 'media-src': [], - 'object-src': [], - 'prefetch-src': [], - 'script-src': [], - 'script-src-elem': [], - 'script-src-attr': [], - 'style-src': [], - 'style-src-elem': [], - 'style-src-attr': [], - 'base-uri': [], - sandbox: [], - 'form-action': [], - 'frame-ancestors': [], - 'navigate-to': [], - 'report-uri': [], - 'report-to': [], - 'require-trusted-types-for': [], - 'trusted-types': [], - 'upgrade-insecure-requests': false, - 'require-sri-for': [], - 'block-all-mixed-content': false, - 'plugin-types': [], - referrer: [] - } - }, - csrf_check_origin: true, - csrf_trusted_origins: [], - embedded: false, - env_public_prefix: 'PUBLIC_', - env_private_prefix: '', - hash_routing: false, - // @ts-expect-error - hooks: null, // added lazily, via \`get_hooks\`, - root: { - render: () => { - return { css: { code: '', map: '' }, head: '', html: '', assets: '' }; - } - }, - service_worker: false, - service_worker_options: {}, - server_error_boundaries: false, - templates: { - app: () => '', - error: () => '' - }, - version_hash: '' -}; - -/** - * @returns {Promise>} - */ -// eslint-disable-next-line @typescript-eslint/require-await -export async function get_hooks() { - return { - handle: undefined, - handleFetch: undefined, - handleError: undefined, - handleValidationError: undefined, - init: undefined, - reroute: undefined, - transport: undefined - }; -} - -export { - set_assets, - set_building, - set_manifest, - set_prerendering, - set_private_env, - set_public_env, - set_read_implementation -}; diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js index d516893dae33..97eb1cf5b419 100644 --- a/packages/kit/src/runtime/server/index.js +++ b/packages/kit/src/runtime/server/index.js @@ -1,12 +1,12 @@ -import { DEV } from 'esm-env'; -import { set_app } from './app.js'; import { IN_WEBCONTAINER } from './constants.js'; -import { set_read_implementation, set_manifest } from './external.js'; -import { options, get_hooks } from './generated.js'; import { respond } from './respond.js'; -import { format_server_error } from './utils.js'; import { set_private_env, set_public_env } from '../shared-server.js'; +import { options, get_hooks } from '__SERVER__/internal.js'; +import { DEV } from 'esm-env'; import { filter_env } from '../../utils/env.js'; +import { format_server_error } from './utils.js'; +import { set_read_implementation, set_manifest } from '__sveltekit/server'; +import { set_app } from './app.js'; /** @type {Promise} */ let init_promise; diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index 597cfff1fd14..a2b04a28125f 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -1,23 +1,23 @@ -import { isRedirect, text } from '@sveltejs/kit'; import * as devalue from 'devalue'; -import { DEV } from 'esm-env'; import { readable, writable } from 'svelte/store'; -import { try_get_request_store, with_request_store } from '@sveltejs/kit/internal/server'; -import { uneval_action_response } from './actions.js'; -import { Csp } from './csp.js'; +import { DEV } from 'esm-env'; +import { isRedirect, text } from '@sveltejs/kit'; +import * as paths from '$app/paths/internal/server'; +import { hash } from '../../../utils/hash.js'; import { serialize_data } from './serialize_data.js'; +import { s } from '../../../utils/misc.js'; +import { Csp } from './csp.js'; +import { public_env } from '../../shared-server.js'; +import { SVELTE_KIT_ASSETS } from '../../../constants.js'; +import { SCHEME } from '../../../utils/url.js'; import { create_server_routing_response, generate_route_object } from './server_routing.js'; -import { get_global_name, handle_error_and_jsonify } from '../utils.js'; -import * as paths from '../../app/paths/internal/server.js'; import { add_resolution_suffix } from '../../pathname.js'; -import { create_remote_key } from '../../shared.js'; +import { try_get_request_store, with_request_store } from '@sveltejs/kit/internal/server'; import { text_encoder } from '../../utils.js'; -import { SVELTE_KIT_ASSETS } from '../../../constants.js'; -import { public_env } from '../../shared-server.js'; +import { uneval_action_response } from './actions.js'; +import { get_global_name, handle_error_and_jsonify } from '../utils.js'; +import { create_remote_key } from '../../shared.js'; import { get_status } from '../../../utils/error.js'; -import { hash } from '../../../utils/hash.js'; -import { s } from '../../../utils/misc.js'; -import { SCHEME } from '../../../utils/url.js'; // TODO rename this function/module diff --git a/packages/kit/src/runtime/server/page/server_routing.js b/packages/kit/src/runtime/server/page/server_routing.js index d224e0d242a6..c01d1ca808b4 100644 --- a/packages/kit/src/runtime/server/page/server_routing.js +++ b/packages/kit/src/runtime/server/page/server_routing.js @@ -1,4 +1,4 @@ -import { base, assets, relative } from '../../app/paths/internal/server.js'; +import { base, assets, relative } from '$app/paths/internal/server'; import { text } from '@sveltejs/kit'; import { s } from '../../../utils/misc.js'; import { find_route } from '../../../utils/routing.js'; diff --git a/packages/kit/src/runtime/server/remote.js b/packages/kit/src/runtime/server/remote.js index 060177c5913c..ab79fdc575be 100644 --- a/packages/kit/src/runtime/server/remote.js +++ b/packages/kit/src/runtime/server/remote.js @@ -4,7 +4,7 @@ import { json, error } from '@sveltejs/kit'; import { HttpError, Redirect, SvelteKitError } from '@sveltejs/kit/internal'; import { with_request_store, merge_tracing } from '@sveltejs/kit/internal/server'; -import { app_dir, base } from '../app/paths/internal/server.js'; +import { app_dir, base } from '$app/paths/internal/server'; import { is_form_content_type } from '../../utils/http.js'; import { parse_remote_arg, stringify } from '../shared.js'; import { handle_error_and_jsonify } from './utils.js'; diff --git a/packages/kit/src/runtime/server/respond.js b/packages/kit/src/runtime/server/respond.js index 638931d7652b..f41e7ea7262b 100644 --- a/packages/kit/src/runtime/server/respond.js +++ b/packages/kit/src/runtime/server/respond.js @@ -3,7 +3,7 @@ import { DEV } from 'esm-env'; import { json, text } from '@sveltejs/kit'; import { Redirect, SvelteKitError } from '@sveltejs/kit/internal'; import { merge_tracing, with_request_store } from '@sveltejs/kit/internal/server'; -import { base, app_dir } from '../../runtime/app/paths/internal/server.js'; +import { base, app_dir } from '$app/paths/internal/server'; import { is_endpoint_request, render_endpoint } from './endpoint.js'; import { render_page } from './page/index.js'; import { render_response } from './page/render.js'; diff --git a/packages/kit/src/types/ambient-private.d.ts b/packages/kit/src/types/ambient-private.d.ts index 5d3b4d470854..c98af8cb0062 100644 --- a/packages/kit/src/types/ambient-private.d.ts +++ b/packages/kit/src/types/ambient-private.d.ts @@ -1,3 +1,12 @@ +/** Internal version of $app/environment */ +declare module '__sveltekit/environment' { + export const building: boolean; + export const prerendering: boolean; + export const version: string; + export function set_building(): void; + export function set_prerendering(): void; +} + /** Internal version of $app/paths */ declare module '__sveltekit/paths' { export let base: '' | `/${string}`; @@ -8,3 +17,13 @@ declare module '__sveltekit/paths' { export function override(paths: { base: string; assets: string }): void; export function set_assets(path: string): void; } + +/** Internal version of $app/server */ +declare module '__sveltekit/server' { + import { SSRManifest } from '@sveltejs/kit'; + + export let manifest: SSRManifest; + export function read_implementation(path: string): ReadableStream; + export function set_manifest(manifest: SSRManifest): void; + export function set_read_implementation(fn: (path: string) => ReadableStream): void; +} diff --git a/packages/kit/src/types/global-private.d.ts b/packages/kit/src/types/global-private.d.ts index 358f6feede47..fbaa98d16d09 100644 --- a/packages/kit/src/types/global-private.d.ts +++ b/packages/kit/src/types/global-private.d.ts @@ -1,7 +1,6 @@ declare global { const __SVELTEKIT_ADAPTER_NAME__: string; const __SVELTEKIT_APP_DIR__: string; - const __SVELTEKIT_APP_VERSION__: string; const __SVELTEKIT_APP_VERSION_FILE__: string; const __SVELTEKIT_APP_VERSION_POLL_INTERVAL__: number; const __SVELTEKIT_EMBEDDED__: boolean; diff --git a/packages/kit/src/utils/os.js b/packages/kit/src/utils/os.js index 52ee66f8a21b..1101d56fa605 100644 --- a/packages/kit/src/utils/os.js +++ b/packages/kit/src/utils/os.js @@ -1,3 +1,6 @@ +// this file needs to remain node-agnostic because it could be imported into an +// environment without access to `node:*` + /** @param {string} str */ export function posixify(str) { return str.replace(/\\/g, '/'); diff --git a/packages/kit/test/apps/basics/test/test.js b/packages/kit/test/apps/basics/test/test.js index b2b64ae9a7e7..7f26e92f44cb 100644 --- a/packages/kit/test/apps/basics/test/test.js +++ b/packages/kit/test/apps/basics/test/test.js @@ -8,10 +8,6 @@ test.skip(() => process.env.KIT_E2E_BROWSER === 'webkit'); test.describe.configure({ mode: 'parallel' }); -test.describe('adapter', () => { - // TODO: adapter vite environment api tests? -}); - test.describe('Imports', () => { // https://github.com/sveltejs/kit/issues/461 test('handles static asset imports', async ({ baseURL, page }) => { diff --git a/packages/kit/test/mocks/path.js b/packages/kit/test/mocks/path.js new file mode 100644 index 000000000000..5919601b2122 --- /dev/null +++ b/packages/kit/test/mocks/path.js @@ -0,0 +1,3 @@ +export const base = ''; +export const assets = ''; +export const app_dir = '_app'; diff --git a/packages/kit/tsconfig.json b/packages/kit/tsconfig.json index 1f82380c9cfd..14ad168e8463 100644 --- a/packages/kit/tsconfig.json +++ b/packages/kit/tsconfig.json @@ -9,11 +9,13 @@ "paths": { "@sveltejs/kit": ["./src/exports/public.d.ts"], "@sveltejs/kit/node": ["./src/exports/node/index.js"], + "@sveltejs/kit/internal": ["./src/exports/internal/index.js"], + "@sveltejs/kit/internal/server": ["./src/exports/internal/server.js"], "$app/paths": ["./src/runtime/app/paths/public.d.ts"], + "$app/paths/internal/client": ["./src/runtime/app/paths/internal/client.js"], + "$app/paths/internal/server": ["./src/runtime/app/paths/internal/server.js"], "$app/server": ["./src/runtime/app/server/index.js"], // internal use only - "@sveltejs/kit/internal": ["./src/exports/internal/index.js"], - "@sveltejs/kit/internal/server": ["./src/exports/internal/server.js"], "types": ["./src/types/internal.d.ts"] }, "noUnusedLocals": true, diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index c366c7a5c2fb..a9345a8e4f22 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -1569,7 +1569,7 @@ declare module '@sveltejs/kit' { } /** - * Powers the server + * Required to instantiate `Server` with project specific information */ export interface SSRManifest { appDir: string; From 4ae489939af5917ef1f105d367d7ab1a738c2927 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Thu, 2 Apr 2026 17:52:43 +0800 Subject: [PATCH 060/117] fix import --- packages/kit/src/core/sync/write_server.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/kit/src/core/sync/write_server.js b/packages/kit/src/core/sync/write_server.js index ebc1db336184..e73e00d6481e 100644 --- a/packages/kit/src/core/sync/write_server.js +++ b/packages/kit/src/core/sync/write_server.js @@ -1,7 +1,8 @@ import path from 'node:path'; import { styleText } from 'node:util'; import { hash } from '../../utils/hash.js'; -import { posixify, resolve_entry } from '../../utils/filesystem.js'; +import { resolve_entry } from '../../utils/filesystem.js'; +import { posixify } from '../../utils/os.js'; import { s } from '../../utils/misc.js'; import { load_error_page, load_template } from '../config/index.js'; import { runtime_directory } from '../utils.js'; From 93ab1446c1d743ff597dd6cc05ed84b46282e609 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Thu, 2 Apr 2026 17:58:40 +0800 Subject: [PATCH 061/117] and this one too --- packages/kit/src/runtime/server/page/render.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index a2b04a28125f..ce7c411079b8 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -7,6 +7,7 @@ import { hash } from '../../../utils/hash.js'; import { serialize_data } from './serialize_data.js'; import { s } from '../../../utils/misc.js'; import { Csp } from './csp.js'; +import { uneval_action_response } from './actions.js'; import { public_env } from '../../shared-server.js'; import { SVELTE_KIT_ASSETS } from '../../../constants.js'; import { SCHEME } from '../../../utils/url.js'; @@ -14,7 +15,6 @@ import { create_server_routing_response, generate_route_object } from './server_ import { add_resolution_suffix } from '../../pathname.js'; import { try_get_request_store, with_request_store } from '@sveltejs/kit/internal/server'; import { text_encoder } from '../../utils.js'; -import { uneval_action_response } from './actions.js'; import { get_global_name, handle_error_and_jsonify } from '../utils.js'; import { create_remote_key } from '../../shared.js'; import { get_status } from '../../../utils/error.js'; From a2fa7c8a647849d6005c3b65b28abf0587caa901 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Thu, 2 Apr 2026 22:02:54 +0800 Subject: [PATCH 062/117] and theeeese too --- packages/kit/src/exports/vite/dev/server.js | 2 +- packages/kit/src/exports/vite/index.js | 67 +++++++++++++------ packages/kit/src/exports/vite/module_ids.js | 1 + .../kit/src/runtime/server/page/render.js | 7 +- packages/kit/src/types/virtual.d.ts | 2 +- 5 files changed, 51 insertions(+), 28 deletions(-) diff --git a/packages/kit/src/exports/vite/dev/server.js b/packages/kit/src/exports/vite/dev/server.js index 252f861b7bce..6f7633d277ea 100644 --- a/packages/kit/src/exports/vite/dev/server.js +++ b/packages/kit/src/exports/vite/dev/server.js @@ -1,6 +1,6 @@ import fs from 'node:fs'; import path from 'node:path'; -import { Server } from '__sveltekit/server'; +import { Server } from '__sveltekit/dev-server'; import { env, manifest } from '__sveltekit/ssr-manifest'; import { createReadableStream } from '@sveltejs/kit/node'; import { from_fs } from '../filesystem.js'; diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 7abcc29b141e..1153f59c76bc 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -47,7 +47,9 @@ import { sveltekit_server, sveltekit_remotes, sveltekit_server_assets, - sveltekit_ssr_manifest + sveltekit_ssr_manifest, + sveltekit_environment, + sveltekit_dev_server } from './module_ids.js'; import { to_fs } from './filesystem.js'; import { import_peer } from '../../utils/import.js'; @@ -382,11 +384,14 @@ function kit({ svelte_config, adapter_in_vite_config }) { const client_hooks = resolve_entry(kit.files.hooks.client); if (client_hooks) allow.add(path.dirname(client_hooks)); + const generated = path.posix.join(kit.outDir, 'generated'); + // dev and preview config can be shared /** @type {import('vite').UserConfig} */ const new_config = { resolve: { alias: [ + { find: '__SERVER__', replacement: `${generated}/server` }, { find: '$app', replacement: `${runtime_directory}/app` }, ...get_config_aliases(kit, root) ] @@ -661,10 +666,12 @@ function kit({ svelte_config, adapter_in_vite_config }) { exactRegex(env_dynamic_private), exactRegex(env_dynamic_public), exactRegex(service_worker), + exactRegex(sveltekit_environment), exactRegex(sveltekit_server), exactRegex(sveltekit_ssr_manifest), exactRegex(sveltekit_server_assets), - exactRegex(sveltekit_remotes) + exactRegex(sveltekit_remotes), + exactRegex(sveltekit_dev_server) ] }, handler(id) { @@ -701,8 +708,42 @@ function kit({ svelte_config, adapter_in_vite_config }) { case service_worker: return create_service_worker_module(svelte_config); + case sveltekit_environment: { + const { version } = svelte_config.kit; + + return dedent` + export const version = ${s(version.name)}; + export let building = false; + export let prerendering = false; + + export function set_building() { + building = true; + } + + export function set_prerendering() { + prerendering = true; + } + `; + } + + case sveltekit_server: { + return dedent` + export let read_implementation = null; + + export let manifest = null; + + export function set_read_implementation(fn) { + read_implementation = fn; + } + + export function set_manifest(_) { + manifest = _; + } + `; + } + case sveltekit_server_assets: { - if (vite_config_env.command === 'build') return; + if (!dev_environment) return; return dedent` export const server_assets = { @@ -966,7 +1007,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { `; } - case sveltekit_server: { + case sveltekit_dev_server: { if (!dev_environment) return; const runtime_base = get_runtime_base(root); @@ -2143,26 +2184,8 @@ function kit({ svelte_config, adapter_in_vite_config }) { } }; - /** @type {import('vite').Plugin} */ - const plugin_generated = { - name: 'vite-plugin-sveltekit-resolve-generated', - resolveId: { - order: 'pre', - filter: { - id: /\/generated\.js$/ - }, - handler(_, importer) { - const generated = path.posix.join(kit.outDir, 'generated'); - if (importer?.startsWith(runtime_directory)) { - return `${generated}/server/internal.js`; - } - } - } - }; - return [ plugin_setup, - plugin_generated, plugin_remote, plugin_server_filesystem, plugin_virtual_modules, diff --git a/packages/kit/src/exports/vite/module_ids.js b/packages/kit/src/exports/vite/module_ids.js index 0580f0c8bbb2..1cd70e974a8e 100644 --- a/packages/kit/src/exports/vite/module_ids.js +++ b/packages/kit/src/exports/vite/module_ids.js @@ -13,6 +13,7 @@ export const sveltekit_server = '\0virtual:__sveltekit/server'; export const sveltekit_ssr_manifest = '\0virtual:__sveltekit/ssr-manifest'; export const sveltekit_server_assets = '\0virtual:__sveltekit/server-assets'; export const sveltekit_remotes = '\0virtual:__sveltekit/remotes'; +export const sveltekit_dev_server = '\0virtual:__sveltekit/dev-server'; export const app_server = posixify( fileURLToPath(new URL('../../runtime/app/server/index.js', import.meta.url)) diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index ce7c411079b8..8259b1cfccd1 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -365,10 +365,9 @@ export async function render_response({ } if (!client.inline) { - // client import paths have no leading slash `_app/immutable/*` so we need to add one - const included_modulepreloads = Array.from(modulepreloads, (dep) => - prefixed('/' + dep) - ).filter((path) => resolve_opts.preload({ type: 'js', path })); + const included_modulepreloads = Array.from(modulepreloads, (dep) => prefixed(dep)).filter( + (path) => resolve_opts.preload({ type: 'js', path }) + ); for (const path of included_modulepreloads) { link_headers.add(`<${encodeURI(path)}>; rel="modulepreload"; nopush`); diff --git a/packages/kit/src/types/virtual.d.ts b/packages/kit/src/types/virtual.d.ts index 67afea6c688c..49f9211439f0 100644 --- a/packages/kit/src/types/virtual.d.ts +++ b/packages/kit/src/types/virtual.d.ts @@ -10,7 +10,7 @@ declare module '__sveltekit/ssr-manifest' { export const root: string; } -declare module '__sveltekit/server' { +declare module '__sveltekit/dev-server' { import { InternalServer } from 'types'; export { InternalServer as Server }; From 25f4b05a19863dae8d7249f42ad5bb3836e7edd1 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Thu, 2 Apr 2026 22:05:15 +0800 Subject: [PATCH 063/117] line order --- 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 1153f59c76bc..dc7fc104ed2f 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -44,11 +44,11 @@ import { env_static_private, env_static_public, service_worker, - sveltekit_server, sveltekit_remotes, sveltekit_server_assets, sveltekit_ssr_manifest, sveltekit_environment, + sveltekit_server, sveltekit_dev_server } from './module_ids.js'; import { to_fs } from './filesystem.js'; From 3373a34012c072bfe0ebeaaf6c3864a2248e74e5 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Fri, 3 Apr 2026 17:03:32 +0800 Subject: [PATCH 064/117] remove module runner usage --- packages/kit/src/exports/vite/dev/index.js | 42 +-- packages/kit/src/exports/vite/dev/server.js | 3 +- packages/kit/src/exports/vite/index.js | 278 +++++++++----------- packages/kit/src/exports/vite/module_ids.js | 1 + 4 files changed, 134 insertions(+), 190 deletions(-) diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 8d3b721f0d67..105b25a386e0 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -382,6 +382,7 @@ function has_correct_case(file, assets) { /** * @param {import('vite').ViteDevServer} vite * @param {string} id + * @returns {void} */ export function invalidate_module(vite, id) { for (const environment in vite.environments) { @@ -393,37 +394,11 @@ export function invalidate_module(vite, id) { } /** - * @param {import('types').ManifestData} manifest_data - * @param {import('vite/module-runner').ModuleRunner} runner - * @param {string} root - * @returns {Promise>} - */ -export async function get_matchers(manifest_data, runner, root) { - /** @type {Record} */ - const matchers = {}; - - for (const key in manifest_data.matchers) { - const file = manifest_data.matchers[key]; - const url = path.resolve(root, file); - const module = await runner.import(url); - - if (module.match) { - matchers[key] = module.match; - } else { - throw new Error(`${file} does not export a \`match\` function`); - } - } - - return matchers; -} - -/** - * * @param {import('vite').ViteDevServer} vite - * @param {import('vite/module-runner').ModuleRunner} runner * @param {string[]} urls + * @returns {Promise>} */ -export async function get_inline_css(vite, runner, urls) { +export async function get_inline_css(vite, urls) { /** @type {Set} */ const deps = new Set(); @@ -438,17 +413,10 @@ export async function get_inline_css(vite, runner, urls) { for (const dep of deps) { if (isCSSRequest(dep.url) && !vite_css_query_regex.test(dep.url)) { - const inlineCssUrl = dep.url.includes('?') + const inline_css_url = dep.url.includes('?') ? dep.url.replace('?', '?inline&') : dep.url + '?inline'; - try { - const mod = await runner.import(inlineCssUrl); - styles.set(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 - } + styles.set(dep.url, inline_css_url); } } diff --git a/packages/kit/src/exports/vite/dev/server.js b/packages/kit/src/exports/vite/dev/server.js index 6f7633d277ea..44da275eaf2c 100644 --- a/packages/kit/src/exports/vite/dev/server.js +++ b/packages/kit/src/exports/vite/dev/server.js @@ -13,11 +13,10 @@ await server.init({ }); /** - * * @param {Request} request * @param {string | undefined} remote_address * @param {import('types').ValidatedKitConfig} kit - * @returns + * @returns {Promise} */ export async function respond(request, remote_address, kit) { return await server.respond(request, { diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index dc7fc104ed2f..248064a34e6d 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -21,7 +21,7 @@ import { runtime_directory, logger, get_runtime_base, get_mime_lookup } from '.. import { generate_manifest } from '../../core/generate_manifest/index.js'; import { build_server_nodes } from './build/build_server.js'; import { assets_base, find_deps, resolve_symlinks } from './build/utils.js'; -import { dev, invalidate_module, get_matchers, get_inline_css } from './dev/index.js'; +import { dev, invalidate_module, get_inline_css } from './dev/index.js'; import { preview } from './preview/index.js'; import { error_for_missing_config, @@ -49,7 +49,8 @@ import { sveltekit_ssr_manifest, sveltekit_environment, sveltekit_server, - sveltekit_dev_server + sveltekit_dev_server, + sveltekit_traced } from './module_ids.js'; import { to_fs } from './filesystem.js'; import { import_peer } from '../../utils/import.js'; @@ -315,19 +316,6 @@ function kit({ svelte_config, adapter_in_vite_config }) { const sourcemapIgnoreList = /** @param {string} relative_path */ (relative_path) => relative_path.includes('node_modules') || relative_path.includes(kit.outDir); - /** - * Only available if the adapter didn't provide its own environment - * @type {import('vite/module-runner').ModuleRunner | null} - */ - let runner = null; - - function get_module_runner() { - if (!runner) { - throw new Error('The module runner should have been created during the configureServer hook'); - } - return runner; - } - /** @type {import('vite').Plugin} */ const plugin_setup = { name: 'vite-plugin-sveltekit-setup', @@ -512,32 +500,28 @@ function kit({ svelte_config, adapter_in_vite_config }) { new_config.environments = { ssr: { + // this node environment is the fallback if the adapter doesn't + // specify its own dev environment dev: { createEnvironment(name, config) { + if (!dev_environment) throw new Error('This should never happen'); + + const module_runner = createServerModuleRunner( + dev_environment.vite.environments.ssr + ); + return createFetchableDevEnvironment(name, config, { hot: true, transport: createServerHotChannel(), async handleRequest(request) { try { - const module_runner = get_module_runner(); - - const resolved_instrumentation = resolve_entry( - path.join(svelte_config.kit.files.src, 'instrumentation.server') - ); - if (resolved_instrumentation) { - await module_runner.import(resolved_instrumentation); - } - - /** - * @type {{ - * respond: (request: Request, remote_address: string | undefined, kit: import('types').ValidatedKitConfig) => Promise - * }} - */ + /** @type {import('./dev/server.js')} */ const { respond } = await module_runner.import( - import.meta.resolve('./dev/server.js') + '__sveltekit/dev-server-entry' ); return await respond(request, dev_environment?.remote_address, kit); } catch (error) { + // Vite doesn't log the thrown error so we have to do it ourselves console.error(error); throw error; } @@ -654,6 +638,13 @@ function kit({ svelte_config, adapter_in_vite_config }) { return `${runtime_directory}/client/remote-functions/index.js`; } + if (id === '__sveltekit/dev-server-entry') { + const resolved_instrumentation = resolve_entry( + path.join(svelte_config.kit.files.src, 'instrumentation.server') + ); + return resolved_instrumentation ? sveltekit_traced : import.meta.resolve('./dev/server.js'); + } + if (id.startsWith('__sveltekit/')) { return `\0virtual:${id}`; } @@ -775,16 +766,20 @@ function kit({ svelte_config, adapter_in_vite_config }) { const { manifest_data, env } = dev_environment; + const runtime_base = get_runtime_base(root); + return dedent` import { server_assets } from '__sveltekit/server-assets'; import { remotes } from '__sveltekit/remotes'; - import { to_fs } from '${get_runtime_base(root)}/../exports/vite/filesystem.js'; + import { to_fs } from '${runtime_base}/../exports/vite/filesystem.js'; + import * as path from '${runtime_base}/../utils/path.js'; export const base_path = ${s(kit.paths.base)}; export const prerendered = new Set(); export const env = ${s(env)}; const nodes = ${devalue.uneval(manifest_data.nodes, revive_functions)}; + const matchers = ${s(Object.entries(manifest_data.matchers))}; export const manifest = { appDir: ${s(kit.appDir)}, @@ -793,24 +788,23 @@ function kit({ svelte_config, adapter_in_vite_config }) { mimeTypes: ${s(get_mime_lookup(manifest_data))}, _: { client: { - start: '${get_runtime_base(root)}/client/entry.js', + start: '${runtime_base}/client/entry.js', app: '${to_fs(kit.outDir)}/generated/client/app.js', imports: [], stylesheets: [], fonts: [], uses_env_dynamic_public: true, - nodes: - ${ - kit.router.resolution === 'client' - ? undefined - : s( - manifest_data.nodes.map((node, i) => { - if (node.component || node.universal) { - return `${kit.paths.base}${to_fs(kit.outDir)}/generated/client/nodes/${i}.js`; - } - }) - ) - }, + nodes: ${ + kit.router.resolution === 'client' + ? undefined + : dedent` + nodes.map((node, i) => { + if (node.component || node.universal) { + return \`${kit.paths.base}\${to_fs(${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: ${ @@ -842,91 +836,83 @@ function kit({ svelte_config, adapter_in_vite_config }) { } }, server_assets, - nodes: [${manifest_data.nodes - .map((node, index) => { - return dedent` - async () => { - const node = nodes[${index}]; - - 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 = []; - - const urls = []; - - ${ - node.component - ? dedent` - result.component = async () => { - const { module, url } = await resolve(${s(path.resolve(root, node.component))}); - urls.push(url); - return module.default; - } - ` - : '' - } + nodes: nodes.map(async (node, i) => { + 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 = []; + + const urls = []; + + if (node.component) { + result.component = async () => { + const { module, url } = await resolve( + path.resolve(__SVELTEKIT_ROOT__, node.component) + ); + urls.push(url); + return module.default; + }; + } - ${ - node.universal - ? dedent` - 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, url } = await resolve(${s(path.resolve(root, node.universal))}); - urls.push(url); - result.universal = module; - } - ` - : '' - } + 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, url } = await resolve( + path.resolve(__SVELTEKIT_ROOT__, node.universal) + ); + urls.push(url); + result.universal = module; + } + } - ${ - node.server - ? dedent` - const { module } = await resolve(${s(path.resolve(root, node.server))}); - result.server = module; - ` - : '' - } + if (node.server) { + const { module } = await resolve( + path.resolve(__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 + // in dev we inline all styles to avoid FOUC. this gets populated lazily so that + // components/stylesheets loaded via import() during \`load\` are included - const event = 'sveltekit:inline-styles-node-${index}-response'; - result.inline_styles = async () => { - if (!import.meta.hot) return; + const event = \`sveltekit:inline-styles-node-\${i}-response\`; + result.inline_styles = async () => { + if (!import.meta.hot) throw new Error('hmr must be enabled in the dev environment'); - const { promise, resolve } = Promise.withResolvers(); + const { promise, resolve } = Promise.withResolvers(); - const listener = (data) => { - import.meta.hot.off(event, listener); - resolve(data); - }; + const listener = async (styles) => { + import.meta.hot.off(event, listener); + const importing_styles = Object.entries(styles).map(async ([dep_url, inline_css_url]) => { + return [dep_url, await import(/* @vite-ignore */ inline_css_url).then((mod) => mod.default)]; + }); + resolve(Object.fromEntries(await Promise.all(importing_styles))); + }; - import.meta.hot.on(event, listener); - import.meta.hot.send('sveltekit:inline-styles-request', { urls, node: result.index }); + import.meta.hot.on(event, listener); + import.meta.hot.send('sveltekit:inline-styles-request', { + urls, + node: result.index + }); - return promise; - } + return promise; + }; - return result; - } - `; - }) - .join(',\n')}], + return result; + }), prerendered_routes: new Set(), get remotes() { return Object.fromEntries( remotes.map((remote) => [ remote.hash, - () => import(/* @vite-ignore */(\`${root}/\${remote.file}\`)).then( + () => import(/* @vite-ignore */(\`\${__SVELTEKIT_ROOT__}/\${remote.file}\`)).then( (module) => ({ default: module }) ) ]) @@ -948,7 +934,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { endpoint ? dedent` async () => { - const url = ${s(path.resolve(root, endpoint.file))}; + const url = path.resolve(__SVELTEKIT_ROOT__, ${s(endpoint.file)}); const { module } = await resolve(url); return module; } @@ -961,27 +947,20 @@ function kit({ svelte_config, adapter_in_vite_config }) { }) ).join(',\n')}], matchers: async () => { - if (!import.meta.hot) return; - - const event = 'sveltekit:matchers-response'; - const { promise, resolve } = Promise.withResolvers(); - - const listener = (data) => { - import.meta.hot.off(event, listener); - resolve(data); - }; - - import.meta.hot.on(event, listener); - import.meta.hot.send('sveltekit:matchers-request'); - - return promise; + const importing_matchers = matchers.map(async ([name, file]) => { + const url = path.resolve(__SVELTEKIT_ROOT__, file); + const { module } = await resolve(url); + if (!module.match) { + throw new Error(\`\${file} does not export a \\\`match\\\` function\`); + } + return [name, module.match]; + }); + return Object.fromEntries(await Promise.all(importing_matchers)); } } }; - /** - * @param {string} url - */ + /** @param {string} url */ async function loud_ssr_load_module(url) { try { return await import(/* @vite-ignore */ url); @@ -1057,6 +1036,17 @@ function kit({ svelte_config, adapter_in_vite_config }) { } `; } + + case sveltekit_traced: { + return dedent` + import '${resolve_entry(path.join(svelte_config.kit.files.src, 'instrumentation.server'))}'; + + const { default: _0 } = await import('${import.meta.resolve('./dev/server.js')}'); + export { _0 as default }; + + import.meta.hot?.accept(); + `; + } } } } @@ -1479,8 +1469,6 @@ function kit({ svelte_config, adapter_in_vite_config }) { } }; - /** @type {() => Promise} */ - let handle_matchers; /** @type {(payload: { urls: string[]; node: number; }) => Promise} */ let handle_inline_styles; /** @type {(error: Error) => void} */ @@ -1772,35 +1760,23 @@ function kit({ svelte_config, adapter_in_vite_config }) { * Adds the SvelteKit middleware to do SSR in dev mode. * @see https://vitejs.dev/guide/api-plugin.html#configureserver */ - async configureServer(vite) { - await runner?.close(); - vite.environments.ssr.hot.off('sveltekit:matchers-request', handle_matchers); + configureServer(vite) { vite.environments.ssr.hot.off('sveltekit:inline-styles-request', handle_inline_styles); vite.environments.ssr.hot.off('sveltekit:ssr-load-module', handle_ssr_load_module); manifest_data = sync.all(svelte_config, vite_config_env.mode, root).manifest_data; // other properties will be populated during the `dev` function - const info = (dev_environment = /** @type {import('types').DevEnvironment} */ ({ + dev_environment = /** @type {import('types').DevEnvironment} */ ({ vite, env: loadEnv(vite_config.mode, svelte_config.kit.env.dir, ''), manifest_data - })); - - const module_runner = (runner = createServerModuleRunner(vite.environments.ssr)); - - handle_matchers ??= async () => { - vite.environments.ssr.hot.send( - 'sveltekit:matchers-response', - await get_matchers(info.manifest_data, module_runner, root) - ); - }; - vite.environments.ssr.hot.on('sveltekit:matchers-request', handle_matchers); + }); handle_inline_styles ??= async ({ urls, node }) => { vite.environments.ssr.hot.send( `sveltekit:inline-styles-node-${node}-response`, - await get_inline_css(vite, module_runner, urls) + await get_inline_css(vite, urls) ); }; vite.environments.ssr.hot.on('sveltekit:inline-styles-request', handle_inline_styles); diff --git a/packages/kit/src/exports/vite/module_ids.js b/packages/kit/src/exports/vite/module_ids.js index 1cd70e974a8e..a8d31ab4286f 100644 --- a/packages/kit/src/exports/vite/module_ids.js +++ b/packages/kit/src/exports/vite/module_ids.js @@ -14,6 +14,7 @@ export const sveltekit_ssr_manifest = '\0virtual:__sveltekit/ssr-manifest'; export const sveltekit_server_assets = '\0virtual:__sveltekit/server-assets'; export const sveltekit_remotes = '\0virtual:__sveltekit/remotes'; export const sveltekit_dev_server = '\0virtual:__sveltekit/dev-server'; +export const sveltekit_traced = '\0virtual:__sveltekit/traced'; export const app_server = posixify( fileURLToPath(new URL('../../runtime/app/server/index.js', import.meta.url)) From 5ee8b3fcdda8c050b949482b059ee58c3fe10ed3 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Sat, 4 Apr 2026 21:19:55 +0800 Subject: [PATCH 065/117] move ssr manifest to file --- .../kit/src/exports/vite/dev/ssr-manifest.js | 202 ++++++++++++++ packages/kit/src/exports/vite/index.js | 263 +++--------------- packages/kit/src/exports/vite/module_ids.js | 1 + packages/kit/src/types/virtual.d.ts | 36 ++- packages/kit/src/utils/path.js | 9 + 5 files changed, 288 insertions(+), 223 deletions(-) create mode 100644 packages/kit/src/exports/vite/dev/ssr-manifest.js 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..d25d2f0b44c8 --- /dev/null +++ b/packages/kit/src/exports/vite/dev/ssr-manifest.js @@ -0,0 +1,202 @@ +import { server_assets } from '__sveltekit/server-assets'; +import { remotes } from '__sveltekit/remotes'; +import { env, kit, manifest_data, mime_types } from '__sveltekit/manifest-data'; +import { to_fs } from '../filesystem.js'; +import { compact } from '../../../utils/array.js'; +import { join } from '../../../utils/path.js'; +import { runtime_directory } from '../../../core/utils.js'; + +export { env }; +export const base_path = kit.paths.base; +export const prerendered = new Set(); + +export const manifest = { + appDir: kit.appDir, + appPath: kit.appDir, + assets: new Set(manifest_data.assets.map((asset) => asset.file)), + mimeTypes: mime_types, + _: { + client: { + start: `${runtime_directory}/client/entry.js`, + app: `${to_fs(kit.outDir)}/generated/client/app.js`, + imports: [], + stylesheets: [], + fonts: [], + uses_env_dynamic_public: true, + nodes: + kit.router.resolution === 'client' + ? undefined + : manifest_data.nodes.map((node, i) => { + if (node.component || node.universal) { + return `${kit.paths.base}${to_fs(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: + 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, + 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 {string[]} */ + const urls = []; + + if (node.component) { + result.component = async () => { + const { module, url } = await resolve( + join(__SVELTEKIT_ROOT__, /** @type {string} */ (node.component)) + ); + urls.push(url); + 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, url } = await resolve(join(__SVELTEKIT_ROOT__, node.universal)); + urls.push(url); + 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 + + const event = `sveltekit:inline-styles-node-${i}-response`; + result.inline_styles = async () => { + if (!import.meta.hot) throw new Error('hmr must be enabled in the dev environment'); + + const { promise, resolve } = Promise.withResolvers(); + + /** @param {Record} styles */ + const listener = async (styles) => { + import.meta.hot?.off(event, listener); + const importing_styles = Object.entries(styles).map( + async ([dep_url, inline_css_url]) => { + return [dep_url, await import(/* @vite-ignore */ inline_css_url).then((mod) => mod.default)]; + } + ); + resolve(Object.fromEntries(await Promise.all(importing_styles))); + }; + + import.meta.hot.on(event, listener); + import.meta.hot.send('sveltekit:inline-styles-request', { + urls, + node: result.index + }); + + return promise; + }; + + return result; + }; + }), + prerendered_routes: new Set(), + get remotes() { + return Object.fromEntries( + remotes.map((remote) => [ + remote.hash, + () => + import(/* @vite-ignore */ join(__SVELTEKIT_ROOT__, 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 () => { + const importing_matchers = manifest_data.matchers.map(async ([name, file]) => { + const { module } = await resolve(file); + if (!module.match) { + throw new Error(`${file} does not export a \`match\` function`); + } + return [name, module.match]; + }); + return Object.fromEntries(await Promise.all(importing_matchers)); + } + } +}; + +/** @param {string} url */ +async function loud_ssr_load_module(url) { + try { + return await import(/* @vite-ignore */ url); + } catch (err) { + if (err instanceof Error) { + import.meta.hot?.send('sveltekit:ssr-load-module', { + ...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(id) : `file:///${id}`; + const module = await loud_ssr_load_module(url); + return { module, url }; +} diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 248064a34e6d..3c95ed9f4615 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -46,11 +46,11 @@ import { service_worker, sveltekit_remotes, sveltekit_server_assets, - sveltekit_ssr_manifest, sveltekit_environment, sveltekit_server, sveltekit_dev_server, - sveltekit_traced + sveltekit_traced, + sveltekit_manifest_data } from './module_ids.js'; import { to_fs } from './filesystem.js'; import { import_peer } from '../../utils/import.js'; @@ -316,6 +316,9 @@ function kit({ svelte_config, adapter_in_vite_config }) { const sourcemapIgnoreList = /** @param {string} relative_path */ (relative_path) => relative_path.includes('node_modules') || relative_path.includes(kit.outDir); + /** @type {import('vite/module-runner').ModuleRunner | null} */ + let module_runner = null; + /** @type {import('vite').Plugin} */ const plugin_setup = { name: 'vite-plugin-sveltekit-setup', @@ -503,17 +506,26 @@ function kit({ svelte_config, adapter_in_vite_config }) { // this node environment is the fallback if the adapter doesn't // specify its own dev environment dev: { - createEnvironment(name, config) { - if (!dev_environment) throw new Error('This should never happen'); - - const module_runner = createServerModuleRunner( - dev_environment.vite.environments.ssr - ); + async createEnvironment(name, config) { + if (module_runner) { + await module_runner?.close(); + module_runner = null; + } return createFetchableDevEnvironment(name, config, { hot: true, transport: createServerHotChannel(), async handleRequest(request) { + if (!dev_environment) { + throw new Error( + 'The Vite dev server was not found. But this should never happen' + ); + } + + module_runner ??= createServerModuleRunner( + dev_environment.vite.environments.ssr + ); + try { /** @type {import('./dev/server.js')} */ const { respond } = await module_runner.import( @@ -638,11 +650,17 @@ function kit({ svelte_config, adapter_in_vite_config }) { return `${runtime_directory}/client/remote-functions/index.js`; } + if (id === '__sveltekit/ssr-manifest') { + return path.join(import.meta.dirname, 'dev/ssr-manifest.js'); + } + if (id === '__sveltekit/dev-server-entry') { const resolved_instrumentation = resolve_entry( path.join(svelte_config.kit.files.src, 'instrumentation.server') ); - return resolved_instrumentation ? sveltekit_traced : import.meta.resolve('./dev/server.js'); + return resolved_instrumentation + ? sveltekit_traced + : path.join(import.meta.dirname, 'dev/server.js'); } if (id.startsWith('__sveltekit/')) { @@ -659,7 +677,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { exactRegex(service_worker), exactRegex(sveltekit_environment), exactRegex(sveltekit_server), - exactRegex(sveltekit_ssr_manifest), + exactRegex(sveltekit_manifest_data), exactRegex(sveltekit_server_assets), exactRegex(sveltekit_remotes), exactRegex(sveltekit_dev_server) @@ -761,227 +779,30 @@ function kit({ svelte_config, adapter_in_vite_config }) { `; } - case sveltekit_ssr_manifest: { + case sveltekit_manifest_data: { if (!dev_environment) return; const { manifest_data, env } = dev_environment; - const runtime_base = get_runtime_base(root); - return dedent` - import { server_assets } from '__sveltekit/server-assets'; - import { remotes } from '__sveltekit/remotes'; - import { to_fs } from '${runtime_base}/../exports/vite/filesystem.js'; - import * as path from '${runtime_base}/../utils/path.js'; - - export const base_path = ${s(kit.paths.base)}; - export const prerendered = new Set(); export const env = ${s(env)}; - const nodes = ${devalue.uneval(manifest_data.nodes, revive_functions)}; - const matchers = ${s(Object.entries(manifest_data.matchers))}; - - export const manifest = { - appDir: ${s(kit.appDir)}, - appPath: ${s(kit.appDir)}, - assets: new Set(${s(manifest_data.assets.map((asset) => asset.file))}), - mimeTypes: ${s(get_mime_lookup(manifest_data))}, - _: { - client: { - start: '${runtime_base}/client/entry.js', - app: '${to_fs(kit.outDir)}/generated/client/app.js', - imports: [], - stylesheets: [], - fonts: [], - uses_env_dynamic_public: true, - nodes: ${ - kit.router.resolution === 'client' - ? undefined - : dedent` - nodes.map((node, i) => { - if (node.component || node.universal) { - return \`${kit.paths.base}\${to_fs(${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: - ${ - kit.router.resolution === 'client' - ? undefined - : devalue.uneval( - 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, - nodes: nodes.map(async (node, i) => { - 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 = []; - - const urls = []; - - if (node.component) { - result.component = async () => { - const { module, url } = await resolve( - path.resolve(__SVELTEKIT_ROOT__, node.component) - ); - urls.push(url); - 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, url } = await resolve( - path.resolve(__SVELTEKIT_ROOT__, node.universal) - ); - urls.push(url); - result.universal = module; - } - } - - if (node.server) { - const { module } = await resolve( - path.resolve(__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 - - const event = \`sveltekit:inline-styles-node-\${i}-response\`; - result.inline_styles = async () => { - if (!import.meta.hot) throw new Error('hmr must be enabled in the dev environment'); - - const { promise, resolve } = Promise.withResolvers(); - - const listener = async (styles) => { - import.meta.hot.off(event, listener); - const importing_styles = Object.entries(styles).map(async ([dep_url, inline_css_url]) => { - return [dep_url, await import(/* @vite-ignore */ inline_css_url).then((mod) => mod.default)]; - }); - resolve(Object.fromEntries(await Promise.all(importing_styles))); - }; - - import.meta.hot.on(event, listener); - import.meta.hot.send('sveltekit:inline-styles-request', { - urls, - node: result.index - }); - - return promise; - }; - - return result; - }), - prerendered_routes: new Set(), - get remotes() { - return Object.fromEntries( - remotes.map((remote) => [ - remote.hash, - () => import(/* @vite-ignore */(\`\${__SVELTEKIT_ROOT__}/\${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 dedent` - { - id: ${s(route.id)}, - pattern: ${devalue.uneval(route.pattern)}, - params: ${devalue.uneval(route.params)}, - page: ${devalue.uneval(route.page)}, - endpoint: ${ - endpoint - ? dedent` - async () => { - const url = path.resolve(__SVELTEKIT_ROOT__, ${s(endpoint.file)}); - const { module } = await resolve(url); - return module; - } - ` - : null - }, - endpoint_id: ${s(endpoint?.file)} - } - `; - }) - ).join(',\n')}], - matchers: async () => { - const importing_matchers = matchers.map(async ([name, file]) => { - const url = path.resolve(__SVELTEKIT_ROOT__, file); - const { module } = await resolve(url); - if (!module.match) { - throw new Error(\`\${file} does not export a \\\`match\\\` function\`); - } - return [name, module.match]; - }); - return Object.fromEntries(await Promise.all(importing_matchers)); - } - } + export const manifest_data = { + routes: ${devalue.uneval(manifest_data.routes)}, + nodes: ${devalue.uneval(manifest_data.nodes, revive_functions)}, + matchers: ${s(Object.entries(manifest_data.matchers))}, + assets: ${s(manifest_data.assets)} }; - /** @param {string} url */ - async function loud_ssr_load_module(url) { - try { - return await import(/* @vite-ignore */ url); - } catch (err) { - import.meta.hot?.send('sveltekit:ssr-load-module', { - ...err, - // these properties are non-enumerable and will not be - // serialized unless we explicitly include them - message: err.message, - stack: err.stack - }); - - throw err; - } - } + export const mime_types = ${s(get_mime_lookup(manifest_data))}; - /** @param {string} id */ - async function resolve(id) { - const url = id.startsWith('..') ? to_fs(id) : \`file:///\${id}\`; - const module = await loud_ssr_load_module(url); - return { module, url }; + export const kit = { + appDir: ${s(kit.appDir)}, + outDir: ${s(kit.outDir)}, + router: { + resolution: ${s(kit.router.resolution)}, + }, + paths: ${s(kit.paths)} } `; } diff --git a/packages/kit/src/exports/vite/module_ids.js b/packages/kit/src/exports/vite/module_ids.js index a8d31ab4286f..d1b7588ea253 100644 --- a/packages/kit/src/exports/vite/module_ids.js +++ b/packages/kit/src/exports/vite/module_ids.js @@ -11,6 +11,7 @@ export const service_worker = '\0virtual:service-worker'; export const sveltekit_environment = '\0virtual:__sveltekit/environment'; export const sveltekit_server = '\0virtual:__sveltekit/server'; export const sveltekit_ssr_manifest = '\0virtual:__sveltekit/ssr-manifest'; +export const sveltekit_manifest_data = '\0virtual:__sveltekit/manifest-data'; export const sveltekit_server_assets = '\0virtual:__sveltekit/server-assets'; export const sveltekit_remotes = '\0virtual:__sveltekit/remotes'; export const sveltekit_dev_server = '\0virtual:__sveltekit/dev-server'; diff --git a/packages/kit/src/types/virtual.d.ts b/packages/kit/src/types/virtual.d.ts index 49f9211439f0..efdfe6261a46 100644 --- a/packages/kit/src/types/virtual.d.ts +++ b/packages/kit/src/types/virtual.d.ts @@ -6,8 +6,6 @@ declare module '__sveltekit/ssr-manifest' { export const remote_address: string | undefined; export const base_path: string; export const prerendered: Set; - /** Allows us to fix up stack traces during development */ - export const root: string; } declare module '__sveltekit/dev-server' { @@ -15,3 +13,37 @@ declare module '__sveltekit/dev-server' { export { InternalServer as Server }; } + +declare module '__sveltekit/manifest-data' { + // eslint-disable-next-line no-duplicate-imports + import { Asset, PageNode, RouteData } from 'types'; + + export const env: Record; + export const kit: { + appDir: string; + outDir: string; + router: { + resolution: 'client' | 'server'; + }; + paths: { + assets: string; + base: string; + relative: boolean; + }; + }; + export const mime_types: string[]; + export const manifest_data: { + routes: RouteData[]; + nodes: PageNode[]; + matchers: string[][]; + assets: Asset[]; + }; +} + +declare module '__sveltekit/server-assets' { + export const server_assets: Record; +} + +declare module '__sveltekit/remotes' { + export const remotes: Array<{ hash: string; file: string }>; +} diff --git a/packages/kit/src/utils/path.js b/packages/kit/src/utils/path.js index a8cefe15f039..2ffca61e7f23 100644 --- a/packages/kit/src/utils/path.js +++ b/packages/kit/src/utils/path.js @@ -1,5 +1,6 @@ // This file contains Node-agnostic path utilities so that it can be used in // environments that do not have access to `node:path` (e.g. Cloudflare Workers). +import { posixify } from './os.js'; /** * @param {string} from @@ -13,3 +14,11 @@ export function relative(from, to) { while (i < from_parts.length && i < to_parts.length && from_parts[i] === to_parts[i]) i++; return [...Array(from_parts.length - i).fill('..'), ...to_parts.slice(i)].join('/') || '.'; } + +/** + * @param {...string} parts + * @returns {string} + */ +export function join(...parts) { + return parts.map(posixify).join('/'); +} From fa52c383d16b9c41a1274cc20c28e0aaaed7d3b0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 4 Apr 2026 15:30:24 +0000 Subject: [PATCH 066/117] chore: autofix lint --- 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 d25d2f0b44c8..f2c7b51caa52 100644 --- a/packages/kit/src/exports/vite/dev/ssr-manifest.js +++ b/packages/kit/src/exports/vite/dev/ssr-manifest.js @@ -110,7 +110,10 @@ export const manifest = { import.meta.hot?.off(event, listener); const importing_styles = Object.entries(styles).map( async ([dep_url, inline_css_url]) => { - return [dep_url, await import(/* @vite-ignore */ inline_css_url).then((mod) => mod.default)]; + return [ + dep_url, + await import(/* @vite-ignore */ inline_css_url).then((mod) => mod.default) + ]; } ); resolve(Object.fromEntries(await Promise.all(importing_styles))); From a2bf6ec083cea384464158b720ee5a594d994ff6 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Sat, 4 Apr 2026 23:48:42 +0800 Subject: [PATCH 067/117] Revert "chore: autofix lint" This reverts commit fa52c383d16b9c41a1274cc20c28e0aaaed7d3b0. --- packages/kit/src/exports/vite/dev/ssr-manifest.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/kit/src/exports/vite/dev/ssr-manifest.js b/packages/kit/src/exports/vite/dev/ssr-manifest.js index f2c7b51caa52..d25d2f0b44c8 100644 --- a/packages/kit/src/exports/vite/dev/ssr-manifest.js +++ b/packages/kit/src/exports/vite/dev/ssr-manifest.js @@ -110,10 +110,7 @@ export const manifest = { import.meta.hot?.off(event, listener); const importing_styles = Object.entries(styles).map( async ([dep_url, inline_css_url]) => { - return [ - dep_url, - await import(/* @vite-ignore */ inline_css_url).then((mod) => mod.default) - ]; + return [dep_url, await import(/* @vite-ignore */ inline_css_url).then((mod) => mod.default)]; } ); resolve(Object.fromEntries(await Promise.all(importing_styles))); From 67be19c866a1828db0e61095f7d57e50cb3ec040 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Sat, 4 Apr 2026 23:48:46 +0800 Subject: [PATCH 068/117] Reapply "chore: autofix lint" This reverts commit a2bf6ec083cea384464158b720ee5a594d994ff6. --- 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 d25d2f0b44c8..f2c7b51caa52 100644 --- a/packages/kit/src/exports/vite/dev/ssr-manifest.js +++ b/packages/kit/src/exports/vite/dev/ssr-manifest.js @@ -110,7 +110,10 @@ export const manifest = { import.meta.hot?.off(event, listener); const importing_styles = Object.entries(styles).map( async ([dep_url, inline_css_url]) => { - return [dep_url, await import(/* @vite-ignore */ inline_css_url).then((mod) => mod.default)]; + return [ + dep_url, + await import(/* @vite-ignore */ inline_css_url).then((mod) => mod.default) + ]; } ); resolve(Object.fromEntries(await Promise.all(importing_styles))); From 941c0c6a13d19dd30e39b29a69c1369296b2ee12 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Sun, 5 Apr 2026 00:18:16 +0800 Subject: [PATCH 069/117] forgot this --- packages/kit/src/exports/vite/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 3c95ed9f4615..af0bf8f670cc 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -680,6 +680,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { exactRegex(sveltekit_manifest_data), exactRegex(sveltekit_server_assets), exactRegex(sveltekit_remotes), + exactRegex(sveltekit_traced), exactRegex(sveltekit_dev_server) ] }, From 909cb2048d0583e87e2aded0ce5309effc502818 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Sun, 5 Apr 2026 00:27:42 +0800 Subject: [PATCH 070/117] forgot to re-export respond --- 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 af0bf8f670cc..5af007b818bc 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -863,8 +863,8 @@ function kit({ svelte_config, adapter_in_vite_config }) { return dedent` import '${resolve_entry(path.join(svelte_config.kit.files.src, 'instrumentation.server'))}'; - const { default: _0 } = await import('${import.meta.resolve('./dev/server.js')}'); - export { _0 as default }; + const { respond } = await import('${import.meta.resolve('./dev/server.js')}'); + export { respond }; import.meta.hot?.accept(); `; From c9ed8a0b64985bfc6b49db141484bef1b90ec62d Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Sun, 5 Apr 2026 00:32:11 +0800 Subject: [PATCH 071/117] add root to matcher filepath --- packages/kit/src/exports/vite/dev/ssr-manifest.js | 3 ++- 1 file changed, 2 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 f2c7b51caa52..403e558511a5 100644 --- a/packages/kit/src/exports/vite/dev/ssr-manifest.js +++ b/packages/kit/src/exports/vite/dev/ssr-manifest.js @@ -167,7 +167,8 @@ export const manifest = { ), matchers: async () => { const importing_matchers = manifest_data.matchers.map(async ([name, file]) => { - const { module } = await resolve(file); + const url = join(__SVELTEKIT_ROOT__, file); + const { module } = await resolve(url); if (!module.match) { throw new Error(`${file} does not export a \`match\` function`); } From 93569a784596b8cb3e678bf28a2c184856775037 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Mon, 6 Apr 2026 01:49:16 +0800 Subject: [PATCH 072/117] fix --- packages/kit/src/exports/vite/dev/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 105b25a386e0..648d1e793d7d 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -13,7 +13,7 @@ import { SVELTE_KIT_ASSETS } from '../../../constants.js'; import * as sync from '../../../core/sync/sync.js'; import { not_found } from '../utils.js'; import { escape_html } from '../../../utils/escape.js'; -import { sveltekit_ssr_manifest } from '../module_ids.js'; +import { sveltekit_manifest_data } from '../module_ids.js'; import { to_fs } from '../filesystem.js'; // vite-specifc queries that we should skip handling for css urls @@ -60,7 +60,7 @@ export function dev(vite, vite_config, svelte_config, root, dev_environment) { return; } - void invalidate_module(vite, sveltekit_ssr_manifest); + void invalidate_module(vite, sveltekit_manifest_data); } update_manifest(); @@ -108,7 +108,7 @@ export function dev(vite, vite_config, svelte_config, root, dev_environment) { if (timeout || restarting || !/\+(page|layout|server).*$/.test(file)) return; sync.update(svelte_config, manifest_data, file, root); // TODO: perform a partial update instead of invalidating the whole virtual module? - void invalidate_module(vite, sveltekit_ssr_manifest); + void invalidate_module(vite, sveltekit_manifest_data); }); const { appTemplate, errorTemplate, serviceWorker, hooks } = svelte_config.kit.files; From d4cbc8c6a2c9834cbcb956892b2e40655c5020b2 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Mon, 6 Apr 2026 16:37:17 +0800 Subject: [PATCH 073/117] fix --- packages/kit/src/exports/vite/build/remote.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/src/exports/vite/build/remote.js b/packages/kit/src/exports/vite/build/remote.js index ddd91c6a8927..65eac5f1762c 100644 --- a/packages/kit/src/exports/vite/build/remote.js +++ b/packages/kit/src/exports/vite/build/remote.js @@ -5,7 +5,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { Parser } from 'acorn'; import MagicString from 'magic-string'; -import { posixify } from '../../../utils/filesystem.js'; +import { posixify } from '../../../utils/os.js'; /** * @param {typeof import('vite')} vite From e274198429e817e4d6fbcf92d4cb5e69a1fba462 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Mon, 6 Apr 2026 17:01:44 +0800 Subject: [PATCH 074/117] consider base path --- packages/kit/src/runtime/server/page/render.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index 39a8e2b1102f..30e82b445777 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -624,7 +624,7 @@ export async function render_response({ // that Vite global constant replacements are initialised before our code runs const init_app = ` { - ${DEV ? `import('/@vite/client')` : ''} + ${DEV ? `import('${paths.base}/@vite/client')` : ''} ${blocks.join('\n\n\t\t\t\t\t')} } `; From 419bf40d6c4d8b71f31ea927872c7a8195be272d Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Mon, 6 Apr 2026 17:10:14 +0800 Subject: [PATCH 075/117] make it a public path --- 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 1cb2b736a93a..7c6863e428bb 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -530,7 +530,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { try { /** @type {import('./dev/server.js')} */ const { respond } = await module_runner.import( - '__sveltekit/dev-server-entry' + '@sveltekit/vite/environment' ); return await respond(request, dev_environment?.remote_address, kit); } catch (error) { @@ -655,7 +655,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { return path.join(import.meta.dirname, 'dev/ssr-manifest.js'); } - if (id === '__sveltekit/dev-server-entry') { + if (id === '@sveltekit/vite/environment') { const resolved_instrumentation = resolve_entry( path.join(svelte_config.kit.files.src, 'instrumentation.server') ); From 6ceb134f522b1b0bcded777d6ba0f3bab4b0c00a Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Mon, 6 Apr 2026 17:54:26 +0800 Subject: [PATCH 076/117] posixify --- packages/adapter-cloudflare/index.js | 35 -------------------------- packages/kit/src/exports/public.d.ts | 6 ----- packages/kit/src/exports/vite/index.js | 25 ++++++++++-------- packages/kit/types/index.d.ts | 6 ----- 4 files changed, 14 insertions(+), 58 deletions(-) diff --git a/packages/adapter-cloudflare/index.js b/packages/adapter-cloudflare/index.js index 66c8a2da246e..95507ca08c82 100644 --- a/packages/adapter-cloudflare/index.js +++ b/packages/adapter-cloudflare/index.js @@ -175,41 +175,6 @@ export default function (options = {}) { writeFileSync(`${dest}/.assetsignore`, generate_assetsignore(), { flag: 'a' }); } }, - emulate() { - // we want to invoke `getPlatformProxy` only once, but await it only when it is accessed. - // If we would await it here, it would hang indefinitely because the platform proxy only resolves once a request happens - const get_emulated = async () => { - const proxy = await getPlatformProxy(options.platformProxy); - const platform = /** @type {App.Platform} */ ({ - env: proxy.env, - ctx: proxy.ctx, - context: proxy.ctx, // deprecated in favor of ctx - caches: proxy.caches, - cf: proxy.cf - }); - /** @type {Record} */ - const env = {}; - const prerender_platform = /** @type {App.Platform} */ (/** @type {unknown} */ ({ env })); - for (const key in proxy.env) { - Object.defineProperty(env, key, { - get: () => { - throw new Error(`Cannot access platform.env.${key} in a prerenderable route`); - } - }); - } - return { platform, prerender_platform }; - }; - - /** @type {{ platform: App.Platform, prerender_platform: App.Platform }} */ - let emulated; - - return { - platform: async ({ prerender }) => { - emulated ??= await get_emulated(); - return prerender ? emulated.prerender_platform : emulated.platform; - } - }; - }, supports: { read: () => true, instrumentation: () => true diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index 8f05b85f09fe..9dd507fcbdf9 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -63,12 +63,6 @@ export interface Adapter { */ instrumentation?: () => boolean; }; - /** - * Creates an `Emulator`, which allows the adapter to influence the environment - * during dev, build and prerendering. - * @deprecated removed in 3.0.0 - */ - emulate?: () => MaybePromise; vite?: { /** * Add a Vite plugin here to replace the default Node SSR environment. diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 7c6863e428bb..1f2096701e0b 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -301,6 +301,9 @@ function kit({ svelte_config, adapter_in_vite_config }) { /** @type {import('node:path').ParsedPath} */ let parsed_service_worker; + /** @type {string | null} */ + let server_instrumentation_file; + /** @type {string} */ let normalized_cwd; /** @type {string} */ @@ -355,6 +358,9 @@ function kit({ svelte_config, adapter_in_vite_config }) { service_worker_entry_file = resolve_entry(kit.files.serviceWorker); parsed_service_worker = path.parse(kit.files.serviceWorker); + server_instrumentation_file = resolve_entry( + path.join(kit.files.src, 'instrumentation.server') + ); vite = await import_peer('vite', root); @@ -656,10 +662,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { } if (id === '@sveltekit/vite/environment') { - const resolved_instrumentation = resolve_entry( - path.join(svelte_config.kit.files.src, 'instrumentation.server') - ); - return resolved_instrumentation + return server_instrumentation_file ? sveltekit_traced : path.join(import.meta.dirname, 'dev/server.js'); } @@ -861,8 +864,11 @@ function kit({ svelte_config, adapter_in_vite_config }) { } case sveltekit_traced: { + if (!server_instrumentation_file) + throw new Error('Server instrumentation file not found. This should never happen'); + return dedent` - import '${resolve_entry(path.join(svelte_config.kit.files.src, 'instrumentation.server'))}'; + import '${posixify(server_instrumentation_file)}'; const { respond } = await import('${import.meta.resolve('./dev/server.js')}'); export { respond }; @@ -1366,13 +1372,10 @@ function kit({ svelte_config, adapter_in_vite_config }) { } // ...and the server instrumentation file - const server_instrumentation = resolve_entry( - path.join(kit.files.src, 'instrumentation.server') - ); - if (server_instrumentation) { + if (server_instrumentation_file) { const { adapter } = kit; if (adapter && !adapter.supports?.instrumentation?.()) { - throw new Error(`${server_instrumentation} is unsupported in ${adapter.name}.`); + throw new Error(`${server_instrumentation_file} is unsupported in ${adapter.name}.`); } if (!kit.experimental.instrumentation.server) { error_for_missing_config( @@ -1381,7 +1384,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { 'true' ); } - server_input['instrumentation.server'] = server_instrumentation; + server_input['instrumentation.server'] = server_instrumentation_file; } /** @type {Record} */ diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 8121f993ee23..c095bdfa3207 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -38,12 +38,6 @@ declare module '@sveltejs/kit' { */ instrumentation?: () => boolean; }; - /** - * Creates an `Emulator`, which allows the adapter to influence the environment - * during dev, build and prerendering. - * @deprecated removed in 3.0.0 - */ - emulate?: () => MaybePromise; vite?: { /** * Add a Vite plugin here to replace the default Node SSR environment. From a5b6f7eb4c02b601a8eb55972c0cf1284979fe8b Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Mon, 6 Apr 2026 18:12:05 +0800 Subject: [PATCH 077/117] docs draft --- .../99-writing-adapters.md | 33 ++++++++++++++----- packages/kit/src/exports/vite/index.js | 4 +-- 2 files changed, 26 insertions(+), 11 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 823e2ffea350..88ab516c20a3 100644 --- a/documentation/docs/25-build-and-deploy/99-writing-adapters.md +++ b/documentation/docs/25-build-and-deploy/99-writing-adapters.md @@ -21,14 +21,6 @@ export default function (options) { async adapt(builder) { // adapter implementation }, - async emulate() { - return { - async platform({ config, prerender }) { - // the returned object becomes `event.platform` during dev, build and - // preview. Its shape is that of `App.Platform` - } - } - }, supports: { read: ({ config, route }) => { // Return `true` if the route with the given `config` can use `read` @@ -39,6 +31,11 @@ export default function (options) { // Return `true` if this adapter supports loading `instrumentation.server.js`. // Return `false if it can't, or throw a descriptive error. } + }, + vite: { + plugins: [ + // add plugins here to integrate with Vite + ] } }; @@ -46,7 +43,7 @@ export default function (options) { } ``` -Of these, `name` and `adapt` are required. `emulate` and `supports` are optional. +Of these, `name` and `adapt` are required. `vite.plugins` and `supports` are optional. Within the `adapt` method, there are a number of things that an adapter should do: @@ -61,3 +58,21 @@ 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 server + +By default, SvelteKit runs your server code through a Node.js runtime during development and preview. You can change this by adding a Vite plugin to run code and respond to requests from [a different SSR environment](https://vite.dev/guide/api-environment-runtimes). + +The default Vite environment SvelteKit uses is named `ssr`. You can customise it by referencing it in the `config` hook of your Vite plugin. + +```js +config(userConfig) { + userConfig.environments.ssr = { ... } +} +``` + +If your development environment implements the `FetchableDevEnvironment` interface, you can import the `respond` method from `@sveltejs/kit/vite/environment` to have SvelteKit handle the request in your custom runtime. + +```js +import { respond } from '@sveltejs/kit/vite/environment'; +``` diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 1f2096701e0b..7441b36f18cd 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -536,7 +536,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { try { /** @type {import('./dev/server.js')} */ const { respond } = await module_runner.import( - '@sveltekit/vite/environment' + '@sveltejs/kit/vite/environment' ); return await respond(request, dev_environment?.remote_address, kit); } catch (error) { @@ -661,7 +661,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { return path.join(import.meta.dirname, 'dev/ssr-manifest.js'); } - if (id === '@sveltekit/vite/environment') { + if (id === '@sveltejs/kit/vite/environment') { return server_instrumentation_file ? sveltekit_traced : path.join(import.meta.dirname, 'dev/server.js'); From c13f2f402babd22e000c900df35ae49b31cb1d1f Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Mon, 6 Apr 2026 18:37:39 +0800 Subject: [PATCH 078/117] avoid node.js modules --- packages/adapter-cloudflare/index.js | 2 +- packages/kit/src/exports/vite/dev/ssr-manifest.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/adapter-cloudflare/index.js b/packages/adapter-cloudflare/index.js index 95507ca08c82..01186988e4e4 100644 --- a/packages/adapter-cloudflare/index.js +++ b/packages/adapter-cloudflare/index.js @@ -2,7 +2,7 @@ import { copyFileSync, existsSync, readFileSync, writeFileSync } from 'node:fs'; import path from 'node:path'; import process from 'node:process'; import { fileURLToPath } from 'node:url'; -import { getPlatformProxy, unstable_readConfig } from 'wrangler'; +import { unstable_readConfig } from 'wrangler'; import { is_building_for_cloudflare_pages, validate_worker_settings, diff --git a/packages/kit/src/exports/vite/dev/ssr-manifest.js b/packages/kit/src/exports/vite/dev/ssr-manifest.js index 403e558511a5..54d907f15f97 100644 --- a/packages/kit/src/exports/vite/dev/ssr-manifest.js +++ b/packages/kit/src/exports/vite/dev/ssr-manifest.js @@ -4,7 +4,7 @@ import { env, kit, manifest_data, mime_types } from '__sveltekit/manifest-data'; import { to_fs } from '../filesystem.js'; import { compact } from '../../../utils/array.js'; import { join } from '../../../utils/path.js'; -import { runtime_directory } from '../../../core/utils.js'; +import { posixify } from '../../../utils/os.js'; export { env }; export const base_path = kit.paths.base; @@ -17,7 +17,7 @@ export const manifest = { mimeTypes: mime_types, _: { client: { - start: `${runtime_directory}/client/entry.js`, + start: posixify(join(import.meta.dirname, '../../../runtime/client/entry.js')), app: `${to_fs(kit.outDir)}/generated/client/app.js`, imports: [], stylesheets: [], From b632257126744e6e6f3ace2b92c76b359f536df8 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Mon, 6 Apr 2026 19:02:08 +0800 Subject: [PATCH 079/117] docs? --- .../99-writing-adapters.md | 26 +++++++++- packages/kit/src/exports/vite/dev/server.js | 4 +- packages/kit/src/exports/vite/index.js | 6 +-- packages/kit/src/exports/vite/module_ids.js | 4 +- packages/kit/src/types/ambient-private.d.ts | 34 +++++++++++++ packages/kit/src/types/ambient.d.ts | 13 +++++ packages/kit/src/types/virtual.d.ts | 49 ------------------- 7 files changed, 78 insertions(+), 58 deletions(-) delete mode 100644 packages/kit/src/types/virtual.d.ts 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 88ab516c20a3..4283cc9380b2 100644 --- a/documentation/docs/25-build-and-deploy/99-writing-adapters.md +++ b/documentation/docs/25-build-and-deploy/99-writing-adapters.md @@ -71,8 +71,30 @@ config(userConfig) { } ``` -If your development environment implements the `FetchableDevEnvironment` interface, you can import the `respond` method from `@sveltejs/kit/vite/environment` to have SvelteKit handle the request in your custom runtime. +You can also create your own server entry file by importing `Server` from `@sveltejs/kit/vite/environment/server` and `env` and `manifest` from `@sveltejs/kit/vite/environment`. ```js -import { respond } from '@sveltejs/kit/vite/environment'; +import { Server } from '@sveltejs/kit/vite/environment/server'; +import { env, manifest } from '@sveltejs/kit/vite/environment'; + +const server = new Server(manifest); + +await server.init({ env }); + +/** + * @param {Request} request + * @returns {Promise} + */ +export async function respond(request) { + return await server.respond(request, { + getClientAddress: () => { + throw new Error('Could not determine clientAddress'); + }, + read: (file) => { + throw new Error('read is not supported in this environment'); + } + }); +} + +import.meta.hot?.accept(); ``` diff --git a/packages/kit/src/exports/vite/dev/server.js b/packages/kit/src/exports/vite/dev/server.js index 44da275eaf2c..55db3f0af17f 100644 --- a/packages/kit/src/exports/vite/dev/server.js +++ b/packages/kit/src/exports/vite/dev/server.js @@ -1,7 +1,7 @@ import fs from 'node:fs'; import path from 'node:path'; -import { Server } from '__sveltekit/dev-server'; -import { env, manifest } from '__sveltekit/ssr-manifest'; +import { Server } from '@sveltejs/kit/vite/environment/server'; +import { env, manifest } from '@sveltejs/kit/vite/environment'; import { createReadableStream } from '@sveltejs/kit/node'; import { from_fs } from '../filesystem.js'; diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 7441b36f18cd..c3faf404d1b4 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -536,7 +536,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { try { /** @type {import('./dev/server.js')} */ const { respond } = await module_runner.import( - '@sveltejs/kit/vite/environment' + '__sveltekit/dev-server-entry' ); return await respond(request, dev_environment?.remote_address, kit); } catch (error) { @@ -661,13 +661,13 @@ function kit({ svelte_config, adapter_in_vite_config }) { return path.join(import.meta.dirname, 'dev/ssr-manifest.js'); } - if (id === '@sveltejs/kit/vite/environment') { + if (id === '__sveltekit/dev-server-entry') { return server_instrumentation_file ? sveltekit_traced : path.join(import.meta.dirname, 'dev/server.js'); } - if (id.startsWith('__sveltekit/')) { + if (id.startsWith('__sveltekit/') && id !== '__sveltekit/dev-server-entry') { return `\0virtual:${id}`; } }, diff --git a/packages/kit/src/exports/vite/module_ids.js b/packages/kit/src/exports/vite/module_ids.js index d1b7588ea253..f3c5ce830c41 100644 --- a/packages/kit/src/exports/vite/module_ids.js +++ b/packages/kit/src/exports/vite/module_ids.js @@ -10,12 +10,12 @@ export const service_worker = '\0virtual:service-worker'; export const sveltekit_environment = '\0virtual:__sveltekit/environment'; export const sveltekit_server = '\0virtual:__sveltekit/server'; -export const sveltekit_ssr_manifest = '\0virtual:__sveltekit/ssr-manifest'; export const sveltekit_manifest_data = '\0virtual:__sveltekit/manifest-data'; export const sveltekit_server_assets = '\0virtual:__sveltekit/server-assets'; export const sveltekit_remotes = '\0virtual:__sveltekit/remotes'; -export const sveltekit_dev_server = '\0virtual:__sveltekit/dev-server'; export const sveltekit_traced = '\0virtual:__sveltekit/traced'; +export const sveltekit_ssr_manifest = '\0virtual:@sveltejs/kit/vite/environment'; +export const sveltekit_dev_server = '\0virtual:@sveltejs/kit/vite/environment/server'; 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 c98af8cb0062..a67b25fc87e5 100644 --- a/packages/kit/src/types/ambient-private.d.ts +++ b/packages/kit/src/types/ambient-private.d.ts @@ -27,3 +27,37 @@ declare module '__sveltekit/server' { export function set_manifest(manifest: SSRManifest): void; export function set_read_implementation(fn: (path: string) => ReadableStream): void; } + +declare module '__sveltekit/manifest-data' { + // eslint-disable-next-line no-duplicate-imports + import { Asset, PageNode, RouteData } from 'types'; + + export const env: Record; + export const kit: { + appDir: string; + outDir: string; + router: { + resolution: 'client' | 'server'; + }; + paths: { + assets: string; + base: string; + relative: boolean; + }; + }; + export const mime_types: string[]; + export const manifest_data: { + routes: RouteData[]; + nodes: PageNode[]; + matchers: string[][]; + assets: Asset[]; + }; +} + +declare module '__sveltekit/server-assets' { + export const server_assets: Record; +} + +declare module '__sveltekit/remotes' { + export const remotes: Array<{ hash: string; file: string }>; +} diff --git a/packages/kit/src/types/ambient.d.ts b/packages/kit/src/types/ambient.d.ts index b08a2f963160..d1f440eee04c 100644 --- a/packages/kit/src/types/ambient.d.ts +++ b/packages/kit/src/types/ambient.d.ts @@ -146,3 +146,16 @@ declare module '$app/types' { */ export type Asset = ReturnType; } + +declare module '@sveltejs/kit/vite/environment' { + import { SSRManifest } from '@sveltejs/kit'; + + export const manifest: SSRManifest; + export const env: Record; +} + +declare module '@sveltejs/kit/vite/environment/server' { + import { InternalServer } from 'types'; + + export { InternalServer as Server }; +} diff --git a/packages/kit/src/types/virtual.d.ts b/packages/kit/src/types/virtual.d.ts deleted file mode 100644 index efdfe6261a46..000000000000 --- a/packages/kit/src/types/virtual.d.ts +++ /dev/null @@ -1,49 +0,0 @@ -declare module '__sveltekit/ssr-manifest' { - import { SSRManifest } from '@sveltejs/kit'; - - export const manifest: SSRManifest; - export const env: Record; - export const remote_address: string | undefined; - export const base_path: string; - export const prerendered: Set; -} - -declare module '__sveltekit/dev-server' { - import { InternalServer } from 'types'; - - export { InternalServer as Server }; -} - -declare module '__sveltekit/manifest-data' { - // eslint-disable-next-line no-duplicate-imports - import { Asset, PageNode, RouteData } from 'types'; - - export const env: Record; - export const kit: { - appDir: string; - outDir: string; - router: { - resolution: 'client' | 'server'; - }; - paths: { - assets: string; - base: string; - relative: boolean; - }; - }; - export const mime_types: string[]; - export const manifest_data: { - routes: RouteData[]; - nodes: PageNode[]; - matchers: string[][]; - assets: Asset[]; - }; -} - -declare module '__sveltekit/server-assets' { - export const server_assets: Record; -} - -declare module '__sveltekit/remotes' { - export const remotes: Array<{ hash: string; file: string }>; -} From 8b41fbccf7bab289160515eeb82e1ff91803259e Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Mon, 6 Apr 2026 19:07:11 +0800 Subject: [PATCH 080/117] generate type --- packages/kit/types/index.d.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index c095bdfa3207..adbaa4c1f040 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -3636,4 +3636,17 @@ declare module '$app/types' { export type Asset = ReturnType; } +declare module '@sveltejs/kit/vite/environment' { + import { SSRManifest } from '@sveltejs/kit'; + + export const manifest: SSRManifest; + export const env: Record; +} + +declare module '@sveltejs/kit/vite/environment/server' { + import { InternalServer } from 'types'; + + export { InternalServer as Server }; +} + //# sourceMappingURL=index.d.ts.map \ No newline at end of file From a6385d1389c64852e03c52790d71d2a4dbe4ea4e Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Mon, 6 Apr 2026 23:45:20 +0800 Subject: [PATCH 081/117] fixes --- .../adapter-cloudflare/test/apps/workers/test/test.js | 3 ++- packages/kit/src/exports/vite/dev/server.js | 4 ++-- packages/kit/src/exports/vite/dev/ssr-manifest.js | 11 +++++++---- packages/kit/src/exports/vite/index.js | 9 ++++++++- packages/kit/src/exports/vite/module_ids.js | 1 - packages/kit/src/types/ambient.d.ts | 4 ++-- packages/kit/types/index.d.ts | 4 ++-- 7 files changed, 23 insertions(+), 13 deletions(-) diff --git a/packages/adapter-cloudflare/test/apps/workers/test/test.js b/packages/adapter-cloudflare/test/apps/workers/test/test.js index 44832c70890f..4c6f2a79a28d 100644 --- a/packages/adapter-cloudflare/test/apps/workers/test/test.js +++ b/packages/adapter-cloudflare/test/apps/workers/test/test.js @@ -7,7 +7,8 @@ test('worker', async ({ page }) => { await expect(page.locator('h1')).toContainText('Sum: 3'); }); -test('ctx', async ({ request }) => { +// TODO: re-enable once we add the vite cloudflare plugin +test.skip('ctx', async ({ request }) => { const res = await request.get('/ctx'); expect(await res.text()).toBe('ctx works'); }); diff --git a/packages/kit/src/exports/vite/dev/server.js b/packages/kit/src/exports/vite/dev/server.js index 55db3f0af17f..ebdb34920c00 100644 --- a/packages/kit/src/exports/vite/dev/server.js +++ b/packages/kit/src/exports/vite/dev/server.js @@ -1,7 +1,7 @@ import fs from 'node:fs'; import path from 'node:path'; -import { Server } from '@sveltejs/kit/vite/environment/server'; -import { env, manifest } from '@sveltejs/kit/vite/environment'; +import { Server } from 'virtual:@sveltejs/kit/vite/environment/server'; +import { env, manifest } from 'virtual:@sveltejs/kit/vite/environment'; import { createReadableStream } from '@sveltejs/kit/node'; import { from_fs } from '../filesystem.js'; diff --git a/packages/kit/src/exports/vite/dev/ssr-manifest.js b/packages/kit/src/exports/vite/dev/ssr-manifest.js index 54d907f15f97..1e5be0f3176c 100644 --- a/packages/kit/src/exports/vite/dev/ssr-manifest.js +++ b/packages/kit/src/exports/vite/dev/ssr-manifest.js @@ -4,11 +4,10 @@ import { env, kit, manifest_data, mime_types } from '__sveltekit/manifest-data'; import { to_fs } from '../filesystem.js'; import { compact } from '../../../utils/array.js'; import { join } from '../../../utils/path.js'; -import { posixify } from '../../../utils/os.js'; export { env }; -export const base_path = kit.paths.base; -export const prerendered = new Set(); + +export const basePath = kit.paths.base; export const manifest = { appDir: kit.appDir, @@ -17,7 +16,11 @@ export const manifest = { mimeTypes: mime_types, _: { client: { - start: posixify(join(import.meta.dirname, '../../../runtime/client/entry.js')), + // we can't use `runtime_directory` here because that module has imports to node:* modules + // and that doesn't work in non-Node environments + start: to_fs( + join(__SVELTEKIT_ROOT__, 'node_modules/@sveltejs/kit/src/runtime/client/entry.js') + ), app: `${to_fs(kit.outDir)}/generated/client/app.js`, imports: [], stylesheets: [], diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index c3faf404d1b4..571d9e0727c3 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -657,7 +657,10 @@ function kit({ svelte_config, adapter_in_vite_config }) { return `${runtime_directory}/client/remote-functions/index.js`; } - if (id === '__sveltekit/ssr-manifest') { + // these virtual modules which are public paths should have the virtual prefix + // otherwise the bundler complains about not being able to find them based + // on the fact that we have @sveltejs/kit/vite in our package.json exports list + if (id === 'virtual:@sveltejs/kit/vite/environment') { return path.join(import.meta.dirname, 'dev/ssr-manifest.js'); } @@ -667,6 +670,10 @@ function kit({ svelte_config, adapter_in_vite_config }) { : path.join(import.meta.dirname, 'dev/server.js'); } + if (id === 'virtual:@sveltejs/kit/vite/environment/server') { + return `\0${id}`; + } + if (id.startsWith('__sveltekit/') && id !== '__sveltekit/dev-server-entry') { return `\0virtual:${id}`; } diff --git a/packages/kit/src/exports/vite/module_ids.js b/packages/kit/src/exports/vite/module_ids.js index f3c5ce830c41..63e5ac52c695 100644 --- a/packages/kit/src/exports/vite/module_ids.js +++ b/packages/kit/src/exports/vite/module_ids.js @@ -14,7 +14,6 @@ export const sveltekit_manifest_data = '\0virtual:__sveltekit/manifest-data'; export const sveltekit_server_assets = '\0virtual:__sveltekit/server-assets'; export const sveltekit_remotes = '\0virtual:__sveltekit/remotes'; export const sveltekit_traced = '\0virtual:__sveltekit/traced'; -export const sveltekit_ssr_manifest = '\0virtual:@sveltejs/kit/vite/environment'; export const sveltekit_dev_server = '\0virtual:@sveltejs/kit/vite/environment/server'; export const app_server = posixify( diff --git a/packages/kit/src/types/ambient.d.ts b/packages/kit/src/types/ambient.d.ts index d1f440eee04c..9d29b77f66a8 100644 --- a/packages/kit/src/types/ambient.d.ts +++ b/packages/kit/src/types/ambient.d.ts @@ -147,14 +147,14 @@ declare module '$app/types' { export type Asset = ReturnType; } -declare module '@sveltejs/kit/vite/environment' { +declare module 'virtual:@sveltejs/kit/vite/environment' { import { SSRManifest } from '@sveltejs/kit'; export const manifest: SSRManifest; export const env: Record; } -declare module '@sveltejs/kit/vite/environment/server' { +declare module 'virtual:@sveltejs/kit/vite/environment/server' { import { InternalServer } from 'types'; export { InternalServer as Server }; diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index adbaa4c1f040..4a9f55a5a990 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -3636,14 +3636,14 @@ declare module '$app/types' { export type Asset = ReturnType; } -declare module '@sveltejs/kit/vite/environment' { +declare module 'virtual:@sveltejs/kit/vite/environment' { import { SSRManifest } from '@sveltejs/kit'; export const manifest: SSRManifest; export const env: Record; } -declare module '@sveltejs/kit/vite/environment/server' { +declare module 'virtual:@sveltejs/kit/vite/environment/server' { import { InternalServer } from 'types'; export { InternalServer as Server }; From 57b92919e702fd29950addf3ece566fa9d24baf3 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Tue, 7 Apr 2026 00:08:15 +0800 Subject: [PATCH 082/117] fix types --- packages/kit/src/exports/vite/dev/server.js | 12 +----------- packages/kit/src/exports/vite/index.js | 2 +- packages/kit/src/exports/vite/preview/index.js | 7 ------- packages/kit/src/runtime/server/fetch.js | 10 +--------- packages/kit/src/types/ambient.d.ts | 5 +++-- packages/kit/src/types/internal.d.ts | 2 -- packages/kit/types/index.d.ts | 5 +++-- 7 files changed, 9 insertions(+), 34 deletions(-) diff --git a/packages/kit/src/exports/vite/dev/server.js b/packages/kit/src/exports/vite/dev/server.js index ebdb34920c00..e9aa03e331bb 100644 --- a/packages/kit/src/exports/vite/dev/server.js +++ b/packages/kit/src/exports/vite/dev/server.js @@ -1,5 +1,3 @@ -import fs from 'node:fs'; -import path from 'node:path'; import { Server } from 'virtual:@sveltejs/kit/vite/environment/server'; import { env, manifest } from 'virtual:@sveltejs/kit/vite/environment'; import { createReadableStream } from '@sveltejs/kit/node'; @@ -15,21 +13,13 @@ await server.init({ /** * @param {Request} request * @param {string | undefined} remote_address - * @param {import('types').ValidatedKitConfig} kit * @returns {Promise} */ -export async function respond(request, remote_address, kit) { +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(kit.files.assets, file)); } }); } diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 571d9e0727c3..ca98e7edcb21 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -538,7 +538,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { const { respond } = await module_runner.import( '__sveltekit/dev-server-entry' ); - return await respond(request, dev_environment?.remote_address, kit); + return await respond(request, dev_environment?.remote_address); } catch (error) { // Vite doesn't log the thrown error so we have to do it ourselves console.error(error); diff --git a/packages/kit/src/exports/vite/preview/index.js b/packages/kit/src/exports/vite/preview/index.js index 76b60181c2e4..12a63252a3c2 100644 --- a/packages/kit/src/exports/vite/preview/index.js +++ b/packages/kit/src/exports/vite/preview/index.js @@ -205,13 +205,6 @@ export async function preview(vite, vite_config, svelte_config) { 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)); } }) ); diff --git a/packages/kit/src/runtime/server/fetch.js b/packages/kit/src/runtime/server/fetch.js index e05f9c1dfd69..8735ffb646e9 100644 --- a/packages/kit/src/runtime/server/fetch.js +++ b/packages/kit/src/runtime/server/fetch.js @@ -93,15 +93,7 @@ export function create_fetch({ event, options, manifest, state, get_cookie_heade if (is_asset || is_asset_html) { const file = is_asset ? filename : filename_html; - if (state.read) { - const type = is_asset - ? manifest.mimeTypes[filename.slice(filename.lastIndexOf('.'))] - : 'text/html'; - - return new Response(state.read(file), { - headers: type ? { 'content-type': type } : {} - }); - } else if (read_implementation && file in manifest._.server_assets) { + if (read_implementation && file in manifest._.server_assets) { const length = manifest._.server_assets[file]; const type = manifest.mimeTypes[file.slice(file.lastIndexOf('.'))]; diff --git a/packages/kit/src/types/ambient.d.ts b/packages/kit/src/types/ambient.d.ts index 9d29b77f66a8..54d15c327106 100644 --- a/packages/kit/src/types/ambient.d.ts +++ b/packages/kit/src/types/ambient.d.ts @@ -155,7 +155,8 @@ declare module 'virtual:@sveltejs/kit/vite/environment' { } declare module 'virtual:@sveltejs/kit/vite/environment/server' { - import { InternalServer } from 'types'; + // eslint-disable-next-line no-duplicate-imports + import { Server } from '@sveltejs/kit'; - export { InternalServer as Server }; + export { Server }; } diff --git a/packages/kit/src/types/internal.d.ts b/packages/kit/src/types/internal.d.ts index 7ec5b7e5d187..6af41a3be2ed 100644 --- a/packages/kit/src/types/internal.d.ts +++ b/packages/kit/src/types/internal.d.ts @@ -176,7 +176,6 @@ export class InternalServer extends Server { request: Request, options: RequestOptions & { prerendering?: PrerenderOptions; - read: (file: string) => NonSharedBuffer; /** A hook called before `handle` during dev, so that `AsyncLocalStorage` can be populated. */ before_handle?: ( event: RequestEvent, @@ -555,7 +554,6 @@ export interface SSRState { * prerender option is inherited by the endpoint, unless overridden. */ prerender_default?: PrerenderOption; - read?: (file: string) => NonSharedBuffer; /** * Used to set up `__SVELTEKIT_TRACK__` which checks if a used feature is supported. * E.g. if `read` from `$app/server` is used, it checks whether the route's config is compatible. diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 4a9f55a5a990..ad32ed23c412 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -3644,9 +3644,10 @@ declare module 'virtual:@sveltejs/kit/vite/environment' { } declare module 'virtual:@sveltejs/kit/vite/environment/server' { - import { InternalServer } from 'types'; + // eslint-disable-next-line no-duplicate-imports + import { Server } from '@sveltejs/kit'; - export { InternalServer as Server }; + export { Server }; } //# sourceMappingURL=index.d.ts.map \ No newline at end of file From 028f34ba66256d938d79356828103e1bf70a6e03 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Tue, 7 Apr 2026 00:13:55 +0800 Subject: [PATCH 083/117] revert removing internal read --- packages/kit/src/exports/vite/dev/server.js | 15 ++++++++++++++- packages/kit/src/exports/vite/index.js | 2 +- packages/kit/src/exports/vite/preview/index.js | 7 +++++++ packages/kit/src/runtime/server/fetch.js | 10 +++++++++- packages/kit/src/types/ambient.d.ts | 5 +---- packages/kit/src/types/internal.d.ts | 8 ++++++++ 6 files changed, 40 insertions(+), 7 deletions(-) diff --git a/packages/kit/src/exports/vite/dev/server.js b/packages/kit/src/exports/vite/dev/server.js index e9aa03e331bb..67b084677d86 100644 --- a/packages/kit/src/exports/vite/dev/server.js +++ b/packages/kit/src/exports/vite/dev/server.js @@ -1,8 +1,13 @@ +/** @import { InternalServer, ValidatedKitConfig } from 'types' */ + +import fs from 'node:fs'; +import path from 'node:path'; import { Server } from 'virtual:@sveltejs/kit/vite/environment/server'; import { env, manifest } from 'virtual:@sveltejs/kit/vite/environment'; import { createReadableStream } from '@sveltejs/kit/node'; import { from_fs } from '../filesystem.js'; +/** @type {InternalServer} */ const server = new Server(manifest); await server.init({ @@ -13,13 +18,21 @@ await server.init({ /** * @param {Request} request * @param {string | undefined} remote_address + * @param {ValidatedKitConfig} kit * @returns {Promise} */ -export async function respond(request, remote_address) { +export async function respond(request, remote_address, kit) { 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(kit.files.assets, file)); } }); } diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index ca98e7edcb21..571d9e0727c3 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -538,7 +538,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { const { respond } = await module_runner.import( '__sveltekit/dev-server-entry' ); - return await respond(request, dev_environment?.remote_address); + return await respond(request, dev_environment?.remote_address, kit); } catch (error) { // Vite doesn't log the thrown error so we have to do it ourselves console.error(error); diff --git a/packages/kit/src/exports/vite/preview/index.js b/packages/kit/src/exports/vite/preview/index.js index 12a63252a3c2..76b60181c2e4 100644 --- a/packages/kit/src/exports/vite/preview/index.js +++ b/packages/kit/src/exports/vite/preview/index.js @@ -205,6 +205,13 @@ export async function preview(vite, vite_config, svelte_config) { 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)); } }) ); diff --git a/packages/kit/src/runtime/server/fetch.js b/packages/kit/src/runtime/server/fetch.js index 8735ffb646e9..e05f9c1dfd69 100644 --- a/packages/kit/src/runtime/server/fetch.js +++ b/packages/kit/src/runtime/server/fetch.js @@ -93,7 +93,15 @@ export function create_fetch({ event, options, manifest, state, get_cookie_heade if (is_asset || is_asset_html) { const file = is_asset ? filename : filename_html; - if (read_implementation && file in manifest._.server_assets) { + if (state.read) { + const type = is_asset + ? manifest.mimeTypes[filename.slice(filename.lastIndexOf('.'))] + : 'text/html'; + + return new Response(state.read(file), { + headers: type ? { 'content-type': type } : {} + }); + } else if (read_implementation && file in manifest._.server_assets) { const length = manifest._.server_assets[file]; const type = manifest.mimeTypes[file.slice(file.lastIndexOf('.'))]; diff --git a/packages/kit/src/types/ambient.d.ts b/packages/kit/src/types/ambient.d.ts index 54d15c327106..62344d30b1a9 100644 --- a/packages/kit/src/types/ambient.d.ts +++ b/packages/kit/src/types/ambient.d.ts @@ -155,8 +155,5 @@ declare module 'virtual:@sveltejs/kit/vite/environment' { } declare module 'virtual:@sveltejs/kit/vite/environment/server' { - // eslint-disable-next-line no-duplicate-imports - import { Server } from '@sveltejs/kit'; - - export { Server }; + export { Server } from '@sveltejs/kit'; } diff --git a/packages/kit/src/types/internal.d.ts b/packages/kit/src/types/internal.d.ts index 6af41a3be2ed..87b5b3fe5f08 100644 --- a/packages/kit/src/types/internal.d.ts +++ b/packages/kit/src/types/internal.d.ts @@ -176,6 +176,10 @@ export class InternalServer extends Server { request: Request, options: RequestOptions & { prerendering?: PrerenderOptions; + /** + * Used internally for saving dependencies during prerendering and generating fallback pages. + */ + read: (file: string) => NonSharedBuffer; /** A hook called before `handle` during dev, so that `AsyncLocalStorage` can be populated. */ before_handle?: ( event: RequestEvent, @@ -554,6 +558,10 @@ export interface SSRState { * prerender option is inherited by the endpoint, unless overridden. */ prerender_default?: PrerenderOption; + /** + * Used internally for saving dependencies during prerendering and generating fallback pages. + */ + read?: (file: string) => NonSharedBuffer; /** * Used to set up `__SVELTEKIT_TRACK__` which checks if a used feature is supported. * E.g. if `read` from `$app/server` is used, it checks whether the route's config is compatible. From a9a509ae16f8873f1b4e260022960658e552af0f Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Tue, 7 Apr 2026 00:14:53 +0800 Subject: [PATCH 084/117] generate types --- packages/kit/types/index.d.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index ad32ed23c412..8fae925d3e91 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -3644,10 +3644,7 @@ declare module 'virtual:@sveltejs/kit/vite/environment' { } declare module 'virtual:@sveltejs/kit/vite/environment/server' { - // eslint-disable-next-line no-duplicate-imports - import { Server } from '@sveltejs/kit'; - - export { Server }; + export { Server } from '@sveltejs/kit'; } //# sourceMappingURL=index.d.ts.map \ No newline at end of file From 6ce7a8fbcc4d37695d5c849088e2374ca5495dc9 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Tue, 7 Apr 2026 00:18:26 +0800 Subject: [PATCH 085/117] fix docs? --- .../99-writing-adapters.md | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/documentation/docs/25-build-and-deploy/99-writing-adapters.md b/documentation/docs/25-build-and-deploy/99-writing-adapters.md index 4283cc9380b2..33db32f48978 100644 --- a/documentation/docs/25-build-and-deploy/99-writing-adapters.md +++ b/documentation/docs/25-build-and-deploy/99-writing-adapters.md @@ -71,29 +71,28 @@ config(userConfig) { } ``` -You can also create your own server entry file by importing `Server` from `@sveltejs/kit/vite/environment/server` and `env` and `manifest` from `@sveltejs/kit/vite/environment`. +You can also create your own server entry file by importing `Server` from `virtual:@sveltejs/kit/vite/environment/server` and `env` and `manifest` from `virtual:@sveltejs/kit/vite/environment`. ```js -import { Server } from '@sveltejs/kit/vite/environment/server'; -import { env, manifest } from '@sveltejs/kit/vite/environment'; +import { Server } from 'virtual:@sveltejs/kit/vite/environment/server'; +import { env, manifest } from 'virtual:@sveltejs/kit/vite/environment'; const server = new Server(manifest); await server.init({ env }); -/** - * @param {Request} request - * @returns {Promise} - */ -export async function respond(request) { - return await server.respond(request, { - getClientAddress: () => { - throw new Error('Could not determine clientAddress'); - }, - read: (file) => { - throw new Error('read is not supported in this environment'); - } - }); +export default { + /** + * @param {Request} request + * @returns {Promise} + */ + async fetch(request) { + return await server.respond(request, { + getClientAddress: () => { + return request.headers.get('your-platform-exposes-the-remote-address') + } + }); + } } import.meta.hot?.accept(); From 4b22acaee5ad662a8161c3188d033c6ccb07bf9f Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Tue, 7 Apr 2026 00:31:47 +0800 Subject: [PATCH 086/117] add types --- 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 33db32f48978..bfba617b8d82 100644 --- a/documentation/docs/25-build-and-deploy/99-writing-adapters.md +++ b/documentation/docs/25-build-and-deploy/99-writing-adapters.md @@ -66,6 +66,7 @@ By default, SvelteKit runs your server code through a Node.js runtime during dev The default Vite environment SvelteKit uses is named `ssr`. You can customise it by referencing it in the `config` hook of your Vite plugin. ```js +// @errors: 2304 1005 1109 config(userConfig) { userConfig.environments.ssr = { ... } } From c33711944f850bd1cdb91eda6462e00fb1fa5527 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Fri, 24 Apr 2026 02:41:16 +0800 Subject: [PATCH 087/117] port changes from cloudflare integration branch --- packages/kit/src/core/adapt/builder.js | 11 +- packages/kit/src/core/adapt/index.js | 7 +- .../kit/src/core/generate_manifest/index.js | 1 + packages/kit/src/core/postbuild/ambient.d.ts | 3 + packages/kit/src/core/postbuild/analyse.js | 324 +------ .../kit/src/core/postbuild/analyse_entry.js | 313 +++++++ packages/kit/src/core/postbuild/fallback.js | 79 +- packages/kit/src/core/postbuild/prerender.js | 322 +++---- .../kit/src/core/postbuild/prerender_entry.js | 130 +++ .../core/sync/create_manifest_data/index.js | 3 +- packages/kit/src/core/sync/write_server.js | 2 +- packages/kit/src/core/utils.js | 13 +- packages/kit/src/exports/public.d.ts | 27 +- .../src/exports/vite/build/build_server.js | 76 +- .../kit/src/exports/vite/build/vite_server.js | 400 +++++++++ packages/kit/src/exports/vite/dev/index.js | 124 ++- packages/kit/src/exports/vite/dev/server.js | 69 +- .../kit/src/exports/vite/dev/ssr_entry.js | 39 + .../kit/src/exports/vite/dev/ssr_manifest.js | 203 +++++ packages/kit/src/exports/vite/index.js | 814 ++++++++++-------- packages/kit/src/exports/vite/module_ids.js | 3 +- packages/kit/src/exports/vite/types.d.ts | 34 + packages/kit/src/exports/vite/utils.js | 9 +- packages/kit/src/runtime/server/ambient.d.ts | 6 + packages/kit/src/runtime/server/fetch.js | 2 +- packages/kit/src/runtime/server/index.js | 38 +- packages/kit/src/runtime/server/read.js | 39 + packages/kit/src/runtime/utils.js | 13 + packages/kit/src/types/ambient-private.d.ts | 32 +- packages/kit/src/types/ambient.d.ts | 54 +- packages/kit/src/types/global-private.d.ts | 1 + packages/kit/src/types/internal.d.ts | 18 +- packages/kit/src/utils/escape.js | 8 + packages/kit/src/utils/features.js | 30 +- packages/kit/types/index.d.ts | 87 +- 35 files changed, 2265 insertions(+), 1069 deletions(-) create mode 100644 packages/kit/src/core/postbuild/ambient.d.ts create mode 100644 packages/kit/src/core/postbuild/analyse_entry.js create mode 100644 packages/kit/src/core/postbuild/prerender_entry.js create mode 100644 packages/kit/src/exports/vite/build/vite_server.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/runtime/server/read.js diff --git a/packages/kit/src/core/adapt/builder.js b/packages/kit/src/core/adapt/builder.js index 781f12f9ca53..95d88b5895f7 100644 --- a/packages/kit/src/core/adapt/builder.js +++ b/packages/kit/src/core/adapt/builder.js @@ -32,7 +32,8 @@ const extensions = ['.html', '.js', '.mjs', '.json', '.css', '.svg', '.xml', '.w * prerender_map: PrerenderMap; * log: Logger; * vite_config: ResolvedConfig; - * remotes: RemoteChunk[] + * remotes: RemoteChunk[]; + * out: string; * }} opts * @returns {Builder} */ @@ -45,7 +46,8 @@ export function create_builder({ prerender_map, log, vite_config, - remotes + remotes, + out }) { /** @type {Map} */ const lookup = new Map(); @@ -114,12 +116,11 @@ export function create_builder({ async generateFallback(dest) { const manifest_path = `${config.kit.outDir}/output/server/manifest-full.js`; - const env = get_env(config.kit.env, vite_config.mode); const fallback = await generate_fallback({ + svelte_config: config, manifest_path, - env: { ...env.private, ...env.public }, - root: vite_config.root + out }); if (existsSync(dest)) { diff --git a/packages/kit/src/core/adapt/index.js b/packages/kit/src/core/adapt/index.js index d50a57e36060..433536a818d2 100644 --- a/packages/kit/src/core/adapt/index.js +++ b/packages/kit/src/core/adapt/index.js @@ -10,6 +10,7 @@ import { create_builder } from './builder.js'; * @param {import('types').Logger} log * @param {import('types').RemoteChunk[]} remotes * @param {import('vite').ResolvedConfig} vite_config + * @param {string} out */ export async function adapt( config, @@ -19,7 +20,8 @@ export async function adapt( prerender_map, log, remotes, - vite_config + vite_config, + out ) { // This is only called when adapter is truthy, so the cast is safe const { name, adapt } = /** @type {import('@sveltejs/kit').Adapter} */ (config.kit.adapter); @@ -35,7 +37,8 @@ export async function adapt( prerender_map, log, remotes, - vite_config + vite_config, + out }); await adapt(builder); diff --git a/packages/kit/src/core/generate_manifest/index.js b/packages/kit/src/core/generate_manifest/index.js index 4bbc78509bdf..b3b4d47e7f5a 100644 --- a/packages/kit/src/core/generate_manifest/index.js +++ b/packages/kit/src/core/generate_manifest/index.js @@ -104,6 +104,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/core/postbuild/ambient.d.ts b/packages/kit/src/core/postbuild/ambient.d.ts new file mode 100644 index 000000000000..d8dcbaeea1f0 --- /dev/null +++ b/packages/kit/src/core/postbuild/ambient.d.ts @@ -0,0 +1,3 @@ +declare module '__SERVER__/index.js' { + export { Server } from '@sveltejs/kit'; +} diff --git a/packages/kit/src/core/postbuild/analyse.js b/packages/kit/src/core/postbuild/analyse.js index 98ddcca5276d..7beb2720448d 100644 --- a/packages/kit/src/core/postbuild/analyse.js +++ b/packages/kit/src/core/postbuild/analyse.js @@ -1,294 +1,60 @@ -/** @import { RemoteChunk } from 'types' */ -import { join } from 'node:path'; -import { pathToFileURL } from 'node:url'; -import { validate_server_exports } from '../../utils/exports.js'; -import { load_config } from '../config/index.js'; -import { forked } from '../../utils/fork.js'; -import { ENDPOINT_METHODS } from '../../constants.js'; -import { filter_env } from '../../utils/env.js'; -import { has_server_load, resolve_route } from '../../utils/routing.js'; -import { check_feature } from '../../utils/features.js'; -import { createReadableStream } from '@sveltejs/kit/node'; -import { PageNodes } from '../../utils/page_nodes.js'; +/** @import { ManifestData, ServerMetadata, ValidatedConfig } from 'types' */ +/** @import { Manifest } from 'vite' */ +import * as devalue from 'devalue'; import { build_server_nodes } from '../../exports/vite/build/build_server.js'; +import { create_build_server } from '../../exports/vite/build/vite_server.js'; -export default forked(import.meta.url, analyse); +const analyse_entry = import.meta.resolve('./analyse_entry.js'); /** - * @param {{ - * hash: boolean; - * manifest_path: string; - * manifest_data: import('types').ManifestData; - * server_manifest: import('vite').Manifest; - * tracked_features: Record; - * env: Record; - * out: string; - * output_config: import('types').RecursiveRequired; - * remotes: RemoteChunk[]; - * root: string; - * }} opts + * @param {object} opts Arguments must be serialisable via the structured clone algorithm + * @param {ValidatedConfig} opts.svelte_config + * @param {string} opts.manifest_path + * @param {ManifestData} opts.manifest_data + * @param {Manifest} opts.server_manifest + * @param {Record} opts.tracked_features + * @param {string} opts.out + * @param {string} opts.root + * @returns {Promise<{ metadata: ServerMetadata }>} */ -async function analyse({ - hash, +export default async function analyse({ + svelte_config, manifest_path, manifest_data, server_manifest, tracked_features, - env, out, - output_config, - remotes, root }) { - /** @type {import('@sveltejs/kit').SSRManifest} */ - const manifest = (await import(pathToFileURL(manifest_path).href)).manifest; - - /** @type {import('types').ValidatedKitConfig} */ - const config = (await load_config({ cwd: root })).kit; - - const server_root = join(config.outDir, 'output'); - - /** @type {import('types').ServerInternalModule} */ - const internal = await import(pathToFileURL(`${server_root}/server/internal.js`).href); - - // configure `import { building } from '$app/environment'` — - // essential we do this before analysing the code - internal.set_building(); - - // set env, `read`, and `manifest`, in case they're used in initialisation - const { publicPrefix: public_prefix, privatePrefix: private_prefix } = config.env; - const private_env = filter_env(env, private_prefix, public_prefix); - const public_env = filter_env(env, public_prefix, private_prefix); - internal.set_private_env(private_env); - internal.set_public_env(public_env); - internal.set_manifest(manifest); - internal.set_read_implementation((file) => createReadableStream(`${server_root}/server/${file}`)); - // first, build server nodes without the client manifest so we can analyse it - build_server_nodes( - out, - config, - manifest_data, - server_manifest, - null, - null, - null, - output_config, - root - ); - - /** @type {import('types').ServerMetadata} */ - const metadata = { - nodes: [], - routes: new Map(), - remotes: new Map() - }; - - const nodes = await Promise.all(manifest._.nodes.map((loader) => loader())); - - // analyse nodes - for (const node of nodes) { - if (hash && node.universal) { - const options = Object.keys(node.universal).filter((o) => o !== 'load'); - if (options.length > 0) { - throw new Error( - `Page options are ignored when \`router.type === 'hash'\` (${node.universal_id} has ${options - .filter((o) => o !== 'load') - .map((o) => `'${o}'`) - .join(', ')})` - ); - } - } - - metadata.nodes[node.index] = { - has_server_load: has_server_load(node), - has_universal_load: node.universal?.load !== undefined - }; - } - - // analyse routes - for (const route of manifest._.routes) { - const page = - route.page && - analyse_page( - route.page.layouts.map((n) => (n === undefined ? n : nodes[n])), - nodes[route.page.leaf] - ); - - const endpoint = route.endpoint && analyse_endpoint(route, await route.endpoint()); - - if (page?.prerender && endpoint?.prerender) { - throw new Error(`Cannot prerender a route with both +page and +server files (${route.id})`); - } + build_server_nodes({ out, manifest_data, server_manifest, root }); - if (page?.config && endpoint?.config) { - for (const key in { ...page.config, ...endpoint.config }) { - if (JSON.stringify(page.config[key]) !== JSON.stringify(endpoint.config[key])) { - throw new Error( - `Mismatched route config for ${route.id} — the +page and +server files must export the same config, if any` - ); - } - } - } - - const route_config = page?.config ?? endpoint?.config ?? {}; - const prerender = page?.prerender ?? endpoint?.prerender; - - if (prerender !== true) { - for (const feature of list_features( - route, - manifest_data, - server_manifest, - tracked_features - )) { - check_feature(route.id, route_config, feature, config.adapter); - } - } - - const page_methods = page?.methods ?? []; - const api_methods = endpoint?.methods ?? []; - const entries = page?.entries ?? endpoint?.entries; - - metadata.routes.set(route.id, { - config: route_config, - methods: Array.from(new Set([...page_methods, ...api_methods])), - page: { - methods: page_methods - }, - api: { - methods: api_methods - }, - prerender, - entries: - entries && (await entries()).map((entry_object) => resolve_route(route.id, entry_object)) - }); - } - - // analyse remotes - for (const remote of remotes) { - const loader = manifest._.remotes[remote.hash]; - const { default: functions } = await loader(); - - const exports = new Map(); - - for (const name in functions) { - const internals = /** @type {import('types').RemoteInternals} */ (functions[name].__); - const type = internals.type; - - exports.set(name, { - type, - dynamic: type !== 'prerender' || internals.dynamic - }); - } - - metadata.remotes.set(remote.hash, exports); - } - - return { metadata }; -} - -/** - * @param {import('types').SSRRoute} route - * @param {import('types').SSREndpoint} mod - */ -function analyse_endpoint(route, mod) { - validate_server_exports(mod, route.id); - - if (mod.prerender && (mod.POST || mod.PATCH || mod.PUT || mod.DELETE)) { - throw new Error( - `Cannot prerender a +server file with POST, PATCH, PUT, or DELETE (${route.id})` - ); - } - - /** @type {Array} */ - const methods = []; - - for (const method of /** @type {import('types').HttpMethod[]} */ (ENDPOINT_METHODS)) { - if (mod[method]) methods.push(method); - } - - if (mod.fallback) { - methods.push('*'); - } - - return { - config: mod.config, - entries: mod.entries, - methods, - prerender: mod.prerender ?? false - }; -} - -/** - * @param {Array} layouts - * @param {import('types').SSRNode} leaf - */ -function analyse_page(layouts, leaf) { - /** @type {Array<'GET' | 'POST'>} */ - const methods = ['GET']; - if (leaf.server?.actions) methods.push('POST'); - - const nodes = new PageNodes([...layouts, leaf]); - nodes.validate(); - - return { - config: nodes.get_config(), - entries: leaf.universal?.entries ?? leaf.server?.entries, - methods, - prerender: nodes.prerender() - }; -} - -/** - * @param {import('types').SSRRoute} route - * @param {import('types').ManifestData} manifest_data - * @param {import('vite').Manifest} server_manifest - * @param {Record} tracked_features - */ -function list_features(route, manifest_data, server_manifest, tracked_features) { - const features = new Set(); - - const route_data = /** @type {import('types').RouteData} */ ( - manifest_data.routes.find((r) => r.id === route.id) - ); - - const visited = new Set(); - /** @param {string} id */ - function visit(id) { - if (visited.has(id)) return; - visited.add(id); - - const chunk = server_manifest[id]; - if (!chunk) return; - - if (chunk.file in tracked_features) { - for (const feature of tracked_features[chunk.file]) { - features.add(feature); - } - } - - if (chunk.imports) { - for (const id of chunk.imports) { - visit(id); - } - } - } - - let page_node = route_data?.leaf; - while (page_node) { - if (page_node.server) visit(page_node.server); - page_node = page_node.parent ?? null; - } - - if (route_data.endpoint) { - visit(route_data.endpoint.file); - } - - if (manifest_data.hooks.server) { - // TODO if hooks.server.js imports `read`, it will be in the entry chunk - // we don't currently account for that case - visit(manifest_data.hooks.server); - } - - return Array.from(features); + const vite = await create_build_server({ + svelte_config, + out, + manifest_path, + server_path: analyse_entry + }); + + await vite.listen(); + + const address = vite.httpServer?.address(); + const port = typeof address === 'string' ? Number(address.split(':').at(-1)) : address?.port; + + const response = await fetch(new URL(`http://localhost:${port}`), { + method: 'POST', + headers: { + 'content-type': 'application/json' + }, + body: devalue.stringify({ + server_manifest, + tracked_features, + manifest_data, + hash: svelte_config.kit.router.type === 'hash' + }) + }); + + await vite.close(); + + return { metadata: devalue.parse(await response.text()) }; } diff --git a/packages/kit/src/core/postbuild/analyse_entry.js b/packages/kit/src/core/postbuild/analyse_entry.js new file mode 100644 index 000000000000..c1738d8752f4 --- /dev/null +++ b/packages/kit/src/core/postbuild/analyse_entry.js @@ -0,0 +1,313 @@ +/** @import { Server as KitServer, SSRManifest } from '@sveltejs/kit' */ +/** @import { ManifestData, ServerMetadata } from 'types' */ +/** @import { Manifest } from 'vite' */ +import { get } from '__sveltekit/ipc'; +import { + options, + set_manifest, + set_read_implementation, + set_private_env, + set_public_env, + set_building +} from '__SERVER__/internal.js'; +import * as devalue from 'devalue'; +import { ENDPOINT_METHODS } from '../../constants.js'; +import { has_server_load, resolve_route } from '../../utils/routing.js'; +import { validate_server_exports } from '../../utils/exports.js'; +import { PageNodes } from '../../utils/page_nodes.js'; +import { check_feature } from '../../utils/features.js'; +import { filter_env } from '../../utils/env.js'; +import { create_synchronous_read } from '../../runtime/server/read.js'; + +set_building(); + +/** @implements {KitServer} */ +export class Server { + /** @type {import('@sveltejs/kit').SSRManifest} */ + manifest; + + /** @type {import('types').SSROptions} */ + options; + + // set env, `read`, and `manifest`, in case they're used when we analyse the user's code + + /** @param {SSRManifest} manifest */ + constructor(manifest) { + this.manifest = manifest; + /** @type {import('types').SSROptions} */ + this.options = options; + + set_manifest(manifest); + } + + /** @type {KitServer['init']} */ + init({ env }) { + const { env_public_prefix, env_private_prefix } = this.options; + + set_private_env(filter_env(env, env_private_prefix, env_public_prefix)); + set_public_env(filter_env(env, env_public_prefix, env_private_prefix)); + + set_read_implementation( + create_synchronous_read(async (file) => { + const response = await get(`/read?${new URLSearchParams({ file })}`); + if (!response.ok) { + throw new Error( + `read(...) failed: could not fetch ${file} (${response.status} ${response.statusText})` + ); + } + return response.body; + }) + ); + + return Promise.resolve(); + } + + /** @type {KitServer['respond']} */ + async respond(request) { + /** @type {{ hash: boolean; server_manifest: Manifest; tracked_features: Record; manifest_data: ManifestData; }} */ + const { server_manifest, tracked_features, manifest_data, hash } = devalue.parse( + await request.text() + ); + + const metadata = await analyse({ + server_manifest, + tracked_features, + manifest: this.manifest, + manifest_data, + hash + }); + + return new Response(devalue.stringify(metadata)); + } +} + +/** + * + * @param {object} opts + * @param {Manifest} opts.server_manifest + * @param {Record} opts.tracked_features + * @param {SSRManifest} opts.manifest + * @param {ManifestData} opts.manifest_data + * @param {boolean} opts.hash + * @returns {Promise} + */ +async function analyse({ server_manifest, tracked_features, manifest, manifest_data, hash }) { + /** @type {import('types').ServerMetadata} */ + const metadata = { + nodes: [], + routes: new Map(), + remotes: new Map(), + remotes_with_prerender: new Set() + }; + + const nodes = await Promise.all(manifest._.nodes.map((loader) => loader())); + + // analyse nodes + for (const node of nodes) { + if (hash && node.universal) { + const options = Object.keys(node.universal).filter((o) => o !== 'load'); + if (options.length > 0) { + throw new Error( + `Page options are ignored when \`router.type === 'hash'\` (${node.universal_id} has ${options + .filter((o) => o !== 'load') + .map((o) => `'${o}'`) + .join(', ')})` + ); + } + } + + metadata.nodes[node.index] = { + has_server_load: has_server_load(node), + has_universal_load: node.universal?.load !== undefined + }; + } + + // analyse routes + for (const route of manifest._.routes) { + const page = + route.page && + analyse_page( + route.page.layouts.map((n) => (n === undefined ? n : nodes[n])), + nodes[route.page.leaf] + ); + + const endpoint = route.endpoint && analyse_endpoint(route, await route.endpoint()); + + if (page?.prerender && endpoint?.prerender) { + throw new Error(`Cannot prerender a route with both +page and +server files (${route.id})`); + } + + if (page?.config && endpoint?.config) { + for (const key in { ...page.config, ...endpoint.config }) { + if (JSON.stringify(page.config[key]) !== JSON.stringify(endpoint.config[key])) { + throw new Error( + `Mismatched route config for ${route.id} — the +page and +server files must export the same config, if any` + ); + } + } + } + + const route_config = page?.config ?? endpoint?.config ?? {}; + const prerender = page?.prerender ?? endpoint?.prerender; + + if (prerender !== true) { + for (const feature of list_features( + route, + manifest_data, + server_manifest, + tracked_features + )) { + await check_feature(route.id, route_config, feature); + } + } + + const page_methods = page?.methods ?? []; + const api_methods = endpoint?.methods ?? []; + const entries = page?.entries ?? endpoint?.entries; + + metadata.routes.set(route.id, { + config: route_config, + methods: Array.from(new Set([...page_methods, ...api_methods])), + page: { + methods: page_methods + }, + api: { + methods: api_methods + }, + prerender, + entries: + entries && (await entries()).map((entry_object) => resolve_route(route.id, entry_object)) + }); + } + + // analyse remotes + for (const [hash, loader] of Object.entries(manifest._.remotes)) { + const { default: functions } = await loader(); + + const exports = new Map(); + + for (const name in functions) { + const internals = /** @type {import('types').RemoteInternals} */ (functions[name].__); + const type = internals.type; + + exports.set(name, { + type, + dynamic: type !== 'prerender' || internals.dynamic + }); + + if (type === 'prerender') { + metadata.remotes_with_prerender.add(hash); + } + } + + metadata.remotes.set(hash, exports); + } + + return metadata; +} + +/** + * @param {import('types').SSRRoute} route + * @param {import('types').SSREndpoint} mod + */ +function analyse_endpoint(route, mod) { + validate_server_exports(mod, route.id); + + if (mod.prerender && (mod.POST || mod.PATCH || mod.PUT || mod.DELETE)) { + throw new Error( + `Cannot prerender a +server file with POST, PATCH, PUT, or DELETE (${route.id})` + ); + } + + /** @type {Array} */ + const methods = []; + + for (const method of /** @type {import('types').HttpMethod[]} */ (ENDPOINT_METHODS)) { + if (mod[method]) methods.push(method); + } + + if (mod.fallback) { + methods.push('*'); + } + + return { + config: mod.config, + entries: mod.entries, + methods, + prerender: mod.prerender ?? false + }; +} + +/** + * @param {Array} layouts + * @param {import('types').SSRNode} leaf + */ +function analyse_page(layouts, leaf) { + /** @type {Array<'GET' | 'POST'>} */ + const methods = ['GET']; + if (leaf.server?.actions) methods.push('POST'); + + const nodes = new PageNodes([...layouts, leaf]); + nodes.validate(); + + return { + config: nodes.get_config(), + entries: leaf.universal?.entries ?? leaf.server?.entries, + methods, + prerender: nodes.prerender() + }; +} + +/** + * @param {import('types').SSRRoute} route + * @param {import('types').ManifestData} manifest_data + * @param {import('vite').Manifest} server_manifest + * @param {Record} tracked_features + */ +function list_features(route, manifest_data, server_manifest, tracked_features) { + const features = new Set(); + + const route_data = /** @type {import('types').RouteData} */ ( + manifest_data.routes.find((r) => r.id === route.id) + ); + + const visited = new Set(); + /** @param {string} id */ + function visit(id) { + if (visited.has(id)) return; + visited.add(id); + + const chunk = server_manifest[id]; + if (!chunk) return; + + if (chunk.file in tracked_features) { + for (const feature of tracked_features[chunk.file]) { + features.add(feature); + } + } + + if (chunk.imports) { + for (const id of chunk.imports) { + visit(id); + } + } + } + + let page_node = route_data?.leaf; + while (page_node) { + if (page_node.server) visit(page_node.server); + page_node = page_node.parent ?? null; + } + + if (route_data.endpoint) { + visit(route_data.endpoint.file); + } + + if (manifest_data.hooks.server) { + // TODO if hooks.server.js imports `read`, it will be in the entry chunk + // we don't currently account for that case + visit(manifest_data.hooks.server); + } + + return Array.from(features); +} diff --git a/packages/kit/src/core/postbuild/fallback.js b/packages/kit/src/core/postbuild/fallback.js index 66fa0c6379e7..3727693d4f26 100644 --- a/packages/kit/src/core/postbuild/fallback.js +++ b/packages/kit/src/core/postbuild/fallback.js @@ -1,49 +1,50 @@ -import { readFileSync } from 'node:fs'; -import { join } from 'node:path'; -import { pathToFileURL } from 'node:url'; -import { load_config } from '../config/index.js'; -import { forked } from '../../utils/fork.js'; +/** @import { ValidatedConfig } from 'types' */ +/** @import { PluginOption } from 'vite' */ +import { escape_for_regexp } from '../../utils/escape.js'; +import { create_build_server } from '../../exports/vite/build/vite_server.js'; -export default forked(import.meta.url, generate_fallback); +const prerender_entry = import.meta.resolve('./prerender_entry.js'); /** - * @param {{ - * manifest_path: string; - * env: Record; - * root: string; - * }} opts + * @param {object} opts Arguments must be serialisable via the structured clone algorithm + * @param {ValidatedConfig} opts.svelte_config + * @param {string} opts.manifest_path + * @param {string} opts.out */ -async function generate_fallback({ manifest_path, env, root }) { - /** @type {import('types').ValidatedKitConfig} */ - const config = (await load_config({ cwd: root })).kit; - - const server_root = join(config.outDir, '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); - - /** @type {import('@sveltejs/kit').SSRManifest} */ - const manifest = (await import(pathToFileURL(manifest_path).href)).manifest; +export default async function generate_fallback({ svelte_config, manifest_path, out }) { + /** @type {PluginOption} */ + const plugin_generate_fallback = { + name: 'vite-plugin-sveltekit-compile:generate-fallback', + configureServer(vite) { + return () => { + vite.middlewares.use((req, _, next) => { + req.url = req.url?.replace( + new RegExp(escape_for_regexp(`^http://localhost:${port}`)), + svelte_config.kit.prerender.origin + ); + req.headers.host = new URL(svelte_config.kit.prerender.origin).host; + + next(); + }); + }; + } + }; + + const vite = await create_build_server({ + svelte_config, + out, + manifest_path, + server_path: prerender_entry, + vite_plugins: [plugin_generate_fallback] + }); - set_building(); + await vite.listen(); - const server = new Server(manifest); - await server.init({ env }); + const address = vite.httpServer?.address(); + const port = typeof address === 'string' ? Number(address.split(':').at(-1)) : address?.port; + const response = await fetch(`http://localhost:${port}/[fallback]`); - const response = await server.respond(new Request(config.prerender.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(config.files.assets, file)) - }); + await vite.close(); 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 d1504f74b4b2..874fcfeeb071 100644 --- a/packages/kit/src/core/postbuild/prerender.js +++ b/packages/kit/src/core/postbuild/prerender.js @@ -1,24 +1,21 @@ +/** @import { Logger, PrerenderDependency, Prerendered, PrerenderMap, ServerMetadata, ValidatedConfig } from 'types' */ +/** @import { PluginOption } from 'vite' */ +/** @import { SerialisedResponse } from '../../exports/vite/types.js' */ import { existsSync, readFileSync, statSync, writeFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; -import { pathToFileURL } from 'node:url'; +import * as devalue from 'devalue'; import { mkdirp, walk } from '../../utils/filesystem.js'; import { noop } from '../../utils/functions.js'; import { decode_uri, is_root_relative, resolve } from '../../utils/url.js'; -import { escape_html } from '../../utils/escape.js'; +import { escape_for_regexp, escape_html } from '../../utils/escape.js'; import { logger } from '../utils.js'; -import { load_config } from '../config/index.js'; import { get_route_segments } from '../../utils/routing.js'; import { queue } from './queue.js'; import { crawl } from './crawl.js'; -import { forked } from '../../utils/fork.js'; -import * as devalue from 'devalue'; -import { createReadableStream } from '@sveltejs/kit/node'; import generate_fallback from './fallback.js'; -import { stringify_remote_arg } from '../../runtime/shared.js'; -import { filter_env } from '../../utils/env.js'; import { posixify } from '../../utils/os.js'; - -export default forked(import.meta.url, prerender); +import { create_app_dir_matcher } from '../../exports/vite/dev/index.js'; +import { create_build_server } from '../../exports/vite/build/vite_server.js'; // https://html.spec.whatwg.org/multipage/browsing-the-web.html#scrolling-to-a-fragment // "If fragment is the empty string, then return the special value top of the document." @@ -26,36 +23,22 @@ export default forked(import.meta.url, prerender); // "If decodedFragment is an ASCII case-insensitive match for the string 'top', then return the top of the document." const SPECIAL_HASHLINKS = new Set(['', 'top']); +const prerender_entry = import.meta.resolve('./prerender_entry.js'); + /** - * @param {{ - * hash: boolean; - * out: string; - * manifest_path: string; - * metadata: import('types').ServerMetadata; - * verbose: boolean; - * env: Record; - * root: string; - * }} opts + * @param {object} opts Arguments must be serialisable via the structured clone algorithm + * @param {ValidatedConfig} opts.svelte_config + * @param {string} opts.out + * @param {string} opts.manifest_path + * @param {ServerMetadata} opts.metadata + * @param {boolean} opts.verbose + * @param {string} opts.root */ -async function prerender({ hash, out, manifest_path, metadata, verbose, env, root }) { - /** @type {import('@sveltejs/kit').SSRManifest} */ - const manifest = (await import(pathToFileURL(manifest_path).href)).manifest; - - /** @type {import('types').ServerInternalModule} */ - const internal = await import(pathToFileURL(`${out}/server/internal.js`).href); - - /** @type {import('types').ServerModule} */ - const { Server } = await import(pathToFileURL(`${out}/server/index.js`).href); - - // configure `import { building } from '$app/environment'` — - // essential we do this before analysing the code - internal.set_building(); - internal.set_prerendering(); - +export default async function prerender({ svelte_config, out, manifest_path, metadata, verbose }) { /** * @template {{message: string}} T * @template {Omit} K - * @param {import('types').Logger} log + * @param {Logger} log * @param {'fail' | 'warn' | 'ignore' | ((details: T) => void)} input * @param {(details: K) => string} format * @returns {(details: K) => void} @@ -81,7 +64,7 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, roo const OK = 2; const REDIRECT = 3; - /** @type {import('types').Prerendered} */ + /** @type {Prerendered} */ const prerendered = { pages: new Map(), assets: new Map(), @@ -89,7 +72,7 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, roo paths: [] }; - /** @type {import('types').PrerenderMap} */ + /** @type {PrerenderMap} */ const prerender_map = new Map(); for (const [id, { prerender }] of metadata.routes) { @@ -98,21 +81,15 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, roo } } - /** @type {Set} */ - const prerendered_routes = new Set(); - - /** @type {import('types').ValidatedKitConfig} */ - const config = (await load_config({ cwd: root })).kit; - - if (hash) { + if (svelte_config.kit.router.type === 'hash') { const fallback = await generate_fallback({ + svelte_config, manifest_path, - env, - root + out }); const file = output_filename('/', true); - const dest = `${config.outDir}/output/prerendered/pages/${file}`; + const dest = `${out}/prerendered/pages/${file}`; mkdirp(dirname(dest)); writeFileSync(dest, fallback); @@ -122,7 +99,7 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, roo return { prerendered, prerender_map }; } - /** @type {import('types').Logger} */ + /** @type {Logger} */ const log = logger({ verbose }); /** @type {Map} */ @@ -130,10 +107,10 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, roo const handle_http_error = normalise_error_handler( log, - config.prerender.handleHttpError, + svelte_config.kit.prerender.handleHttpError, ({ status, path, referrer, referenceType }) => { const message = - status === 404 && !path.startsWith(config.paths.base) + status === 404 && !path.startsWith(svelte_config.kit.paths.base) ? `${path} does not begin with \`base\`, which is configured in \`paths.base\` and can be imported from \`$app/paths\` - see https://svelte.dev/docs/kit/configuration#paths for more info` : path; @@ -143,7 +120,7 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, roo const handle_missing_id = normalise_error_handler( log, - config.prerender.handleMissingId, + svelte_config.kit.prerender.handleMissingId, ({ path, id, referrers }) => { return ( `The following pages contain links to ${path}#${id}, but no element with id="${id}" exists on ${path} - see the \`handleMissingId\` option in https://svelte.dev/docs/kit/configuration#prerender for more info:` + @@ -154,7 +131,7 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, roo const handle_entry_generator_mismatch = normalise_error_handler( log, - config.prerender.handleEntryGeneratorMismatch, + svelte_config.kit.prerender.handleEntryGeneratorMismatch, ({ generatedFromId, entry, matchedId }) => { return `The entries export from ${generatedFromId} generated entry ${entry}, which was matched by ${matchedId} - see the \`handleEntryGeneratorMismatch\` option in https://svelte.dev/docs/kit/configuration#prerender for more info.`; } @@ -162,21 +139,21 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, roo const handle_not_prerendered_route = normalise_error_handler( log, - config.prerender.handleUnseenRoutes, + svelte_config.kit.prerender.handleUnseenRoutes, ({ routes }) => { const list = routes.map((id) => ` - ${id}`).join('\n'); return `The following routes were marked as prerenderable, but were not prerendered because they were not found while crawling your app:\n${list}\n\nSee the \`handleUnseenRoutes\` option in https://svelte.dev/docs/kit/configuration#prerender for more info.`; } ); - const q = queue(config.prerender.concurrency); + const q = queue(svelte_config.kit.prerender.concurrency); /** * @param {string} path * @param {boolean} is_html */ function output_filename(path, is_html) { - const file = path.slice(config.paths.base.length + 1) || 'index.html'; + const file = path.slice(svelte_config.kit.paths.base.length + 1) || 'index.html'; if (is_html && !file.endsWith('.html')) { return file + (file.endsWith('/') ? 'index.html' : '.html'); @@ -186,20 +163,19 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, roo } const files = new Set(walk(`${out}/client`).map(posixify)); - files.add(`${config.appDir}/env.js`); + files.add(`${svelte_config.kit.appDir}/env.js`); - const immutable = `${config.appDir}/immutable`; + const immutable = `${svelte_config.kit.appDir}/immutable`; if (existsSync(`${out}/server/${immutable}`)) { for (const file of walk(`${out}/server/${immutable}`)) { - files.add(posixify(`${config.appDir}/immutable/${file}`)); + files.add(posixify(`${svelte_config.kit.appDir}/immutable/${file}`)); } } - const remote_prefix = `${config.paths.base}/${config.appDir}/remote/`; + const remote_prefix = `${svelte_config.kit.paths.base}/${svelte_config.kit.appDir}/remote/`; const seen = new Set(); const written = new Set(); - const remote_responses = new Map(); /** @type {Map>} */ const expected_hashlinks = new Map(); @@ -217,7 +193,7 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, roo if (seen.has(decoded)) return; seen.add(decoded); - const file = decoded.slice(config.paths.base.length + 1); + const file = decoded.slice(svelte_config.kit.paths.base.length + 1); if (files.has(file)) return; return q.add(() => visit(decoded, encoded || encodeURI(decoded), referrer, generated_from_id)); @@ -230,36 +206,35 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, roo * @param {string} [generated_from_id] */ async function visit(decoded, encoded, referrer, generated_from_id) { - if (!decoded.startsWith(config.paths.base)) { + if (!decoded.startsWith(svelte_config.kit.paths.base)) { handle_http_error({ status: 404, path: decoded, referrer, referenceType: 'linked' }); return; } - /** @type {Map} */ - const dependencies = new Map(); - - const response = await server.respond(new Request(config.prerender.origin + encoded), { - 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)); + /** @type {PromiseWithResolvers>} */ + const prerender_dependencies = Promise.withResolvers(); + + const event = `sveltekit:prerender-dependencies-${encoded}`; + /** @param {Record} dependencies */ + const listener = (dependencies) => { + /** @type {Map} */ + const deserialised = new Map(); + for (const [path, dependency] of Object.entries(dependencies)) { + deserialised.set(path, { + response: new Response(dependency.response.body, { + headers: dependency.response.headers, + status: dependency.response.status, + statusText: dependency.response.statusText + }), + body: dependency.body + }); } - }); + prerender_dependencies.resolve(deserialised); + vite.environments.ssr.hot.off(event, listener); + }; + + vite.environments.ssr.hot.on(event, listener); + const response = await fetch(`http://localhost:${port}${encoded}`); const encoded_id = response.headers.get('x-sveltekit-routeid'); const decoded_id = encoded_id && decode_uri(encoded_id); @@ -280,7 +255,7 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, roo const category = decoded.startsWith(remote_prefix) ? 'data' : 'pages'; save(category, response, body, decoded, encoded, referrer, 'linked'); - for (const [dependency_path, result] of dependencies) { + for (const [dependency_path, result] of await prerender_dependencies.promise) { // this seems circuitous, but using new URL allows us to not care // whether dependency_path is encoded or not const encoded_dependency_path = new URL(dependency_path, 'http://localhost').pathname; @@ -319,17 +294,21 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, roo const headers = Object.fromEntries(response.headers); // if it's a 200 HTML response, crawl it. Skip error responses, as we don't save those - if (response.ok && config.prerender.crawl && headers['content-type'] === 'text/html') { + if ( + response.ok && + svelte_config.kit.prerender.crawl && + headers['content-type'] === 'text/html' + ) { const { ids, hrefs } = crawl(body.toString(), decoded); actual_hashlinks.set(decoded, ids); /** @param {string} href */ const removePrerenderOrigin = (href) => { - if (href.startsWith(config.prerender.origin)) { - if (href === config.prerender.origin) return '/'; - if (href.at(config.prerender.origin.length) !== '/') return href; - return href.slice(config.prerender.origin.length); + if (href.startsWith(svelte_config.kit.prerender.origin)) { + if (href === svelte_config.kit.prerender.origin) return '/'; + if (href.at(svelte_config.kit.prerender.origin.length) !== '/') return href; + return href.slice(svelte_config.kit.prerender.origin.length); } return href; }; @@ -358,6 +337,9 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, roo } } + /** @type {Set} */ + const prerendered_routes = new Set(); + /** * @param {'pages' | 'dependencies' | 'data'} category * @param {Response} response @@ -375,7 +357,7 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, roo const is_html = response_type === REDIRECT || type === 'text/html'; const file = output_filename(decoded, is_html); - const dest = `${config.outDir}/output/prerendered/${category}/${file}`; + const dest = `${out}/prerendered/${category}/${file}`; if (written.has(file)) return; @@ -462,7 +444,7 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, roo handle_http_error({ status: response.status, path: decoded, referrer, referenceType }); } - manifest.assets.add(file); + vite.environments.ssr.hot.send('sveltekit:prerender-assets-update', file); saved.set(file, dest); } @@ -474,55 +456,91 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, roo } } - let should_prerender = false; - - for (const value of prerender_map.values()) { - if (value) { - should_prerender = true; - break; - } - } - - // the user's remote function modules may reference environment variables, - // `read` or the `manifest` at the top-level so we need to set them before - // evaluating those modules to avoid potential runtime errors - const { publicPrefix: public_prefix, privatePrefix: private_prefix } = config.env; - const private_env = filter_env(env, private_prefix, public_prefix); - const public_env = filter_env(env, public_prefix, private_prefix); - internal.set_private_env(private_env); - internal.set_public_env(public_env); - internal.set_manifest(manifest); - internal.set_read_implementation((file) => createReadableStream(`${out}/server/${file}`)); - - /** @type {Array} */ - const prerender_functions = []; - - for (const loader of Object.values(manifest._.remotes)) { - const module = await loader(); - - for (const fn of Object.values(module.default)) { - if (fn?.__?.type === 'prerender') { - prerender_functions.push(fn.__); - should_prerender = true; - } - } - } + const should_prerender = + prerender_map.values().some((value) => !!value) || !!metadata.remotes_with_prerender.size; if (!should_prerender) { return { prerendered, prerender_map }; } - // 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}`) + log.info('Prerendering'); + + const prerender_read_pathname = create_app_dir_matcher( + svelte_config.kit.paths.base, + svelte_config.kit.appDir, + '/prerender-read' + ); + + /** @type {PluginOption} */ + const plugin_prerender = { + name: 'vite-plugin-sveltekit-compile:prerender', + configureServer(vite) { + return () => { + vite.middlewares.use((req, res, next) => { + req.url = req.url?.replace( + new RegExp(escape_for_regexp(`^http://localhost:${port}`)), + svelte_config.kit.prerender.origin + ); + req.headers.host = new URL(svelte_config.kit.prerender.origin).host; + + const base = `${vite.config.server.https ? 'https' : 'http'}://${ + req.headers[':authority'] || req.headers.host + }`; + + const url = new URL(base + req.url); + const decoded = decodeURI(url.pathname); + + if (decoded.match(prerender_read_pathname)) { + const file = url.searchParams.get('file'); + + if (!file) { + res.writeHead(400); + res.end('Missing file query argument'); + return; + } + + /** @type {Buffer} */ + let data; + + // stuff we just wrote + const filepath = saved.get(file); + if (filepath) { + data = readFileSync(filepath); + } else if (file.startsWith(svelte_config.kit.appDir)) { + // Static assets emitted during build + data = readFileSync(`${out}/server/${file}`); + } else { + // stuff in `static` + data = readFileSync(join(svelte_config.kit.files.assets, file)); + } + + res.setHeader('content-type', 'application/octet-stream'); + res.end(data); + return; + } + + next(); + }); + }; + } + }; + + const vite = await create_build_server({ + svelte_config, + out, + manifest_path, + server_path: prerender_entry, + vite_plugins: [plugin_prerender] }); - log.info('Prerendering'); + // only start the app server after checking if prerendering is needed so + // that we don't run the user's `init` hook unnecessarily + await vite.listen(); - for (const entry of config.prerender.entries) { + const address = vite.httpServer?.address(); + const port = typeof address === 'string' ? Number(address.split(':').at(-1)) : address?.port; + + for (const entry of svelte_config.kit.prerender.entries) { if (entry === '*') { for (const [id, prerender] of prerender_map) { if (prerender) { @@ -532,36 +550,40 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env, roo if (processed_id.includes('[')) continue; const path = `/${get_route_segments(processed_id).join('/')}`; - void enqueue(null, config.paths.base + path); + void enqueue(null, svelte_config.kit.paths.base + path); } } } else { - void enqueue(null, config.paths.base + entry); + void enqueue(null, svelte_config.kit.paths.base + entry); } } for (const { id, entries } of route_level_entries) { for (const entry of entries) { - void enqueue(null, config.paths.base + entry, undefined, id); + void enqueue(null, svelte_config.kit.paths.base + entry, undefined, id); } } - const transport = (await internal.get_hooks()).transport ?? {}; - for (const internals of prerender_functions) { - if (internals.has_arg) { - for (const arg of (await internals.inputs?.()) ?? []) { - void enqueue( - null, - remote_prefix + internals.id + '/' + stringify_remote_arg(arg, transport) - ); - } - } else { - void enqueue(null, remote_prefix + internals.id); - } + const url = new URL( + `${svelte_config.kit.paths.base}/${svelte_config.kit.appDir}/prerender-functions`, + `http://localhost:${port}` + ); + for (const name of metadata.remotes_with_prerender) { + url.searchParams.append('name', name); + } + + const response = await fetch(url); + /** @type {string[]} */ + const functions_to_prerender = await response.json(); + + for (const decoded of functions_to_prerender) { + void enqueue(null, decoded); } await q.done(); + await vite.close(); + // handle invalid fragment links for (const [key, referrers] of expected_hashlinks) { const index = key.indexOf('#'); diff --git a/packages/kit/src/core/postbuild/prerender_entry.js b/packages/kit/src/core/postbuild/prerender_entry.js new file mode 100644 index 000000000000..25b72a9f440a --- /dev/null +++ b/packages/kit/src/core/postbuild/prerender_entry.js @@ -0,0 +1,130 @@ +/** @import { SSRManifest } from '@sveltejs/kit' */ +/** @import { InternalServer, RemotePrerenderInternals } from 'types' */ +/** @import { SerialisedResponse } from '../../exports/vite/types.js' */ +import { get } from '__sveltekit/ipc'; +import { Server as KitServer } from '__SERVER__/index.js'; +import { get_hooks, set_building, set_prerendering } from '__SERVER__/internal.js'; +import { stringify_remote_arg } from '../../runtime/shared.js'; + +set_building(); +set_prerendering(); + +export class Server extends KitServer { + #manifest; + + /** @param {SSRManifest} manifest */ + constructor(manifest) { + super(manifest); + + this.#manifest = manifest; + + import.meta.hot?.on('sveltekit:prerender-assets-update', (data) => { + manifest.assets.add(data); + }); + } + + /** @type {InternalServer['init']} */ + async init(options) { + options.read = async (file) => { + const response = await get(`/read?${new URLSearchParams({ file })}`); + if (!response.ok) { + throw new Error( + `read(...) failed: could not fetch ${file} (${response.status} ${response.statusText})` + ); + } + return response.body; + }; + + return super.init(options); + } + + /** @type {InternalServer['respond']} */ + async respond(request, options) { + options.getClientAddress = () => { + throw new Error('Cannot read clientAddress during prerendering'); + }; + + options.prerendering = { + fallback: request.url.endsWith('/[fallback]'), + dependencies: new Map(), + remote_responses: import.meta.hot?.data.remote_responses + }; + + options.read = async (file) => { + const response = await get(`/prerender-read?${new URLSearchParams({ file })}`); + return Buffer.from(await response.arrayBuffer()); + }; + + const url = new URL(request.url); + + if (url.pathname === `/${this.#manifest.appPath}/prerender-functions`) { + const names = url.searchParams.getAll('name'); + const pathnames = await get_prerender_function_paths(this.#manifest, names); + return Response.json(pathnames); + } + + const response = await super.respond(request, options); + + /** @type {Map} */ + const dependencies = new Map(); + for (const [key, value] of options.prerendering.dependencies) { + dependencies.set(key, { + response: { + status: value.response.status, + statusText: value.response.statusText, + headers: Object.fromEntries(value.response.headers), + body: await value.response.arrayBuffer() + }, + body: value.body + }); + } + + import.meta.hot?.send( + `sveltekit:prerender-dependencies-${url.pathname}`, + Object.fromEntries(dependencies) + ); + + return response; + } +} + +/** + * @param {SSRManifest} manifest + * @param {string[]} names + * @returns {Promise} + */ +async function get_prerender_function_paths(manifest, names) { + /** @type {RemotePrerenderInternals[]} */ + const prerender_functions = []; + + for (const name of names) { + const module = await manifest._.remotes[name](); + + for (const fn of Object.values(module.default)) { + if (fn?.__?.type === 'prerender') { + prerender_functions.push(fn.__); + } + } + } + + const remote_prefix = `/${manifest.appPath}/remote/`; + + /** @type {string[]} */ + const pathnames = []; + + const transport = (await get_hooks()).transport ?? {}; + for (const internals of prerender_functions) { + if (internals.has_arg) { + for (const arg of (await internals.inputs?.()) ?? []) { + pathnames.push(remote_prefix + internals.id + '/' + stringify_remote_arg(arg, transport)); + } + } else { + pathnames.push(remote_prefix + internals.id); + } + } + return pathnames; +} + +if (import.meta.hot) { + import.meta.hot.data.remote_responses ??= new Map(); +} diff --git a/packages/kit/src/core/sync/create_manifest_data/index.js b/packages/kit/src/core/sync/create_manifest_data/index.js index 0d28dcaf4d52..1ad4996e0282 100644 --- a/packages/kit/src/core/sync/create_manifest_data/index.js +++ b/packages/kit/src/core/sync/create_manifest_data/index.js @@ -5,12 +5,13 @@ import { styleText } from 'node:util'; import { resolve_entry } from '../../../utils/filesystem.js'; import { posixify } from '../../../utils/os.js'; import { parse_route_id } from '../../../utils/routing.js'; -import { list_files, runtime_directory } from '../../utils.js'; +import { list_files } from '../../utils.js'; import { sort_routes } from './sort.js'; import { create_node_analyser, get_page_options } from '../../../exports/vite/static_analysis/index.js'; +import { runtime_directory } from '../../../runtime/utils.js'; /** * Generates the manifest data used for the client-side manifest and types generation. diff --git a/packages/kit/src/core/sync/write_server.js b/packages/kit/src/core/sync/write_server.js index e73e00d6481e..acfaed103c53 100644 --- a/packages/kit/src/core/sync/write_server.js +++ b/packages/kit/src/core/sync/write_server.js @@ -5,7 +5,7 @@ import { resolve_entry } from '../../utils/filesystem.js'; import { posixify } from '../../utils/os.js'; import { s } from '../../utils/misc.js'; import { load_error_page, load_template } from '../config/index.js'; -import { runtime_directory } from '../utils.js'; +import { runtime_directory } from '../../runtime/utils.js'; import { write_if_changed } from './utils.js'; import { escape_html } from '../../utils/escape.js'; diff --git a/packages/kit/src/core/utils.js b/packages/kit/src/core/utils.js index f4f97bc7477e..cef687d8ac48 100644 --- a/packages/kit/src/core/utils.js +++ b/packages/kit/src/core/utils.js @@ -1,21 +1,10 @@ import fs from 'node:fs'; import path from 'node:path'; -import { fileURLToPath } from 'node:url'; import { styleText } from 'node:util'; -import { posixify } from '../utils/os.js'; import { to_fs } from '../exports/vite/filesystem.js'; +import { runtime_directory } from '../runtime/utils.js'; import { noop } from '../utils/functions.js'; -/** - * Resolved path of the `runtime` directory - * - * TODO Windows issue: - * Vite or sth else somehow sets the driver letter inconsistently to lower or upper case depending on the run environment. - * In playwright debug mode run through VS Code this a root-to-lowercase conversion is needed in order for the tests to run. - * If we do this conversion in other cases it has the opposite effect though and fails. - */ -export const runtime_directory = posixify(fileURLToPath(new URL('../runtime', import.meta.url))); - /** * This allows us to import SvelteKit internals that aren't exposed via `pkg.exports` in a * way that works whether `@sveltejs/kit` is installed inside the project's `node_modules` diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index 5d30e4d3625c..30993a8d583a 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -107,6 +107,12 @@ type UnpackValidationError = ? undefined // needs to be undefined, because void will corrupt union type : T; +export interface ManifestGenerationOptions { + /** A relative path to the base directory of the server build output */ + relativePath: string; + routes?: RouteDefinition[]; +} + /** * This object is passed to the `adapt` function of adapters. * It contains various methods and properties that are useful for adapting the app. @@ -150,9 +156,8 @@ export interface Builder { /** * Generate a server-side manifest to initialise the SvelteKit [server](https://svelte.dev/docs/kit/@sveltejs-kit#Server) with. - * @param opts a relative path to the base directory of the app and optionally in which format (esm or cjs) the manifest should be generated */ - generateManifest: (opts: { relativePath: string; routes?: RouteDefinition[] }) => string; + generateManifest: (opts: ManifestGenerationOptions) => string; /** * Resolve a path to the `name` directory inside `outDir`, e.g. `/path/to/.svelte-kit/my-adapter`. @@ -313,17 +318,6 @@ export interface Cookies { serialize: (name: string, value: string, opts: import('cookie').SerializeOptions) => string; } -/** - * A collection of functions that influence the environment during dev, build and prerendering - */ -export interface Emulator { - /** - * A function that is called with the current route `config` and `prerender` option - * and returns an `App.Platform` object - */ - platform?(details: { config: any; prerender: PrerenderOption }): MaybePromise; -} - export interface KitConfig { /** * Your [adapter](https://svelte.dev/docs/kit/adapters) is run when executing `vite build`. It determines how the output is converted for different platforms. @@ -1608,18 +1602,21 @@ export interface ServerInitOptions { * Required to instantiate `Server` with project specific information */ export interface SSRManifest { + /** The directory where SvelteKit keeps its stuff, including static assets (such as JS and CSS) and internally-used routes. */ appDir: string; + /** The `base` and `appDir` settings combined without a leading slash. */ appPath: string; + base: string; /** Static files from `kit.config.files.assets` and the service worker (if any). */ assets: Set; mimeTypes: Record; - /** private fields */ + /** @internal private fields */ _: { client: NonNullable; nodes: SSRNodeLoader[]; /** hashed filename -> import to that file */ - remotes: Record Promise>; + remotes: Record Promise<{ default: Record }>>; routes: SSRRoute[]; prerendered_routes: Set; matchers: () => Promise>; diff --git a/packages/kit/src/exports/vite/build/build_server.js b/packages/kit/src/exports/vite/build/build_server.js index 65ae7914a291..5e4bb422a87a 100644 --- a/packages/kit/src/exports/vite/build/build_server.js +++ b/packages/kit/src/exports/vite/build/build_server.js @@ -1,3 +1,6 @@ +/** @import { AssetDependencies, ManifestData, RecursiveRequired, SSRNode, ValidatedConfig, ValidatedKitConfig } from 'types' */ +/** @import { Manifest, Rolldown } from 'vite' */ + import fs from 'node:fs'; import { mkdirp } from '../../../utils/filesystem.js'; import { @@ -14,27 +17,55 @@ import { fix_css_urls } from '../../../utils/css.js'; import { escape_for_interpolation } from '../../../utils/escape.js'; /** - * @param {string} out - * @param {import('types').ValidatedKitConfig} kit - * @param {import('types').ManifestData} manifest_data - * @param {import('vite').Manifest} server_manifest - * @param {import('vite').Manifest | null} client_manifest - * @param {string | null} assets_path - * @param {import('vite').Rolldown.RolldownOutput['output'] | null} client_chunks - * @param {import('types').RecursiveRequired} output_config - * @param {string} root + * @overload partial build to analyse the server nodes + * @param {object} opts + * @param {string} opts.out + * @param {ManifestData} opts.manifest_data + * @param {Manifest} opts.server_manifest + * @param {string} opts.root + * @returns {void} + */ +/** + * @overload final build + * @param {object} opts + * @param {string} opts.out + * @param {ManifestData} opts.manifest_data + * @param {Manifest} opts.server_manifest + * @param {Manifest} opts.client_manifest + * @param {ValidatedKitConfig['paths']} opts.paths + * @param {number} opts.inline_style_threshold + * @param {string} opts.assets_path + * @param {Rolldown.RolldownOutput['output']} opts.client_chunks + * @param {RecursiveRequired} opts.output_config + * @param {string} opts.root + * @returns {void} + */ +/** + * @param {object} opts + * @param {string} opts.out + * @param {ManifestData} opts.manifest_data + * @param {Manifest} opts.server_manifest + * @param {Manifest} [opts.client_manifest] + * @param {ValidatedKitConfig['paths']} [opts.paths] + * @param {number} [opts.inline_style_threshold] + * @param {string} [opts.assets_path] path to `_app/immutable/assets` + * @param {Rolldown.RolldownOutput['output']} [opts.client_chunks] + * @param {RecursiveRequired} [opts.output_config] + * @param {string} opts.root + * @returns {void} */ -export function build_server_nodes( +export function build_server_nodes({ out, - kit, manifest_data, server_manifest, client_manifest, + paths, + inline_style_threshold, assets_path, client_chunks, output_config, root -) { +}) { mkdirp(`${out}/server/nodes`); mkdirp(`${out}/server/stylesheets`); @@ -51,14 +82,19 @@ export function build_server_nodes( */ let prepare_css_for_inlining = (css) => s(css); - if (client_chunks && kit.inlineStyleThreshold > 0 && output_config.bundleStrategy === 'split') { + if ( + client_chunks && + inline_style_threshold && + inline_style_threshold > 0 && + output_config?.bundleStrategy === 'split' + ) { for (const chunk of client_chunks) { if (chunk.type !== 'asset' || !chunk.fileName.endsWith('.css')) { continue; } const source = chunk.source.toString(); - if (source.length < kit.inlineStyleThreshold) { + if (source.length < inline_style_threshold) { stylesheets_to_inline.set(chunk.fileName, source); } } @@ -67,7 +103,7 @@ export function build_server_nodes( // relative path so that they are correct when inlined into the document. // Although `paths.assets` is static, we need to pass in a fake path // `/_svelte_kit_assets` at runtime when running `vite preview` - if (kit.paths.assets || kit.paths.relative) { + if (paths?.assets || paths?.relative) { const static_assets = new Set( manifest_data.assets.map((asset) => decodeURIComponent(asset.file)) ); @@ -115,7 +151,7 @@ export function build_server_nodes( const imports = []; // String representation of - /** @type {import('types').SSRNode} */ + /** @type {SSRNode} */ /** @type {string[]} */ const exports = [`export const index = ${i};`]; @@ -164,22 +200,22 @@ export function build_server_nodes( if ( client_manifest && (node.universal || node.component) && - output_config.bundleStrategy === 'split' + output_config?.bundleStrategy === 'split' ) { - const entry_path = `${normalizePath(kit.outDir)}/generated/client-optimized/nodes/${i}.js`; + const entry_path = `${normalizePath(`${out}/..`)}/generated/client-optimized/nodes/${i}.js`; const entry = find_deps(client_manifest, entry_path, true, root); // Eagerly load client stylesheets and fonts imported by the SSR-ed page to avoid FOUC. // However, if it is not used during SSR (not present in the server manifest), // then it can be lazily loaded in the browser. - /** @type {import('types').AssetDependencies | undefined} */ + /** @type {AssetDependencies | undefined} */ let component; if (node.component) { component = find_deps(server_manifest, node.component, true, root); } - /** @type {import('types').AssetDependencies | undefined} */ + /** @type {AssetDependencies | undefined} */ let universal; if (node.universal) { universal = find_deps(server_manifest, node.universal, true, root); diff --git a/packages/kit/src/exports/vite/build/vite_server.js b/packages/kit/src/exports/vite/build/vite_server.js new file mode 100644 index 000000000000..889124f5aba3 --- /dev/null +++ b/packages/kit/src/exports/vite/build/vite_server.js @@ -0,0 +1,400 @@ +/** @import { ValidatedConfig } from 'types' */ +/** @import { Connect, PluginOption, ViteDevServer } from 'vite' */ +/** @import { ModuleRunner } from 'vite/module-runner' */ +import fs from 'node:fs'; +import path, { basename } from 'node:path'; +import { + createFetchableDevEnvironment, + createServer, + createServerHotChannel, + createServerModuleRunner, + isFetchableDevEnvironment +} from 'vite'; +import { exactRegex } from 'rolldown/filter'; +import { sveltekit_env, sveltekit_ipc } from '../module_ids.js'; +import { dedent } from '../../../core/sync/utils.js'; +import { + check_feature, + create_app_dir_matcher, + has_correct_case, + invalidate_module, + remove_static_middlewares +} from '../dev/index.js'; +import { getRequest, setResponse } from '@sveltejs/kit/node'; +import { s } from '../../../utils/misc.js'; +import { get_env } from '../utils.js'; +import sirv from 'sirv'; +import { SVELTE_KIT_ASSETS } from '../../../constants.js'; + +/** + * @param {object} opts + * @param {ValidatedConfig} opts.svelte_config + * @param {string} opts.out + * @param {string} opts.manifest_path + * @param {string} opts.server_path + * @param {PluginOption} [opts.vite_plugins] + * @returns {Promise} + */ +export async function create_build_server({ + svelte_config, + out, + manifest_path, + server_path, + vite_plugins +}) { + /** @type {number | undefined} */ + let port; + + /** + * Allows us to perform Node operations from a non-Node environment by sending + * a request to the Vite dev server. We can then configure a middleware to + * intercept, operate, and respond. + * + * We can't achieve the same with import.meta.hot and Promise.withResolver() + * because Cloudflare's workerd doesn't like it when we await a promise resolved + * from import.meta.hot + * @type {PluginOption} + */ + const plugin_ipc = { + name: 'vite-plugin-sveltekit-compile:ipc', + configureServer(vite) { + return () => { + vite.middlewares.use((_req, _res, next) => { + // ensure the server port is up-to-date + const address = vite.httpServer?.address(); + const current_port = + typeof address === 'string' ? Number(address.split(':').at(-1)) : address?.port; + if (current_port && current_port !== port) { + port = current_port; + vite.environments.ssr.hot.send('sveltekit:port', port); + invalidate_module(vite, sveltekit_ipc); + } + + next(); + }); + }; + }, + applyToEnvironment(environment) { + return environment.config.consumer === 'server'; + }, + resolveId: { + filter: { + id: exactRegex('__sveltekit/ipc') + }, + handler() { + return '\0virtual:__sveltekit/ipc'; + } + }, + load: { + filter: { + id: exactRegex(sveltekit_ipc) + }, + handler() { + return dedent` + // helps us avoid global fetch warnings we emit when the user uses it incorrectly + const native_fetch = globalThis.fetch; + + export function get(pathname) { + return native_fetch(\`http://localhost:\${port}/${svelte_config.kit.appDir}\${pathname}\`); + } + + let port${port ? ` = ${port}` : ''}; + import.meta.hot?.on('sveltekit:port', (update) => { port = update }); + `; + } + } + }; + + /** @type {{ public: Record; private: Record }} */ + let env; + + /** @type {Connect.ServerStackItem | undefined} */ + let serve_static_middleware; + + /** @type {PluginOption} */ + const plugin_server = { + name: 'vite-plugin-sveltekit-compile:build-entry', + config(_, vite_config_env) { + env = get_env(svelte_config.kit.env, vite_config_env.mode); + + return { + appType: 'custom', + cacheDir: `node_modules/.vite-${basename(server_path, '.js')}`, + environments: { + ssr: { + build: { + outDir: `${out}/server` + } + } + }, + publicDir: `${out}/client`, + resolve: { + alias: [ + { + find: '__SERVER__', + replacement: `${out}/server` + } + ] + }, + server: { + watch: { + ignored: out + } + } + }; + }, + configureServer(vite) { + const assets = svelte_config.kit.paths.assets + ? SVELTE_KIT_ASSETS + : svelte_config.kit.paths.base; + + const asset_server = sirv(`${out}/client`, { + dev: true, + etag: true, + maxAge: 0, + extensions: [], + setHeaders: (res) => { + res.setHeader('access-control-allow-origin', '*'); + } + }); + + vite.middlewares.use((req, res, next) => { + const base = `${vite.config.server.https ? 'https' : 'http'}://${ + req.headers[':authority'] || req.headers.host + }`; + + const decoded = decodeURI(new URL(base + req.url).pathname); + + if (decoded.startsWith(assets)) { + const pathname = decoded.slice(assets.length); + const file = svelte_config.kit.files.assets + pathname; + + if (fs.existsSync(file) && !fs.statSync(file).isDirectory()) { + if (has_correct_case(file, svelte_config.kit.files.assets)) { + req.url = encodeURI(pathname); // don't need query/hash + asset_server(req, res); + return; + } + } + } + + next(); + }); + + const read_pathname = create_app_dir_matcher( + svelte_config.kit.paths.base, + svelte_config.kit.appDir, + '/read' + ); + + const check_feature_pathname = create_app_dir_matcher( + svelte_config.kit.paths.base, + svelte_config.kit.appDir, + '/check-feature' + ); + + return () => { + serve_static_middleware = vite.middlewares.stack.find( + (middleware) => + /** @type {Function} */ (middleware.handle).name === 'viteServeStaticMiddleware' + ); + + // Vite will give a 403 on URLs like /test, /static, and /package.json preventing us from + // serving routes with those names. See https://github.com/vitejs/vite/issues/7363 + remove_static_middlewares(vite.middlewares); + + vite.middlewares.use((req, res, next) => { + // Vite's base middleware strips out the base path. Restore it + req.url = req.originalUrl; + + const base = `${vite.config.server.https ? 'https' : 'http'}://${ + req.headers[':authority'] || req.headers.host + }`; + + const url = new URL(base + req.url); + const decoded = decodeURI(url.pathname); + + if (decoded.match(check_feature_pathname)) { + const route_id = url.searchParams.get('route_id'); + const config = url.searchParams.get('config'); + const feature = url.searchParams.get('feature'); + + if (!route_id || !config || !feature) { + res.writeHead(400); + res.end('Must have route_id, config, and feature query arguments'); + return; + } + + const result = check_feature( + route_id, + JSON.parse(config), + feature, + svelte_config.kit.adapter + ); + + res.writeHead(200); + res.end(result?.message); + return; + } + + if (decoded.match(read_pathname)) { + const file = url.searchParams.get('file'); + if (!file) { + res.writeHead(400); + res.end('Missing file query argument'); + return; + } + + const readable_stream = fs.createReadStream( + `${svelte_config.kit.outDir}/output/server/${file}` + ); + + res.writeHead(200); + readable_stream.pipe(res); + return; + } + + next(); + }); + }; + }, + 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 manifest_path; + } + + // substitute the Server class with our script instead + if (id === 'sveltekit:server') { + return server_path; + } + + if (id === 'sveltekit:env') { + return sveltekit_env; + } + } + }, + load: { + filter: { + id: exactRegex(sveltekit_env) + }, + handler() { + return `export const env = ${s({ ...env.private, ...env.public })};`; + } + } + }; + + /** @type {ModuleRunner} */ + let runner; + + /** @type {string | undefined} */ + let remote_address; + + /** @type {PluginOption} */ + const plugin_node_environment = { + name: 'vite-plugin-sveltekit-compile:node-environment', + config() { + return { + environments: { + ssr: { + dev: { + createEnvironment(name, config) { + return createFetchableDevEnvironment(name, config, { + hot: true, + transport: createServerHotChannel(), + async handleRequest(request) { + try { + /** @type {import('../dev/ssr_entry.js')} */ + const { respond } = await runner.import('__sveltekit/dev-server-entry'); + return await respond(request, remote_address); + } catch (error) { + // Vite doesn't log errors so we do it ourselves + console.error(error); + throw error; + } + } + }); + } + } + } + } + }; + }, + async configureServer(vite) { + if (runner) await runner.close(); + runner = createServerModuleRunner(vite.environments.ssr); + + return () => { + vite.middlewares.use(async (req, res, next) => { + remote_address = req.socket.remoteAddress; + + // Vite's base middleware strips out the base path. Restore it + req.url = req.originalUrl; + + const base = `${vite.config.server.https ? 'https' : 'http'}://${ + req.headers[':authority'] || req.headers.host + }`; + + // fallback to our own fetch handler if the adapter doesn't provide one + if (!isFetchableDevEnvironment(vite.environments.ssr)) { + throw new Error( + 'The Vite configured dev SSR environment must be a FetchableDevEnvironment' + ); + } + + const request = await getRequest({ + base, + request: req + }); + const response = await vite.environments.ssr.dispatchFetch(request); + + if (response.status === 404) { + // @ts-expect-error + serve_static_middleware?.handle(req, res, () => { + void setResponse(res, response); + }); + } else { + void setResponse(res, response); + } + + next(); + }); + }; + }, + applyToEnvironment(environment) { + return environment.config.consumer === 'server'; + }, + resolveId: { + filter: { + id: exactRegex('__sveltekit/dev-server-entry') + }, + handler() { + return path.join(import.meta.dirname, '../dev/ssr_entry.js'); + } + } + }; + + /** @type {PluginOption} */ + const plugins = [ + vite_plugins, + plugin_ipc, + plugin_server, + svelte_config.kit.adapter?.vite?.plugins ?? plugin_node_environment + ].filter(Boolean); + + return createServer({ + configFile: false, + command: 'serve', + plugins + }); +} diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 8202a1076a34..f42201e67b7a 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -12,8 +12,8 @@ import { load_error_page } from '../../../core/config/index.js'; import { SVELTE_KIT_ASSETS } from '../../../constants.js'; import * as sync from '../../../core/sync/sync.js'; import { is_chrome_devtools_request, not_found } from '../utils.js'; -import { escape_html } from '../../../utils/escape.js'; -import { sveltekit_manifest_data } from '../module_ids.js'; +import { escape_for_regexp, escape_html } from '../../../utils/escape.js'; +import { sveltekit_ipc, sveltekit_manifest_data } from '../module_ids.js'; import { to_fs } from '../filesystem.js'; // vite-specifc queries that we should skip handling for css urls @@ -60,7 +60,7 @@ export function dev(vite, vite_config, svelte_config, root, dev_environment) { return; } - void invalidate_module(vite, sveltekit_manifest_data); + invalidate_module(vite, sveltekit_manifest_data); } update_manifest(); @@ -147,7 +147,6 @@ export function dev(vite, vite_config, svelte_config, root, dev_environment) { }); const assets = svelte_config.kit.paths.assets ? SVELTE_KIT_ASSETS : svelte_config.kit.paths.base; - dev_environment.assets = assets; const asset_server = sirv(svelte_config.kit.files.assets, { dev: true, @@ -182,7 +181,21 @@ export function dev(vite, vite_config, svelte_config, root, dev_environment) { next(); }); + const inline_styles_pathname = create_app_dir_matcher( + svelte_config.kit.paths.base, + svelte_config.kit.appDir, + '/inline-styles' + ); + const check_feature_pathname = create_app_dir_matcher( + svelte_config.kit.paths.base, + svelte_config.kit.appDir, + '/check-feature' + ); + return () => { + // ensure it has the correct server port + invalidate_module(vite, sveltekit_ipc); + const serve_static_middleware = vite.middlewares.stack.find( (middleware) => /** @type {Function} */ (middleware.handle).name === 'viteServeStaticMiddleware' @@ -193,25 +206,16 @@ export function dev(vite, vite_config, svelte_config, root, dev_environment) { remove_static_middlewares(vite.middlewares); vite.middlewares.use(async (req, res, next) => { - dev_environment.remote_address = req.socket.remoteAddress; - // Vite's base middleware strips out the base path. Restore it - let original_url = req.url; + const original_url = req.url; req.url = req.originalUrl; try { const base = `${vite.config.server.https ? 'https' : 'http'}://${ req.headers[':authority'] || req.headers.host }`; - let decoded = decodeURI(new URL(base + req.url).pathname); - - // requests to _app/immutable during development are fetchable dev - // environments trying to read the filesystem - const immutable = `/${svelte_config.kit.appDir}/immutable`; - if (decoded.startsWith(immutable)) { - decoded = decoded.slice(immutable.length); - original_url = original_url?.slice(immutable.length); - } + const url = new URL(base + req.url); + const decoded = decodeURI(url.pathname); const file = posixify( path.resolve(root, decoded.slice(svelte_config.kit.paths.base.length + 1)) @@ -236,6 +240,39 @@ export function dev(vite, vite_config, svelte_config, root, dev_environment) { return not_found(req, res, svelte_config.kit.paths.base); } + if (decoded.match(inline_styles_pathname)) { + const urls = url.searchParams.getAll('urls'); + const styles = await get_inline_css(vite, urls); + res.writeHead(200, { + 'content-type': 'application/json' + }); + res.end(JSON.stringify(styles)); + return; + } + + if (decoded.match(check_feature_pathname)) { + const route_id = url.searchParams.get('route_id'); + const config = url.searchParams.get('config'); + const feature = url.searchParams.get('feature'); + + if (!route_id || !config || !feature) { + res.writeHead(400); + res.end('Must have route_id, config, and feature query arguments'); + return; + } + + const result = check_feature( + route_id, + JSON.parse(config), + feature, + svelte_config.kit.adapter + ); + + res.writeHead(200); + res.end(result?.message); + return; + } + if (decoded === svelte_config.kit.paths.base + '/service-worker.js') { const resolved = resolve_entry(svelte_config.kit.files.serviceWorker); @@ -275,10 +312,13 @@ export function dev(vite, vite_config, svelte_config, root, dev_environment) { } // fallback to our own fetch handler if the adapter doesn't provide one - if ( - !svelte_config.kit.adapter?.vite?.plugins && - isFetchableDevEnvironment(vite.environments.ssr) - ) { + if (!svelte_config.kit.adapter?.vite?.plugins) { + if (!isFetchableDevEnvironment(vite.environments.ssr)) { + throw new Error( + 'The Vite configured dev SSR environment must be a FetchableDevEnvironment' + ); + } + const request = await getRequest({ base, request: req @@ -309,7 +349,7 @@ export function dev(vite, vite_config, svelte_config, root, dev_environment) { /** * @param {import('connect').Server} server */ -function remove_static_middlewares(server) { +export function remove_static_middlewares(server) { const static_middlewares = ['viteServeStaticMiddleware', 'viteServePublicMiddleware']; for (let i = server.stack.length - 1; i > 0; i--) { // @ts-expect-error using internals @@ -371,7 +411,7 @@ async function find_deps(vite, node, deps) { * @param {string} assets * @returns {boolean} */ -function has_correct_case(file, assets) { +export function has_correct_case(file, assets) { if (file === assets) return true; const parent = path.dirname(file); @@ -397,12 +437,23 @@ export function invalidate_module(vite, id) { } } +/** + * Creates a RegExp to help match against app directory requests. + * @param {string} base + * @param {string} appDir + * @param {string} pattern + * @returns {RegExp} + */ +export function create_app_dir_matcher(base, appDir, pattern) { + return new RegExp(`^${escape_for_regexp(`${base}/${appDir}${pattern}`)}$`); +} + /** * @param {import('vite').ViteDevServer} vite * @param {string[]} urls * @returns {Promise>} */ -export async function get_inline_css(vite, urls) { +async function get_inline_css(vite, urls) { /** @type {Set} */ const deps = new Set(); @@ -426,3 +477,30 @@ export async function get_inline_css(vite, urls) { return Object.fromEntries(styles); } + +/** + * + * @param {string} route_id + * @param {unknown} config + * @param {string} feature + * @param {import('@sveltejs/kit').Adapter | undefined} adapter + * @returns { { message: string } | void } + */ +export function check_feature(route_id, config, feature, adapter) { + if (!adapter) return; + + switch (feature) { + case '$app/server:read': { + const supported = adapter.supports?.read?.({ + route: { id: route_id }, + config + }); + + if (!supported) { + return { + message: `Cannot use \`read\` from \`$app/server\` in ${route_id} when using ${adapter.name}. Please ensure that your adapter is up to date and supports this feature.` + }; + } + } + } +} diff --git a/packages/kit/src/exports/vite/dev/server.js b/packages/kit/src/exports/vite/dev/server.js index 67b084677d86..da6c9a16665f 100644 --- a/packages/kit/src/exports/vite/dev/server.js +++ b/packages/kit/src/exports/vite/dev/server.js @@ -1,40 +1,43 @@ -/** @import { InternalServer, ValidatedKitConfig } from 'types' */ +import { AsyncLocalStorage } from 'node:async_hooks'; +import { DEV } from 'esm-env'; +import { Server as KitServer } from '../../../runtime/server/index.js'; +import { check_feature } from '../../../utils/features.js'; +import { SCHEME } from '../../../utils/url.js'; -import fs from 'node:fs'; -import path from 'node:path'; -import { Server } from 'virtual:@sveltejs/kit/vite/environment/server'; -import { env, manifest } from 'virtual:@sveltejs/kit/vite/environment'; -import { createReadableStream } from '@sveltejs/kit/node'; -import { from_fs } from '../filesystem.js'; +const async_local_storage = new AsyncLocalStorage(); -/** @type {InternalServer} */ -const server = new Server(manifest); +/** @param {string} label */ +globalThis.__SVELTEKIT_TRACK__ = (label) => { + const context = async_local_storage.getStore(); + if (!context || context.prerender === true) return; -await server.init({ - env, - read: (file) => createReadableStream(from_fs(file)) -}); + // we can't await this because `read` has a synchronous signature + void check_feature(context.event.route.id, context.config, label); +}; -/** - * @param {Request} request - * @param {string | undefined} remote_address - * @param {ValidatedKitConfig} kit - * @returns {Promise} - */ -export async function respond(request, remote_address, kit) { - 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)); - } +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 fs.readFileSync(path.join(kit.files.assets, file)); + return fetch(info, init); +}; + +export class Server extends KitServer { + /** @type {import('types').InternalServer['respond']} */ + async respond(request, options) { + if (DEV) { + 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); + }; } - }); -} -import.meta.hot?.accept(); + return await super.respond(request, options); + } +} 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..f20c0cd01014 --- /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 '../filesystem.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_PATHS_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..d509e02e4f99 --- /dev/null +++ b/packages/kit/src/exports/vite/dev/ssr_manifest.js @@ -0,0 +1,203 @@ +/** @import { SSRManifest } from '@sveltejs/kit' */ +import { server_assets } from '__sveltekit/server-assets'; +import { remotes } from '__sveltekit/remotes'; +import { manifest_data, mime_types } from '__sveltekit/manifest-data'; +import { get } from '__sveltekit/ipc'; +import { to_fs } from '../filesystem.js'; +import { compact } from '../../../utils/array.js'; +import { join } from '../../../utils/path.js'; +import { runtime_directory } from '../../../runtime/utils.js'; + +/** @type {SSRManifest} */ +export const manifest = { + appDir: __SVELTEKIT_APP_DIR__, + appPath: __SVELTEKIT_APP_DIR__, + assets: new Set(manifest_data.assets.map((asset) => asset.file)), + base: __SVELTEKIT_PATHS_BASE__, + mimeTypes: mime_types, + _: { + client: { + start: to_fs(`${runtime_directory}/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, + 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 {string[]} */ + const urls = []; + + if (node.component) { + result.component = async () => { + const { module, url } = await resolve( + join(__SVELTEKIT_ROOT__, /** @type {string} */ (node.component)) + ); + urls.push(url); + 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, url } = await resolve(join(__SVELTEKIT_ROOT__, node.universal)); + urls.push(url); + 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 () => { + const search_params = new URLSearchParams(); + for (const url of urls) { + search_params.append('urls', url); + } + + const response = await get(`/inline-styles?${search_params}`); + if (!response.ok) { + throw new Error( + `Failed to fetch inline styles for node ${i}: ${response.status} ${response.statusText}. This should never happen` + ); + } + + /** @type {Record} */ + const styles = await response.json(); + + const importing_styles = Object.entries(styles).map(async ([dep_url, inline_css_url]) => { + return [ + dep_url, + await import(/* @vite-ignore */ inline_css_url).then((mod) => mod.default) + ]; + }); + + return Object.fromEntries(await Promise.all(importing_styles)); + }; + + return result; + }; + }), + prerendered_routes: new Set(), + get remotes() { + return Object.fromEntries( + remotes.map((remote) => [ + remote.hash, + () => + import(/* @vite-ignore */ join(__SVELTEKIT_ROOT__, 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 () => { + const importing_matchers = Object.entries(manifest_data.matchers).map( + async ([name, file]) => { + const url = join(__SVELTEKIT_ROOT__, file); + const { module } = await resolve(url); + if (!module.match) { + throw new Error(`${file} does not export a \`match\` function`); + } + return [name, module.match]; + } + ); + return Object.fromEntries(await Promise.all(importing_matchers)); + } + } +}; + +/** @param {string} url */ +async function loud_ssr_load_module(url) { + try { + return await import(/* @vite-ignore */ url); + } catch (err) { + if (err instanceof Error) { + import.meta.hot?.send('sveltekit:ssr-load-module', { + ...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(id) : `file:///${id}`; + const module = await loud_ssr_load_module(url); + return { module, url }; +} diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 1d7502a74854..d7b4ef8f951b 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -1,7 +1,6 @@ -/** @import { Adapter } from '@sveltejs/kit' */ /** @import { Options } from '@sveltejs/vite-plugin-svelte' */ /** @import { PreprocessorGroup } from 'svelte/compiler' */ -/** @import { ConfigEnv, Plugin, PluginOption, ResolvedConfig, UserConfig, ViteDevServer } from 'vite' */ +/** @import { ConfigEnv, Plugin, PluginOption, ResolvedConfig, UserConfig, Manifest, EnvironmentOptions, Rolldown } from 'vite' */ /** @import { ModuleRunner } from 'vite/module-runner' */ import fs from 'node:fs'; import path from 'node:path'; @@ -14,19 +13,18 @@ import { buildErrorMessage, createFetchableDevEnvironment, createServerHotChannel, - createServerModuleRunner, - loadEnv + createServerModuleRunner } from 'vite'; import { copy, mkdirp, read, resolve_entry, rimraf } from '../../utils/filesystem.js'; import { create_static_module, create_dynamic_module } from '../../core/env.js'; import * as sync from '../../core/sync/sync.js'; import { create_assets } from '../../core/sync/create_manifest_data/index.js'; -import { runtime_directory, logger, get_runtime_base, get_mime_lookup } from '../../core/utils.js'; +import { logger, 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 { assets_base, find_deps, resolve_symlinks } from './build/utils.js'; -import { dev, invalidate_module, get_inline_css } from './dev/index.js'; +import { dev, invalidate_module } from './dev/index.js'; import { preview } from './preview/index.js'; import { error_for_missing_config, @@ -53,9 +51,10 @@ import { sveltekit_server_assets, sveltekit_environment, sveltekit_server, - sveltekit_dev_server, sveltekit_traced, - sveltekit_manifest_data + sveltekit_manifest_data, + sveltekit_env, + sveltekit_ipc } from './module_ids.js'; import { to_fs } from './filesystem.js'; import { import_peer } from '../../utils/import.js'; @@ -64,6 +63,8 @@ import { posixify } from '../../utils/os.js'; import { should_ignore, has_children } from './static_analysis/utils.js'; import { load_config } from '../../core/config/index.js'; import { treeshake_prerendered_remotes } from './build/remote.js'; +import { runtime_directory } from '../../runtime/utils.js'; +import { SVELTE_KIT_ASSETS } from '../../constants.js'; const cwd = process.cwd(); @@ -161,9 +162,9 @@ let vite_plugin_svelte; /** * Returns the SvelteKit Vite plugins. * @param {{ - * adapter?: Adapter; + * adapter?: import('@sveltejs/kit').Adapter; * }=} options - * @returns {Promise} + * @returns {Promise} */ export async function sveltekit(options = {}) { // the config options will be set only after the Vite `config` hook runs @@ -182,12 +183,11 @@ export async function sveltekit(options = {}) { return [ plugin_svelte_config({ vite_plugin_svelte_options, svelte_config }), vite_plugin_svelte.svelte(vite_plugin_svelte_options), - kit({ svelte_config, adapter_in_vite_config: !!options.adapter }), - options.adapter?.vite?.plugins + kit({ svelte_config, adapter_in_vite_config: options.adapter }) ]; } -/** @param {import('vite').UserConfig | import('vite').ResolvedConfig} vite_config */ +/** @param {UserConfig | ResolvedConfig} vite_config */ function resolve_root(vite_config) { return posixify(vite_config.root ? path.resolve(vite_config.root) : cwd); } @@ -199,7 +199,7 @@ function resolve_root(vite_config) { * vite_plugin_svelte_options: import('@sveltejs/vite-plugin-svelte').Options; * svelte_config: import('types').ValidatedConfig; * }} options - * @return {import('vite').Plugin} + * @return {Plugin} */ function plugin_svelte_config({ vite_plugin_svelte_options, svelte_config }) { return { @@ -262,11 +262,10 @@ function revive_functions(value) { * - https://rolldown.rs/apis/plugin-api#build-hooks * - https://rolldown.rs/apis/plugin-api#output-generation-hooks * - * @param {{ - * svelte_config: import('types').ValidatedConfig; - * adapter_in_vite_config: boolean - * }} options - * @return {import('vite').PluginOption[]} + * @param {object} opts + * @param {import('types').ValidatedConfig} opts.svelte_config options are only resolved after the Vite `config` hook runs + * @param {import('@sveltejs/kit').Adapter | undefined} opts.adapter_in_vite_config True if an adapter was passed to the Vite plugin + * @return {PluginOption[]} */ function kit({ svelte_config, adapter_in_vite_config }) { /** @type {typeof import('vite')} */ @@ -315,6 +314,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { let normalized_lib; /** @type {string} */ let normalized_node_modules; + /** * A map showing which features (such as `$app/server:read`) are defined * in which chunks, so that we can later determine which routes use which features @@ -325,9 +325,6 @@ function kit({ svelte_config, adapter_in_vite_config }) { const sourcemapIgnoreList = /** @param {string} relative_path */ (relative_path) => relative_path.includes('node_modules') || relative_path.includes(kit.outDir); - /** @type {ModuleRunner | null} */ - let module_runner = null; - /** @type {Plugin} */ const plugin_setup = { name: 'vite-plugin-sveltekit-setup', @@ -347,13 +344,19 @@ function kit({ svelte_config, adapter_in_vite_config }) { vite_config_env = config_env; is_build = config_env.command === 'build'; + // if the adapter vite plugins store some values at the module level, we + // should use the adapter instance passed through the vite config to + // avoid losing those values + if (adapter_in_vite_config) svelte_config.kit.adapter = adapter_in_vite_config; + ({ kit } = svelte_config); out = `${kit.outDir}/output`; if (kit.adapter?.vite?.plugins && !adapter_in_vite_config) { + // TODO: use the actual pathname of the user's svelte config file in the error message example throw new Error( - `${kit.adapter.name} requires the adapter to be passed through the \`sveltekit\` Vite plugin in the \`vite.config.js\` file. For example:\n\n` + - `+++import adapter from '${kit.adapter.name}';+++\n\nexport default defineConfig({\n plugins: [sveltekit( +++{ adapter }+++ )]\n});` + `${kit.adapter.name} requires the adapter to be passed to the \`sveltekit\` Vite plugin in the \`vite.config.js\` file. For example:\n\n` + + `+++import svelteConfig from './svelte.config.js';+++\n\nexport default defineConfig({\n plugins: [sveltekit( +++{ adapter: svelteConfig.kit?.adapter }+++ )]\n});` ); } @@ -415,11 +418,10 @@ function kit({ svelte_config, adapter_in_vite_config }) { preview: { cors: { preflightContinue: true } }, + // By default, only client environments inherit the top-level `optimizeDeps` + // but we manually pass it down in adapters that use `optimizeDeps` for "full-bundle mode" optimizeDeps: { - entries: [ - `${kit.files.routes}/**/+*.{svelte,js,ts}`, - `!${kit.files.routes}/**/+*server.*` - ], + entries: [`${kit.files.routes}/**/+*.{svelte,js,ts}`], exclude: [ // Without this SvelteKit will be prebundled on the client, which means we end up with two versions of Redirect etc. // Also see https://github.com/sveltejs/kit/issues/5952#issuecomment-1218844057 @@ -428,7 +430,9 @@ function kit({ svelte_config, adapter_in_vite_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: [ @@ -444,17 +448,6 @@ function kit({ svelte_config, adapter_in_vite_config }) { // uses basic concatenation) '@sveltejs/kit/src/runtime' ] - }, - future: { - removePluginHookHandleHotUpdate: 'warn', - removePluginHookSsrArgument: 'warn', - removeServerHot: 'warn', - removeServerModuleGraph: 'warn', - removeServerPluginContainer: 'warn', - removeServerReloadModule: 'warn', - removeServerTransformRequest: 'warn', - removeServerWarmupRequest: 'warn', - removeSsrLoadModule: 'warn' } }; @@ -481,6 +474,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { const define = { __SVELTEKIT_APP_DIR__: s(kit.appDir), + __SVELTEKIT_OUT_DIR__: s(kit.outDir), __SVELTEKIT_EMBEDDED__: s(kit.embedded), __SVELTEKIT_FORK_PRELOADS__: s(kit.experimental.forkPreloads), __SVELTEKIT_PATHS_ASSETS__: s(kit.paths.assets), @@ -489,7 +483,8 @@ function kit({ svelte_config, adapter_in_vite_config }) { __SVELTEKIT_CLIENT_ROUTING__: s(kit.router.resolution === 'client'), __SVELTEKIT_HASH_ROUTING__: s(kit.router.type === 'hash'), __SVELTEKIT_SERVER_TRACING_ENABLED__: s(kit.experimental.tracing.server), - __SVELTEKIT_EXPERIMENTAL_USE_TRANSFORM_ERROR__: s(kit.experimental.handleRenderingErrors) + __SVELTEKIT_EXPERIMENTAL_USE_TRANSFORM_ERROR__: s(kit.experimental.handleRenderingErrors), + __SVELTEKIT_ROOT__: s(root) }; if (is_build) { @@ -509,51 +504,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { __SVELTEKIT_APP_VERSION_POLL_INTERVAL__: '0', __SVELTEKIT_PAYLOAD__: 'globalThis.__sveltekit_dev', __SVELTEKIT_HAS_SERVER_LOAD__: 'true', - __SVELTEKIT_HAS_UNIVERSAL_LOAD__: 'true', - __SVELTEKIT_ROOT__: s(root) - }; - - new_config.environments = { - ssr: { - // this node environment is the fallback if the adapter doesn't - // specify its own dev environment - dev: { - async createEnvironment(name, config) { - if (module_runner) { - await module_runner?.close(); - module_runner = null; - } - - return createFetchableDevEnvironment(name, config, { - hot: true, - transport: createServerHotChannel(), - async handleRequest(request) { - if (!dev_environment) { - throw new Error( - 'The Vite dev server was not found. But this should never happen' - ); - } - - module_runner ??= createServerModuleRunner( - dev_environment.vite.environments.ssr - ); - - try { - /** @type {import('./dev/server.js')} */ - const { respond } = await module_runner.import( - '__sveltekit/dev-server-entry' - ); - return await respond(request, dev_environment?.remote_address, kit); - } catch (error) { - // Vite doesn't log the thrown error so we have to do it ourselves - console.error(error); - throw error; - } - } - }); - } - } - } + __SVELTEKIT_HAS_UNIVERSAL_LOAD__: 'true' }; } @@ -571,21 +522,86 @@ function kit({ svelte_config, adapter_in_vite_config }) { } }; - /** @type {Record} */ - let server_assets = {}; + /** @type {ModuleRunner} */ + let runner; + + /** @type {string | undefined} */ + let remote_address; + + /** @type {Plugin} */ + const plugin_node_environment = { + name: 'vite-plugin-sveltekit-node-environment', + apply: 'serve', + config(config) { + /** @type {UserConfig} */ + const new_config = { + environments: { + ssr: { + dev: { + createEnvironment(name, config) { + return createFetchableDevEnvironment(name, config, { + hot: true, + transport: createServerHotChannel(), + async handleRequest(request) { + try { + /** @type {import('./dev/ssr_entry.js')} */ + const { respond } = await runner.import('__sveltekit/dev-server-entry'); + return await respond(request, remote_address); + } catch (error) { + // Vite doesn't log errors so we do it ourselves + console.error(error); + throw error; + } + } + }); + } + } + } + } + }; + + warn_overridden_config(config, new_config); + + return new_config; + }, + async configureServer(vite) { + if (runner) await runner.close(); + runner = createServerModuleRunner(vite.environments.ssr); + + return () => { + vite.middlewares.use((req, _res, next) => { + remote_address = req.socket.remoteAddress; + next(); + }); + }; + }, + resolveId: { + filter: { + id: exactRegex('__sveltekit/dev-server-entry') + }, + handler() { + return server_instrumentation_file + ? sveltekit_traced + : path.join(import.meta.dirname, 'dev/ssr_entry.js'); + } + } + }; + + /** @type {Map} */ + let server_assets; /** * Allows us to access the filesystem from an environment that doesn't have `node:fs` - * @type {import('vite').Plugin} + * @type {Plugin} */ const plugin_server_filesystem = { - name: 'vite-plugin-sveltekit-server-filesystem', + name: 'vite-plugin-sveltekit-dev-server-filesystem', apply: 'serve', applyToEnvironment(environment) { - return environment.name !== 'client' && environment.name !== 'serviceWorker'; + return environment.config.consumer === 'server'; }, configureServer() { - server_assets = {}; + server_assets = new Map(); }, load: { order: 'pre', @@ -602,21 +618,207 @@ function kit({ svelte_config, adapter_in_vite_config }) { const filepath = pathname.startsWith(root) ? posixify(path.relative(root, pathname)) : to_fs(pathname); - const size = fs.statSync(pathname).size; + // it should be a typed array for devalue to serialise it + const data = new Uint8Array(fs.readFileSync(pathname)); + const size = data.byteLength; // update it immediately dev_environment.vite.environments.ssr.hot.send(`sveltekit:server-assets`, { - [filepath]: size + filepath, + size, + data: devalue.stringify(data) }); // persist changes in case of server reload - server_assets[filepath] = size; + server_assets.set(filepath, { size, data: devalue.uneval(data) }); invalidate_module(dev_environment.vite, sveltekit_server_assets); } } } }; + /** @type {number | undefined} */ + let port; + + /** @type {Plugin} */ + const plugin_dev_ssr = { + name: 'vite-plugin-sveltekit-dev-ssr', + apply: 'serve', + configureServer(vite) { + return () => { + vite.middlewares.use((_req, _res, next) => { + // ensure the server port is up-to-date + const address = vite.httpServer?.address(); + const current_port = + typeof address === 'string' ? Number(address.split(':').at(-1)) : address?.port; + if (current_port && current_port !== port) { + port = current_port; + vite.environments.ssr.hot.send('sveltekit:port', port); + invalidate_module(vite, sveltekit_ipc); + } + + next(); + }); + }; + }, + 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 sveltekit_env; + } + } + }, + load: { + filter: { + id: [ + exactRegex(sveltekit_env), + exactRegex(sveltekit_ipc), + exactRegex(sveltekit_remotes), + exactRegex(sveltekit_server_assets), + exactRegex(sveltekit_manifest_data), + exactRegex(sveltekit_traced) + ] + }, + handler(id) { + switch (id) { + case sveltekit_env: { + return `export const env = ${s({ ...env.private, ...env.public })};`; + } + + case sveltekit_ipc: { + if (!dev_environment) { + throw new Error('dev_environment was not initialised. But this should never happen'); + } + + const address = dev_environment.vite.httpServer?.address(); + const port = + typeof address === 'string' ? Number(address.split(':').at(-1)) : address?.port; + + return dedent` + // helps us avoid global fetch warnings we emit when the user uses it incorrectly + const native_fetch = globalThis.fetch; + + export function get(pathname) { + return native_fetch(\`http://localhost:\${port}/${svelte_config.kit.appDir}\${pathname}\`); + } + + let port${port ? ` = ${port}` : ''}; + import.meta.hot?.on('sveltekit:port', (update) => { port = update }); + `; + } + + case sveltekit_server_assets: { + /** @type {Array<[string, { size: number; data: string }]>} */ + const entries = Object.entries(server_assets); + + return dedent` + export const server_assets = { + ${entries + .map(([filepath, { size }]) => { + return `${s(filepath)}: ${size}`; + }) + .join(',\n')} + }; + + export const server_assets_content = { + ${entries + .map(([filepath, { data }]) => { + return `${s(filepath)}: ${data}`; + }) + .join(',\n')} + }; + + let devalue; + + import.meta.hot?.on('sveltekit:server-assets', async ({ filepath, size, data }) => { + // we have to dynamically import this because a static import throws + // Error: Cannot find module 'devalue' imported from 'virtual:__sveltekit/server-assets' + devalue ??= await import('devalue'); + server_assets[filepath] = size; + server_assets_content[filepath] = devalue.parse(data); + }); + `; + } + + case sveltekit_remotes: { + return dedent` + export const remotes = ${s(remotes)}; + + import.meta.hot?.on('sveltekit:remotes', (data) => { + remotes.push(data); + }); + `; + } + + case sveltekit_manifest_data: { + if (!dev_environment) { + throw new Error('dev_environment was not initialised. But this should never happen'); + } + + const { manifest_data } = dev_environment; + + const assets = svelte_config.kit.paths.assets + ? SVELTE_KIT_ASSETS + : svelte_config.kit.paths.base; + + return dedent` + import { set_assets } from '__SERVER__/internal.js'; + + set_assets(${s(assets)}); + + 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, revive_functions)}, + routes: ${devalue.uneval(manifest_data.routes)}, + matchers: ${s(manifest_data.matchers)} + }; + + export const mime_types = ${s(get_mime_lookup(manifest_data))}; + `; + } + + case sveltekit_traced: { + if (!server_instrumentation_file) { + throw new Error('Server instrumentation file not found. This should never happen'); + } + + return dedent` + import '${posixify(server_instrumentation_file)}'; + + const { respond } = await import('${import.meta.resolve('./dev/ssr_entry.js')}'); + export { respond }; + + import.meta.hot?.accept(); + `; + } + } + } + } + }; + /** @type {Plugin} */ const plugin_virtual_modules = { name: 'vite-plugin-sveltekit-virtual-modules', @@ -662,24 +864,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { return `${runtime_directory}/client/remote-functions/index.js`; } - // these virtual modules which are public paths should have the virtual prefix - // otherwise the bundler complains about not being able to find them based - // on the fact that we have @sveltejs/kit/vite in our package.json exports list - if (id === 'virtual:@sveltejs/kit/vite/environment') { - return path.join(import.meta.dirname, 'dev/ssr-manifest.js'); - } - - if (id === '__sveltekit/dev-server-entry') { - return server_instrumentation_file - ? sveltekit_traced - : path.join(import.meta.dirname, 'dev/server.js'); - } - - if (id === 'virtual:@sveltejs/kit/vite/environment/server') { - return `\0${id}`; - } - - if (id.startsWith('__sveltekit/') && id !== '__sveltekit/dev-server-entry') { + if (id.startsWith('__sveltekit/')) { return `\0virtual:${id}`; } }, @@ -692,12 +877,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { exactRegex(env_dynamic_public), exactRegex(service_worker), exactRegex(sveltekit_environment), - exactRegex(sveltekit_server), - exactRegex(sveltekit_manifest_data), - exactRegex(sveltekit_server_assets), - exactRegex(sveltekit_remotes), - exactRegex(sveltekit_traced), - exactRegex(sveltekit_dev_server) + exactRegex(sveltekit_server) ] }, handler(id) { @@ -735,157 +915,36 @@ function kit({ svelte_config, adapter_in_vite_config }) { return create_service_worker_module(svelte_config); case sveltekit_environment: { - const { version } = svelte_config.kit; + const { version } = kit; return dedent` - export const version = ${s(version.name)}; - export let building = false; - export let prerendering = false; + export const version = ${s(version.name)}; + export let building = false; + export let prerendering = false; - export function set_building() { - building = true; - } - - export function set_prerendering() { - prerendering = true; - } - `; - } - - case sveltekit_server: { - return dedent` - export let read_implementation = null; - - export let manifest = null; - - export function set_read_implementation(fn) { - read_implementation = fn; - } - - export function set_manifest(_) { - manifest = _; - } - `; - } - - case sveltekit_server_assets: { - if (!dev_environment) return; - - return dedent` - export const server_assets = { - ${Object.entries(server_assets) - .map(([filepath, size]) => `${s(filepath)}: ${size}`) - .join(',\n')} - }; - - import.meta.hot?.on('sveltekit:server-assets', (data) => { - Object.assign(server_assets, data); - }); - `; - } - - case sveltekit_remotes: { - if (!dev_environment) return; - - return dedent` - export const remotes = ${s(remotes)}; - - import.meta.hot?.on('sveltekit:remotes', (data) => { - remotes.push(data); - }); - `; - } - - case sveltekit_manifest_data: { - if (!dev_environment) return; - - const { manifest_data, env } = dev_environment; - - return dedent` - export const env = ${s(env)}; - - export const manifest_data = { - routes: ${devalue.uneval(manifest_data.routes)}, - nodes: ${devalue.uneval(manifest_data.nodes, revive_functions)}, - matchers: ${s(Object.entries(manifest_data.matchers))}, - assets: ${s(manifest_data.assets)} - }; - - export const mime_types = ${s(get_mime_lookup(manifest_data))}; + export function set_building() { + building = true; + } - export const kit = { - appDir: ${s(kit.appDir)}, - outDir: ${s(kit.outDir)}, - router: { - resolution: ${s(kit.router.resolution)}, - }, - paths: ${s(kit.paths)} + export function set_prerendering() { + prerendering = true; } `; } - case sveltekit_dev_server: { - if (!dev_environment) return; - - const runtime_base = get_runtime_base(root); - const adapter = svelte_config.kit.adapter; - + case sveltekit_server: { return dedent` - import { AsyncLocalStorage } from 'node:async_hooks'; - import { set_assets } from '${runtime_base}/app/paths/internal/server.js'; - // TODO: do we need to import Server before set_assets? see https://github.com/sveltejs/kit/commit/26d1958b9ee08541d719b77c6853a56142808ebc#diff-24f4a64dcf3021ab46c64bd6eddd314d4c630bde4557cc2e714f9c1f8b57ad06R441 - import { Server as InternalServer } from '${runtime_base}/server/index.js'; - import { check_feature } from '${runtime_base}/../utils/features.js'; - import { SCHEME } from '${runtime_base}/../utils/url.js'; - - const async_local_storage = new AsyncLocalStorage(); - - const adapter = ${adapter ? devalue.uneval({ name: adapter.name, supports: adapter.supports }, revive_functions) : null}; - - globalThis.__SVELTEKIT_TRACK__ = (label) => { - const context = async_local_storage.getStore(); - if (!context || context.prerender === true) return; + export let read_implementation = null; - check_feature(context.event.route.id, context.config, label, 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 let manifest = null; - set_assets(${s(dev_environment.assets)}); - - export class Server extends InternalServer { - 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); - }; - return super.respond(request, options); - } + export function set_read_implementation(fn) { + read_implementation = fn; } - `; - } - case sveltekit_traced: { - if (!server_instrumentation_file) - throw new Error('Server instrumentation file not found. This should never happen'); - - return dedent` - import '${posixify(server_instrumentation_file)}'; - - const { respond } = await import('${import.meta.resolve('./dev/server.js')}'); - export { respond }; - - import.meta.hot?.accept(); + export function set_manifest(_) { + manifest = _; + } `; } } @@ -1001,7 +1060,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { chain.push((current = candidates[0])); - includes_remote_file ||= svelte_config.kit.moduleExtensions.some((ext) => { + includes_remote_file ||= kit.moduleExtensions.some((ext) => { return current.endsWith(`.remote${ext}`); }); @@ -1087,7 +1146,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { } const normalized = normalize_id(id, normalized_lib, normalized_cwd); - if (!svelte_config.kit.moduleExtensions.some((ext) => normalized.endsWith(`.remote${ext}`))) { + if (!kit.moduleExtensions.some((ext) => normalized.endsWith(`.remote${ext}`))) { return; } @@ -1174,16 +1233,22 @@ function kit({ svelte_config, adapter_in_vite_config }) { // being called again with `opts.ssr === true` if the module isn't // already loaded) so we can determine what it exports if (dev_environment?.vite) { + /** @type {PromiseWithResolvers>} */ const { promise, resolve } = Promise.withResolvers(); const event = `sveltekit:remote-${remote.hash}-response`; - dev_environment.vite.environments.ssr.hot.on(event, resolve); + /** @param {Record} payload */ + const listener = (payload) => { + resolve(payload); + dev_environment?.vite.environments.ssr.hot.off(event, listener); + }; + + dev_environment.vite.environments.ssr.hot.on(event, listener); await dev_environment.vite.environments.ssr.transformRequest(id); dev_environment.vite.environments.ssr.hot.send(`sveltekit:remote-${remote.hash}-request`); const exports = await promise; - dev_environment.vite.environments.ssr.hot.off(event, resolve); for (const [name, value] of Object.entries(exports)) { const type = value.type; @@ -1224,7 +1289,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { } }; - /** @type {import('vite').Manifest} */ + /** @type {Manifest} */ let client_manifest; /** @type {import('types').Prerendered} */ let prerendered; @@ -1236,7 +1301,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { /** * Creates the service worker virtual modules - * @type {import('vite').Plugin} + * @type {Plugin} */ const plugin_service_worker = { name: 'vite-plugin-sveltekit-service-worker', @@ -1310,12 +1375,10 @@ function kit({ svelte_config, adapter_in_vite_config }) { } }; - /** @type {(payload: { urls: string[]; node: number; }) => Promise} */ - let handle_inline_styles; - /** @type {(error: Error) => void} */ - let handle_ssr_load_module; + /** @type {() => Promise} */ + let finalise; - /** @type {import('vite').Plugin} */ + /** @type {Plugin} */ const plugin_compile = { name: 'vite-plugin-sveltekit-compile', @@ -1401,7 +1464,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { /** @type {Record} */ const client_input = {}; - if (svelte_config.kit.output.bundleStrategy !== 'split') { + if (kit.output.bundleStrategy !== 'split') { client_input['bundle'] = `${runtime_directory}/client/bundle.js`; } else { client_input['entry/start'] = `${runtime_directory}/client/entry.js`; @@ -1414,7 +1477,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { }); } - const inline = svelte_config.kit.output.bundleStrategy === 'inline'; + const inline = kit.output.bundleStrategy === 'inline'; const config_base = assets_base(kit); @@ -1493,7 +1556,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { format: inline ? 'iife' : 'esm', entryFileNames: `${prefix}/[name].[hash].js`, chunkFileNames: `${prefix}/chunks/[hash].js`, - codeSplitting: svelte_config.kit.output.bundleStrategy === 'split' + codeSplitting: kit.output.bundleStrategy === 'split' }, // This silences Rolldown warnings about not supporting `import.meta` // for the `iife` output format. We don't care because it's @@ -1549,7 +1612,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { }; if (service_worker_entry_file) { - /** @type {Record} */ ( + /** @type {Record} */ ( new_config.environments ).serviceWorker = { build: { @@ -1598,47 +1661,12 @@ function kit({ svelte_config, adapter_in_vite_config }) { * @see https://vitejs.dev/guide/api-plugin.html#configureserver */ configureServer(vite) { - vite.environments.ssr.hot.off('sveltekit:inline-styles-request', handle_inline_styles); - vite.environments.ssr.hot.off('sveltekit:ssr-load-module', handle_ssr_load_module); - - manifest_data = sync.all(svelte_config, vite_config_env.mode, root).manifest_data; - - // other properties will be populated during the `dev` function + // other properties will be populated after running the `dev` function below dev_environment = /** @type {import('types').DevEnvironment} */ ({ - vite, - env: loadEnv(vite_config.mode, svelte_config.kit.env.dir, ''), - manifest_data + vite }); - handle_inline_styles ??= async ({ urls, node }) => { - vite.environments.ssr.hot.send( - `sveltekit:inline-styles-node-${node}-response`, - await get_inline_css(vite, urls) - ); - }; - vite.environments.ssr.hot.on('sveltekit:inline-styles-request', handle_inline_styles); - - handle_ssr_load_module ??= (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 }); - } - - vite.ws.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 ?? '' - } - }); - }; - vite.environments.ssr.hot.on('sveltekit:ssr-load-module', handle_ssr_load_module); + vite.environments.ssr.hot.on('sveltekit:ssr-load-module', display_ssr_error_on_client); return dev(vite, vite_config, svelte_config, root, dev_environment); }, @@ -1648,6 +1676,8 @@ function kit({ svelte_config, adapter_in_vite_config }) { * @see https://vitejs.dev/guide/api-plugin.html#configurepreviewserver */ configurePreviewServer(vite) { + if (kit.adapter?.vite?.plugins) return; + return preview(vite, vite_config, svelte_config); }, @@ -1686,20 +1716,21 @@ function kit({ svelte_config, adapter_in_vite_config }) { } mkdirp(out); - const server_bundle = /** @type {import('vite').Rolldown.RolldownOutput} */ ( + const server_bundle = /** @type {Rolldown.RolldownOutput} */ ( await builder.build(builder.environments.ssr) ); - const verbose = vite_config.logLevel === 'info'; + const verbose = builder.config.logLevel === 'info'; const log = logger({ verbose }); - /** @type {import('vite').Manifest} */ + /** @type {Manifest} */ const server_manifest = JSON.parse(read(`${out}/server/.vite/manifest.json`)); /** @type {import('types').BuildData} */ 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? @@ -1723,15 +1754,12 @@ function kit({ svelte_config, adapter_in_vite_config }) { log.info('Analysing routes'); const { metadata } = await analyse({ - hash: kit.router.type === 'hash', + svelte_config, manifest_path, manifest_data, server_manifest, tracked_features, - env: { ...env.private, ...env.public }, out, - output_config: svelte_config.output, - remotes, root }); @@ -1762,7 +1790,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { s(has_universal_load); } - const { output: client_chunks } = /** @type {import('vite').Rolldown.RolldownOutput} */ ( + const { output: client_chunks } = /** @type {Rolldown.RolldownOutput} */ ( await builder.build(builder.environments.client) ); @@ -1805,7 +1833,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { } } - /** @type {import('vite').Manifest} */ + /** @type {Manifest} */ client_manifest = JSON.parse(read(`${out}/client/.vite/manifest.json`)); /** @@ -1815,7 +1843,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { const deps_of = (entry, add_dynamic_css = false) => find_deps(client_manifest, posixify(path.relative(root, entry)), add_dynamic_css, root); - if (svelte_config.kit.output.bundleStrategy === 'split') { + if (kit.output.bundleStrategy === 'split') { const start = deps_of(`${runtime_directory}/client/entry.js`); const app = deps_of(`${kit.outDir}/generated/client-optimized/app.js`); @@ -1833,7 +1861,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { // In case of server-side route resolution, we create a purpose-built route manifest that is // similar to that on the client, with as much information computed upfront so that we // don't need to include any code of the actual routes in the server bundle. - if (svelte_config.kit.router.resolution === 'server') { + if (kit.router.resolution === 'server') { const nodes = manifest_data.nodes.map((node, i) => { if (node.component || node.universal) { const entry = `${kit.outDir}/generated/client-optimized/nodes/${i}.js`; @@ -1880,8 +1908,8 @@ function kit({ svelte_config, adapter_in_vite_config }) { ) }; - if (svelte_config.kit.output.bundleStrategy === 'inline') { - const style = /** @type {import('vite').Rolldown.OutputAsset} */ ( + if (kit.output.bundleStrategy === 'inline') { + const style = /** @type {Rolldown.OutputAsset} */ ( client_chunks.find( (chunk) => chunk.type === 'asset' && chunk.names.length === 1 && chunk.names[0] === 'style.css' @@ -1912,26 +1940,26 @@ function kit({ svelte_config, adapter_in_vite_config }) { ); // regenerate nodes with the client manifest... - build_server_nodes( + build_server_nodes({ out, - kit, manifest_data, server_manifest, client_manifest, + paths: kit.paths, + inline_style_threshold: kit.inlineStyleThreshold, assets_path, client_chunks, - svelte_config.kit.output, + output_config: kit.output, root - ); + }); // ...and prerender const prerender_results = await prerender({ - hash: kit.router.type === 'hash', + svelte_config, out, manifest_path, metadata, verbose, - env: { ...env.private, ...env.public }, root }); prerendered = prerender_results.prerendered; @@ -1943,7 +1971,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { metadata, cwd, server_bundle, - vite_config.build.sourcemap + builder.config.build.sourcemap ); // generate a new manifest that doesn't include prerendered pages @@ -1961,63 +1989,88 @@ function kit({ svelte_config, adapter_in_vite_config }) { })};\n` ); - if (service_worker_entry_file) { - if (kit.paths.assets) { - throw new Error('Cannot use service worker alongside config.kit.paths.assets'); - } + // defer the adapt step to run after any buildApp hooks the adapter might have + finalise = async () => { + // defer creating the service worker too because other plugins might build + // the client environment again and overwrite our service worker which + // outputs to the same directory + if (service_worker_entry_file) { + if (kit.paths.assets) { + throw new Error('Cannot use service worker alongside config.kit.paths.assets'); + } - log.info('Building service worker'); + log.info('Building service worker'); - builder.environments.serviceWorker.config.define = - builder.environments.client.config.define; - builder.environments.serviceWorker.config.resolve.alias = [ - ...get_config_aliases(kit, vite_config.root) - ]; - builder.environments.serviceWorker.config.experimental.renderBuiltUrl = (filename) => { - return { - runtime: `new URL(${JSON.stringify(filename)}, location.href).pathname` + builder.environments.serviceWorker.config.define = + builder.environments.client.config.define; + builder.environments.serviceWorker.config.resolve.alias = [ + ...get_config_aliases(kit, builder.config.root) + ]; + builder.environments.serviceWorker.config.experimental.renderBuiltUrl = (filename) => { + return { + runtime: `new URL(${JSON.stringify(filename)}, location.href).pathname` + }; }; - }; - await builder.build(builder.environments.serviceWorker); - } - - console.log( - `\nRun ${styleText(['bold', 'cyan'], 'npm run preview')} to preview your production build locally.` - ); - - if (kit.adapter) { - const { adapt } = await import('../../core/adapt/index.js'); - await adapt( - svelte_config, - build_data, - metadata, - prerendered, - prerender_results.prerender_map, - log, - remotes, - vite_config - ); - } else { - console.log(styleText(['bold', 'yellow'], '\nNo adapter specified')); + await builder.build(builder.environments.serviceWorker); + } - const link = styleText(['bold', 'cyan'], 'https://svelte.dev/docs/kit/adapters'); console.log( - `See ${link} to learn how to configure your app to run on the platform of your choosing` + `\nRun ${styleText(['bold', 'cyan'], 'npm run preview')} to preview your production build locally.` ); + + if (kit.adapter) { + const { adapt } = await import('../../core/adapt/index.js'); + await adapt( + svelte_config, + build_data, + metadata, + prerendered, + prerender_results.prerender_map, + log, + remotes, + builder.config, + out + ); + } else { + console.log(styleText(['bold', 'yellow'], '\nNo adapter specified')); + + const link = styleText(['bold', 'cyan'], 'https://svelte.dev/docs/kit/adapters'); + console.log( + `See ${link} to learn how to configure your app to run on the platform of your choosing` + ); + } + }; + } + }; + + /** @type {Plugin} */ + const plugin_adapter = { + name: 'vite-plugin-sveltekit-adapter', + apply: 'build', + buildApp: { + // this will run after any buildApp hooks provided by other Vite plugins + // see https://vite.dev/guide/api-environment-frameworks#environments-during-build + order: 'post', + async handler() { + await finalise(); } } }; return [ plugin_setup, + adapter_in_vite_config?.vite?.plugins ? undefined : plugin_node_environment, plugin_remote, plugin_server_filesystem, + plugin_dev_ssr, plugin_virtual_modules, process.env.TEST !== 'true' ? plugin_guard : undefined, plugin_service_worker, - plugin_compile - ].filter((p) => !!p); + plugin_compile, + plugin_adapter, + adapter_in_vite_config?.vite?.plugins + ].filter(Boolean); } /** @@ -2086,3 +2139,26 @@ function create_service_worker_module(config) { export const version = ${s(config.kit.version.name)}; `; } + +/** @type {(error: Error) => void} */ +function display_ssr_error_on_client(err) { + const vite = dev_environment?.vite; + if (!vite) return; + + const msg = buildErrorMessage(err, [styleText('red', `Internal server error: ${err.message}`)]); + + if (!vite.config.logger.hasErrorLogged(err)) { + vite.config.logger.error(msg, { error: err }); + } + + vite.ws.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 ?? '' + } + }); +} diff --git a/packages/kit/src/exports/vite/module_ids.js b/packages/kit/src/exports/vite/module_ids.js index 63e5ac52c695..758dc2297178 100644 --- a/packages/kit/src/exports/vite/module_ids.js +++ b/packages/kit/src/exports/vite/module_ids.js @@ -14,7 +14,8 @@ export const sveltekit_manifest_data = '\0virtual:__sveltekit/manifest-data'; export const sveltekit_server_assets = '\0virtual:__sveltekit/server-assets'; export const sveltekit_remotes = '\0virtual:__sveltekit/remotes'; export const sveltekit_traced = '\0virtual:__sveltekit/traced'; -export const sveltekit_dev_server = '\0virtual:@sveltejs/kit/vite/environment/server'; +export const sveltekit_ipc = '\0virtual:__sveltekit/ipc'; +export const sveltekit_env = '\0virtual:sveltekit:env'; export const app_server = posixify( fileURLToPath(new URL('../../runtime/app/server/index.js', import.meta.url)) diff --git a/packages/kit/src/exports/vite/types.d.ts b/packages/kit/src/exports/vite/types.d.ts index 900aa43541ea..c92ea45d0c95 100644 --- a/packages/kit/src/exports/vite/types.d.ts +++ b/packages/kit/src/exports/vite/types.d.ts @@ -1,3 +1,37 @@ +import 'vite/types/customEvent.d.ts'; + +declare module 'vite/types/customEvent.d.ts' { + interface CustomEventMap { + 'sveltekit:port': number; + 'sveltekit:remotes': { + hash: string; + file: string; + }; + 'sveltekit:server-assets': { + filepath: string; + size: number; + data: string; + }; + + 'sveltekit:ssr-load-module': Error; + 'sveltekit:prerender-assets-update': string; + 'sveltekit:prerender-dependencies': Record< + string, + { + response: SerialisedResponse; + body: null | string | Uint8Array; + } + >; + } +} + +export interface SerialisedResponse { + status: number; + statusText: string; + headers: Record; + body: ArrayBuffer; +} + export interface EnforcedConfig { [key: string]: EnforcedConfig | true; } diff --git a/packages/kit/src/exports/vite/utils.js b/packages/kit/src/exports/vite/utils.js index 3c0cb6c0bf62..e2bbcc878856 100644 --- a/packages/kit/src/exports/vite/utils.js +++ b/packages/kit/src/exports/vite/utils.js @@ -3,7 +3,7 @@ import { loadEnv } from 'vite'; import { posixify } from '../../utils/os.js'; import { negotiate } from '../../utils/http.js'; import { filter_env } from '../../utils/env.js'; -import { escape_html } from '../../utils/escape.js'; +import { escape_for_regexp, escape_html } from '../../utils/escape.js'; import { dedent } from '../../core/sync/utils.js'; import { app_server, @@ -55,13 +55,6 @@ export function get_config_aliases(config, root) { return alias; } -/** - * @param {string} str - */ -function escape_for_regexp(str) { - return str.replace(/[.*+?^${}()|[\]\\]/g, (match) => '\\' + match); -} - /** * Load environment variables from process.env and .env files * @param {import('types').ValidatedKitConfig['env']} env_config diff --git a/packages/kit/src/runtime/server/ambient.d.ts b/packages/kit/src/runtime/server/ambient.d.ts index 2f38261123dc..09c2db316f26 100644 --- a/packages/kit/src/runtime/server/ambient.d.ts +++ b/packages/kit/src/runtime/server/ambient.d.ts @@ -1,4 +1,10 @@ declare module '__SERVER__/internal.js' { export const options: import('types').SSROptions; export const get_hooks: () => Promise>; + export const set_manifest: (manifest: import('@sveltejs/kit').SSRManifest) => void; + export const set_read_implementation: (read: (file: string) => ReadableStream) => void; + export const set_private_env: (env: Record) => void; + export const set_public_env: (env: Record) => void; + export const set_building: () => void; + export const set_prerendering: () => void; } diff --git a/packages/kit/src/runtime/server/fetch.js b/packages/kit/src/runtime/server/fetch.js index baf93f1b507f..b010dd7589b7 100644 --- a/packages/kit/src/runtime/server/fetch.js +++ b/packages/kit/src/runtime/server/fetch.js @@ -99,7 +99,7 @@ export function create_fetch({ event, options, manifest, state, get_cookie_heade ? manifest.mimeTypes[filename.slice(filename.lastIndexOf('.'))] : 'text/html'; - return new Response(state.read(file), { + return new Response(await state.read(file), { headers: type ? { 'content-type': type } : {} }); } else if (read_implementation && file in manifest._.server_assets) { diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js index 5038b7395d18..5778f341930e 100644 --- a/packages/kit/src/runtime/server/index.js +++ b/packages/kit/src/runtime/server/index.js @@ -8,6 +8,7 @@ import { filter_env } from '../../utils/env.js'; import { format_server_error } from './utils.js'; import { set_read_implementation, set_manifest } from '__sveltekit/server'; import { set_app } from './app.js'; +import { create_synchronous_read } from './read.js'; /** @type {Promise} */ let init_promise; @@ -65,42 +66,7 @@ export class Server { set_public_env(filter_env(env, env_public_prefix, env_private_prefix)); if (read) { - // Wrap the read function to handle MaybePromise - // and ensure the public API stays synchronous - /** @param {string} file */ - const wrapped_read = (file) => { - const result = read(file); - if (result instanceof ReadableStream) { - return result; - } else { - return new ReadableStream({ - async start(controller) { - try { - const stream = await Promise.resolve(result); - if (!stream) { - controller.close(); - return; - } - - const reader = stream.getReader(); - - while (true) { - const { done, value } = await reader.read(); - if (done) break; - controller.enqueue(value); - } - - controller.close(); - } catch (error) { - // TODO: it only throws if the user tries to read the body. Otherwise, the response is a 200 - controller.error(error); - } - } - }); - } - }; - - set_read_implementation(wrapped_read); + set_read_implementation(create_synchronous_read(read)); } // During DEV and for some adapters this function might be called in quick succession, diff --git a/packages/kit/src/runtime/server/read.js b/packages/kit/src/runtime/server/read.js new file mode 100644 index 000000000000..31801e0a80f6 --- /dev/null +++ b/packages/kit/src/runtime/server/read.js @@ -0,0 +1,39 @@ +/** + * Wrap the `read` function to handle `MaybePromise` + * and ensure the public API stays synchronous + * @param {NonNullable} read + * @returns {(path: string) => ReadableStream} + */ +export function create_synchronous_read(read) { + return (file) => { + const result = read(file); + if (result instanceof ReadableStream) { + return result; + } else { + return new ReadableStream({ + async start(controller) { + try { + const stream = await Promise.resolve(result); + if (!stream) { + controller.close(); + return; + } + + const reader = stream.getReader(); + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + controller.enqueue(value); + } + + controller.close(); + } catch (error) { + // TODO: we should throw here even if the user doesn't try to read the response body + controller.error(error); + } + } + }); + } + }; +} diff --git a/packages/kit/src/runtime/utils.js b/packages/kit/src/runtime/utils.js index b6e3103ff1ee..736a9df9c057 100644 --- a/packages/kit/src/runtime/utils.js +++ b/packages/kit/src/runtime/utils.js @@ -1,4 +1,17 @@ import { BROWSER } from 'esm-env'; +import { posixify } from '../utils/os.js'; + +/** + * Resolved path of the `runtime` directory + * + * TODO Windows issue: + * Vite or sth else somehow sets the driver letter inconsistently to lower or upper case depending on the run environment. + * In playwright debug mode run through VS Code this a root-to-lowercase conversion is needed in order for the tests to run. + * If we do this conversion in other cases it has the opposite effect though and fails. + */ +// import.meta.dirname doesn't exist on the client so we need to avoid running +// posixify to avoid a runtime error when it's undefined +export const runtime_directory = import.meta.dirname ? posixify(import.meta.dirname) : ''; export const text_encoder = new TextEncoder(); export const text_decoder = new TextDecoder(); diff --git a/packages/kit/src/types/ambient-private.d.ts b/packages/kit/src/types/ambient-private.d.ts index a67b25fc87e5..753248b1d785 100644 --- a/packages/kit/src/types/ambient-private.d.ts +++ b/packages/kit/src/types/ambient-private.d.ts @@ -28,36 +28,26 @@ declare module '__sveltekit/server' { export function set_read_implementation(fn: (path: string) => ReadableStream): void; } +/** Used to construct the SSR manifest in development from a Node-agnostic environment */ declare module '__sveltekit/manifest-data' { - // eslint-disable-next-line no-duplicate-imports - import { Asset, PageNode, RouteData } from 'types'; + import { ManifestData } from 'types'; export const env: Record; - export const kit: { - appDir: string; - outDir: string; - router: { - resolution: 'client' | 'server'; - }; - paths: { - assets: string; - base: string; - relative: boolean; - }; - }; - export const mime_types: string[]; - export const manifest_data: { - routes: RouteData[]; - nodes: PageNode[]; - matchers: string[][]; - assets: Asset[]; - }; + export const mime_types: Record; + export const manifest_data: ManifestData; } +/** Used to read the filesystem during development from an environment without `node:fs` */ declare module '__sveltekit/server-assets' { export const server_assets: Record; + export const server_assets_content: Record>; } +/** Used to identify remote functions processed by Vite from any environment */ declare module '__sveltekit/remotes' { export const remotes: Array<{ hash: string; file: string }>; } + +declare module '__sveltekit/ipc' { + export function get(pathname: string): Promise; +} diff --git a/packages/kit/src/types/ambient.d.ts b/packages/kit/src/types/ambient.d.ts index 62344d30b1a9..896c91c4b4af 100644 --- a/packages/kit/src/types/ambient.d.ts +++ b/packages/kit/src/types/ambient.d.ts @@ -31,6 +31,7 @@ declare namespace App { /** * The interface that defines `event.locals`, which can be accessed in server [hooks](https://svelte.dev/docs/kit/hooks) (`handle`, and `handleError`), server-only `load` functions, and `+server.js` files. */ + // eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface Locals {} /** @@ -38,16 +39,19 @@ declare namespace App { * The `Load` and `ServerLoad` functions in `./$types` will be narrowed accordingly. * Use optional properties for data that is only present on specific pages. Do not add an index signature (`[key: string]: any`). */ + // eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface PageData {} /** * The shape of the `page.state` object, which can be manipulated using the [`pushState`](https://svelte.dev/docs/kit/$app-navigation#pushState) and [`replaceState`](https://svelte.dev/docs/kit/$app-navigation#replaceState) functions from `$app/navigation`. */ + // eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface PageState {} /** * If your adapter provides [platform-specific context](https://svelte.dev/docs/kit/adapters#Platform-specific-context) via `event.platform`, you can specify it here. */ + // eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface Platform {} } @@ -147,13 +151,55 @@ declare module '$app/types' { export type Asset = ReturnType; } -declare module 'virtual:@sveltejs/kit/vite/environment' { +/** + * 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; - export const env: Record; } -declare module 'virtual:@sveltejs/kit/vite/environment/server' { - export { Server } from '@sveltejs/kit'; +/** + * 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 fbaa98d16d09..b4004125a410 100644 --- a/packages/kit/src/types/global-private.d.ts +++ b/packages/kit/src/types/global-private.d.ts @@ -44,6 +44,7 @@ declare global { }; /** Allows us to resolve paths relative to the Vite root setting during development */ const __SVELTEKIT_ROOT__: string; + const __SVELTEKIT_OUT_DIR__: string; /** * This makes the use of specific features visible at both dev and build time, in such a * way that we can error when they are not supported by the target platform. diff --git a/packages/kit/src/types/internal.d.ts b/packages/kit/src/types/internal.d.ts index 87b5b3fe5f08..0c99719a5899 100644 --- a/packages/kit/src/types/internal.d.ts +++ b/packages/kit/src/types/internal.d.ts @@ -68,6 +68,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; @@ -176,11 +177,9 @@ export class InternalServer extends Server { request: Request, options: RequestOptions & { prerendering?: PrerenderOptions; - /** - * Used internally for saving dependencies during prerendering and generating fallback pages. - */ - read: (file: string) => NonSharedBuffer; - /** A hook called before `handle` during dev, so that `AsyncLocalStorage` can be populated. */ + /** @internal for saving dependencies during prerendering and generating fallback pages */ + read: (file: string) => MaybePromise>; + /** @internal used during development to check feature availability depending on the current route */ before_handle?: ( event: RequestEvent, config: any, @@ -404,6 +403,7 @@ export interface ServerMetadata { routes: Map; /** For each hashed remote file, a map of export name -> { type, dynamic }, where `dynamic` is `false` for non-dynamic prerender functions */ remotes: Map>; + remotes_with_prerender: Set; } export interface SSRComponent { @@ -559,9 +559,9 @@ export interface SSRState { */ prerender_default?: PrerenderOption; /** - * Used internally for saving dependencies during prerendering and generating fallback pages. + * @internal reads from the filesystem when user code tries to fetch a static asset */ - read?: (file: string) => NonSharedBuffer; + read?: (file: string) => MaybePromise>; /** * Used to set up `__SVELTEKIT_TRACK__` which checks if a used feature is supported. * E.g. if `read` from `$app/server` is used, it checks whether the route's config is compatible. @@ -692,10 +692,8 @@ export interface RequestStore { export interface DevEnvironment { vite: ViteDevServer; + /** used to construct the SSR manifest */ manifest_data: ManifestData; - env: Record; - assets: string; - remote_address: string | undefined; } export * from '../exports/index.js'; diff --git a/packages/kit/src/utils/escape.js b/packages/kit/src/utils/escape.js index 3feaafae6fc8..bf3b660b21a6 100644 --- a/packages/kit/src/utils/escape.js +++ b/packages/kit/src/utils/escape.js @@ -81,3 +81,11 @@ export function escape_for_interpolation(str, replacements) { } return escaped; } + +// TODO: replace this with RegExp.escape when we make Node 24 the minimum +/** + * @param {string} str + */ +export function escape_for_regexp(str) { + return str.replace(/[.*+?^${}()|[\]\\]/g, (match) => '\\' + match); +} diff --git a/packages/kit/src/utils/features.js b/packages/kit/src/utils/features.js index 7ca2f2abe679..3662a3e05dc9 100644 --- a/packages/kit/src/utils/features.js +++ b/packages/kit/src/utils/features.js @@ -1,26 +1,22 @@ -/** @import { Adapter } from '@sveltejs/kit'; */ +import { get } from '__sveltekit/ipc'; /** * @param {string} route_id * @param {unknown} config * @param {string} feature - * @param {Pick | undefined} adapter */ -export function check_feature(route_id, config, feature, adapter) { - if (!adapter) return; - - switch (feature) { - case '$app/server:read': { - const supported = adapter.supports?.read?.({ - route: { id: route_id }, - config - }); +export async function check_feature(route_id, config, feature) { + const response = await get( + `/check-feature?${new URLSearchParams({ route_id, config: JSON.stringify(config), feature })}` + ); + if (!response.ok) { + throw new Error( + `Failed to check feature ${feature} for route ${route_id}: ${response.status} ${response.statusText}. This should never happen` + ); + } - if (!supported) { - throw new Error( - `Cannot use \`read\` from \`$app/server\` in ${route_id} when using ${adapter.name}. Please ensure that your adapter is up to date and supports this feature.` - ); - } - } + const error_message = await response.text(); + if (error_message) { + throw new Error(error_message); } } diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 325ba1dcd600..4b399ed80e77 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -82,6 +82,12 @@ declare module '@sveltejs/kit' { ? undefined // needs to be undefined, because void will corrupt union type : T; + export interface ManifestGenerationOptions { + /** A relative path to the base directory of the server build output */ + relativePath: string; + routes?: RouteDefinition[]; + } + /** * This object is passed to the `adapt` function of adapters. * It contains various methods and properties that are useful for adapting the app. @@ -125,9 +131,8 @@ declare module '@sveltejs/kit' { /** * Generate a server-side manifest to initialise the SvelteKit [server](https://svelte.dev/docs/kit/@sveltejs-kit#Server) with. - * @param opts a relative path to the base directory of the app and optionally in which format (esm or cjs) the manifest should be generated */ - generateManifest: (opts: { relativePath: string; routes?: RouteDefinition[] }) => string; + generateManifest: (opts: ManifestGenerationOptions) => string; /** * Resolve a path to the `name` directory inside `outDir`, e.g. `/path/to/.svelte-kit/my-adapter`. @@ -287,17 +292,6 @@ declare module '@sveltejs/kit' { serialize: (name: string, value: string, opts: import('cookie').SerializeOptions) => string; } - /** - * A collection of functions that influence the environment during dev, build and prerendering - */ - export interface Emulator { - /** - * A function that is called with the current route `config` and `prerender` option - * and returns an `App.Platform` object - */ - platform?(details: { config: any; prerender: PrerenderOption }): MaybePromise; - } - export interface KitConfig { /** * Your [adapter](https://svelte.dev/docs/kit/adapters) is run when executing `vite build`. It determines how the output is converted for different platforms. @@ -1582,18 +1576,21 @@ declare module '@sveltejs/kit' { * Required to instantiate `Server` with project specific information */ export interface SSRManifest { + /** The directory where SvelteKit keeps its stuff, including static assets (such as JS and CSS) and internally-used routes. */ appDir: string; + /** The `base` and `appDir` settings combined without a leading slash. */ appPath: string; + base: string; /** Static files from `kit.config.files.assets` and the service worker (if any). */ assets: Set; mimeTypes: Record; - /** private fields */ + /** @internal private fields */ _: { client: NonNullable; nodes: SSRNodeLoader[]; /** hashed filename -> import to that file */ - remotes: Record Promise>; + remotes: Record Promise<{ default: Record }>>; routes: SSRRoute[]; prerendered_routes: Set; matchers: () => Promise>; @@ -2430,6 +2427,7 @@ declare module '@sveltejs/kit' { interface BuildData { app_dir: string; app_path: string; + base: string; manifest_data: ManifestData; out_dir: string; service_worker: string | null; @@ -2900,14 +2898,13 @@ declare module '@sveltejs/kit/node' { } declare module '@sveltejs/kit/vite' { - import type { Adapter } from '@sveltejs/kit'; import type { PluginOption } from 'vite'; /** * Returns the SvelteKit Vite plugins. * */ export function sveltekit(options?: { - adapter?: Adapter; - } | undefined): Promise; + adapter?: import("@sveltejs/kit").Adapter; + } | undefined): Promise; export {}; } @@ -3536,6 +3533,7 @@ declare namespace App { /** * The interface that defines `event.locals`, which can be accessed in server [hooks](https://svelte.dev/docs/kit/hooks) (`handle`, and `handleError`), server-only `load` functions, and `+server.js` files. */ + // eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface Locals {} /** @@ -3543,16 +3541,19 @@ declare namespace App { * The `Load` and `ServerLoad` functions in `./$types` will be narrowed accordingly. * Use optional properties for data that is only present on specific pages. Do not add an index signature (`[key: string]: any`). */ + // eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface PageData {} /** * The shape of the `page.state` object, which can be manipulated using the [`pushState`](https://svelte.dev/docs/kit/$app-navigation#pushState) and [`replaceState`](https://svelte.dev/docs/kit/$app-navigation#replaceState) functions from `$app/navigation`. */ + // eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface PageState {} /** * If your adapter provides [platform-specific context](https://svelte.dev/docs/kit/adapters#Platform-specific-context) via `event.platform`, you can specify it here. */ + // eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface Platform {} } @@ -3652,15 +3653,57 @@ declare module '$app/types' { export type Asset = ReturnType; } -declare module 'virtual:@sveltejs/kit/vite/environment' { +/** + * 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; - export const env: Record; } -declare module 'virtual:@sveltejs/kit/vite/environment/server' { - export { Server } from '@sveltejs/kit'; +/** + * 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 200b919b7c765fd96e4a5226b3147d1aa7386cb7 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Fri, 24 Apr 2026 02:45:30 +0800 Subject: [PATCH 088/117] delete --- .../kit/src/exports/vite/dev/ssr-manifest.js | 209 ------------------ packages/kit/src/utils/fork.js | 70 ------ 2 files changed, 279 deletions(-) delete mode 100644 packages/kit/src/exports/vite/dev/ssr-manifest.js delete mode 100644 packages/kit/src/utils/fork.js 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 1e5be0f3176c..000000000000 --- a/packages/kit/src/exports/vite/dev/ssr-manifest.js +++ /dev/null @@ -1,209 +0,0 @@ -import { server_assets } from '__sveltekit/server-assets'; -import { remotes } from '__sveltekit/remotes'; -import { env, kit, manifest_data, mime_types } from '__sveltekit/manifest-data'; -import { to_fs } from '../filesystem.js'; -import { compact } from '../../../utils/array.js'; -import { join } from '../../../utils/path.js'; - -export { env }; - -export const basePath = kit.paths.base; - -export const manifest = { - appDir: kit.appDir, - appPath: kit.appDir, - assets: new Set(manifest_data.assets.map((asset) => asset.file)), - mimeTypes: mime_types, - _: { - client: { - // we can't use `runtime_directory` here because that module has imports to node:* modules - // and that doesn't work in non-Node environments - start: to_fs( - join(__SVELTEKIT_ROOT__, 'node_modules/@sveltejs/kit/src/runtime/client/entry.js') - ), - app: `${to_fs(kit.outDir)}/generated/client/app.js`, - imports: [], - stylesheets: [], - fonts: [], - uses_env_dynamic_public: true, - nodes: - kit.router.resolution === 'client' - ? undefined - : manifest_data.nodes.map((node, i) => { - if (node.component || node.universal) { - return `${kit.paths.base}${to_fs(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: - 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, - 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 {string[]} */ - const urls = []; - - if (node.component) { - result.component = async () => { - const { module, url } = await resolve( - join(__SVELTEKIT_ROOT__, /** @type {string} */ (node.component)) - ); - urls.push(url); - 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, url } = await resolve(join(__SVELTEKIT_ROOT__, node.universal)); - urls.push(url); - 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 - - const event = `sveltekit:inline-styles-node-${i}-response`; - result.inline_styles = async () => { - if (!import.meta.hot) throw new Error('hmr must be enabled in the dev environment'); - - const { promise, resolve } = Promise.withResolvers(); - - /** @param {Record} styles */ - const listener = async (styles) => { - import.meta.hot?.off(event, listener); - const importing_styles = Object.entries(styles).map( - async ([dep_url, inline_css_url]) => { - return [ - dep_url, - await import(/* @vite-ignore */ inline_css_url).then((mod) => mod.default) - ]; - } - ); - resolve(Object.fromEntries(await Promise.all(importing_styles))); - }; - - import.meta.hot.on(event, listener); - import.meta.hot.send('sveltekit:inline-styles-request', { - urls, - node: result.index - }); - - return promise; - }; - - return result; - }; - }), - prerendered_routes: new Set(), - get remotes() { - return Object.fromEntries( - remotes.map((remote) => [ - remote.hash, - () => - import(/* @vite-ignore */ join(__SVELTEKIT_ROOT__, 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 () => { - const importing_matchers = manifest_data.matchers.map(async ([name, file]) => { - const url = join(__SVELTEKIT_ROOT__, file); - const { module } = await resolve(url); - if (!module.match) { - throw new Error(`${file} does not export a \`match\` function`); - } - return [name, module.match]; - }); - return Object.fromEntries(await Promise.all(importing_matchers)); - } - } -}; - -/** @param {string} url */ -async function loud_ssr_load_module(url) { - try { - return await import(/* @vite-ignore */ url); - } catch (err) { - if (err instanceof Error) { - import.meta.hot?.send('sveltekit:ssr-load-module', { - ...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(id) : `file:///${id}`; - const module = await loud_ssr_load_module(url); - return { module, url }; -} diff --git a/packages/kit/src/utils/fork.js b/packages/kit/src/utils/fork.js deleted file mode 100644 index 918eded41e5f..000000000000 --- a/packages/kit/src/utils/fork.js +++ /dev/null @@ -1,70 +0,0 @@ -import { fileURLToPath } from 'node:url'; -import { Worker, parentPort } from 'node:worker_threads'; -import process from 'node:process'; - -/** - * Runs a task in a subprocess so any dangling stuff gets killed upon completion. - * The subprocess needs to be the file `forked` is called in, and `forked` needs to be called eagerly at the top level. - * @template T - * @template U - * @param {string} module `import.meta.url` of the file - * @param {(opts: T) => Promise} callback The function that is invoked in the subprocess - * @returns {(opts: T) => Promise} A function that when called starts the subprocess - */ -export function forked(module, callback) { - if (process.env.SVELTEKIT_FORK && parentPort) { - parentPort.on( - 'message', - /** @param {any} data */ async (data) => { - if (data?.type === 'args' && data.module === module) { - parentPort?.postMessage({ - type: 'result', - module, - payload: await callback(data.payload) - }); - } - } - ); - - parentPort.postMessage({ type: 'ready', module }); - } - - /** - * @param {T} opts - * @returns {Promise} - */ - return function (opts) { - return new Promise((fulfil, reject) => { - const worker = new Worker(fileURLToPath(module), { - env: { - ...process.env, - SVELTEKIT_FORK: 'true' - } - }); - - worker.on( - 'message', - /** @param {any} data */ (data) => { - if (data?.type === 'ready' && data.module === module) { - worker.postMessage({ - type: 'args', - module, - payload: opts - }); - } - - if (data?.type === 'result' && data.module === module) { - worker.unref(); - fulfil(data.payload); - } - } - ); - - worker.on('exit', (code) => { - if (code) { - reject(new Error(`Failed with code ${code}`)); - } - }); - }); - }; -} From ca4f597c849864a52e36003dba7f112545d2e831 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Fri, 24 Apr 2026 02:49:22 +0800 Subject: [PATCH 089/117] docs --- .../docs/25-build-and-deploy/99-writing-adapters.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 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 bfba617b8d82..4f6a23d4a7d0 100644 --- a/documentation/docs/25-build-and-deploy/99-writing-adapters.md +++ b/documentation/docs/25-build-and-deploy/99-writing-adapters.md @@ -72,11 +72,12 @@ config(userConfig) { } ``` -You can also create your own server entry file by importing `Server` from `virtual:@sveltejs/kit/vite/environment/server` and `env` and `manifest` from `virtual:@sveltejs/kit/vite/environment`. +You can also create your own development server entry file by importing `Server` from `sveltekit:server` and `env` and `manifest` from `sveltekit:server-manifest`. ```js -import { Server } from 'virtual:@sveltejs/kit/vite/environment/server'; -import { env, manifest } from 'virtual:@sveltejs/kit/vite/environment'; +import { env } from 'sveltekit:env'; +import { Server } from 'sveltekit:server'; +import { manifest } from 'sveltekit:server-manifest'; const server = new Server(manifest); From 3142fd5d2b12003d93e4801ef8ea63aa7f5a557b Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Fri, 24 Apr 2026 02:50:21 +0800 Subject: [PATCH 090/117] docs --- 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 4f6a23d4a7d0..62ed7acb7bf2 100644 --- a/documentation/docs/25-build-and-deploy/99-writing-adapters.md +++ b/documentation/docs/25-build-and-deploy/99-writing-adapters.md @@ -72,7 +72,7 @@ config(userConfig) { } ``` -You can also create your own development server entry file by importing `Server` from `sveltekit:server` and `env` and `manifest` from `sveltekit:server-manifest`. +You can also create your own development server entry file by importing `Server` from `sveltekit:server`, `env` from `sveltekit:env`, and `manifest` from `sveltekit:server-manifest`. ```js import { env } from 'sveltekit:env'; From 03b8bcb725b3171cfbce3e75fc41050909f2340e Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Fri, 24 Apr 2026 03:03:40 +0800 Subject: [PATCH 091/117] try to fix build error test --- packages/kit/test/build-errors/prerender.spec.js | 7 ++++++- packages/kit/test/build-errors/tsconfig.json | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/kit/test/build-errors/prerender.spec.js b/packages/kit/test/build-errors/prerender.spec.js index e788055bac8b..1e9b508a2eb5 100644 --- a/packages/kit/test/build-errors/prerender.spec.js +++ b/packages/kit/test/build-errors/prerender.spec.js @@ -2,6 +2,7 @@ import { assert, test } from 'vitest'; import { execSync } from 'node:child_process'; import path from 'node:path'; import { EOL } from 'node:os'; +import { escape_for_regexp } from '../../src/utils/escape.js'; const timeout = 60_000; @@ -25,7 +26,11 @@ test('entry generators should match their own route', { timeout }, () => { stdio: 'pipe', timeout }), - `Error: The entries export from /[slug]/[notSpecific] generated entry /whatever/specific, which was matched by /[slug]/specific - see the \`handleEntryGeneratorMismatch\` option in https://svelte.dev/docs/kit/configuration#prerender for more info.${EOL}To suppress or handle this error, implement \`handleEntryGeneratorMismatch\` in https://svelte.dev/docs/kit/configuration#prerender` + new RegExp( + escape_for_regexp( + `Error: The entries export from /[slug]/[notSpecific] generated entry /whatever/specific, which was matched by /[slug]/specific - see the \`handleEntryGeneratorMismatch\` option in https://svelte.dev/docs/kit/configuration#prerender for more info.${EOL}To suppress or handle this error, implement \`handleEntryGeneratorMismatch\` in https://svelte.dev/docs/kit/configuration#prerender` + ) + ) ); }); diff --git a/packages/kit/test/build-errors/tsconfig.json b/packages/kit/test/build-errors/tsconfig.json index eb9d304fd592..a0baa4e30c60 100644 --- a/packages/kit/test/build-errors/tsconfig.json +++ b/packages/kit/test/build-errors/tsconfig.json @@ -4,7 +4,8 @@ "checkJs": true, "noEmit": true, "target": "esnext", - "moduleResolution": "node" + "module": "nodenext", + "moduleResolution": "nodenext" }, "include": ["./*"] } From dda5926b09c6c1c860241908234293cf0a81795c Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Fri, 24 Apr 2026 04:15:58 +0800 Subject: [PATCH 092/117] fix static file access during dev --- .../kit/src/exports/vite/dev/ssr_entry.js | 2 +- packages/kit/src/exports/vite/index.js | 29 ++++++++++--------- packages/kit/src/types/global-private.d.ts | 1 + 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/packages/kit/src/exports/vite/dev/ssr_entry.js b/packages/kit/src/exports/vite/dev/ssr_entry.js index f20c0cd01014..b72347b43350 100644 --- a/packages/kit/src/exports/vite/dev/ssr_entry.js +++ b/packages/kit/src/exports/vite/dev/ssr_entry.js @@ -31,7 +31,7 @@ export async function respond(request, remote_address) { return fs.readFileSync(from_fs(file)); } - return fs.readFileSync(path.join(__SVELTEKIT_PATHS_ASSETS__, file)); + return fs.readFileSync(path.join(__SVELTEKIT_FILES_ASSETS__, file)); } }); } diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index d7b4ef8f951b..3c5c851732ba 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -504,7 +504,8 @@ function kit({ svelte_config, adapter_in_vite_config }) { __SVELTEKIT_APP_VERSION_POLL_INTERVAL__: '0', __SVELTEKIT_PAYLOAD__: 'globalThis.__sveltekit_dev', __SVELTEKIT_HAS_SERVER_LOAD__: 'true', - __SVELTEKIT_HAS_UNIVERSAL_LOAD__: 'true' + __SVELTEKIT_HAS_UNIVERSAL_LOAD__: 'true', + __SVELTEKIT_FILES_ASSETS__: s(kit.files.assets) }; } @@ -587,11 +588,13 @@ function kit({ svelte_config, adapter_in_vite_config }) { } }; - /** @type {Map} */ + /** @type {Map }>} */ let server_assets; /** - * Allows us to access the filesystem from an environment that doesn't have `node:fs` + * Allows us to access the filesystem synchronously from an environment that + * doesn't have `node:fs`. This is used to dynamically populate the server + * manifest's `server_assets` property. * @type {Plugin} */ const plugin_server_filesystem = { @@ -630,7 +633,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { }); // persist changes in case of server reload - server_assets.set(filepath, { size, data: devalue.uneval(data) }); + server_assets.set(filepath, { size, data }); invalidate_module(dev_environment.vite, sveltekit_server_assets); } } @@ -726,8 +729,12 @@ function kit({ svelte_config, adapter_in_vite_config }) { } case sveltekit_server_assets: { - /** @type {Array<[string, { size: number; data: string }]>} */ - const entries = Object.entries(server_assets); + /** @type {Array<[string, { size: number; data: Uint8Array }]>} */ + const entries = []; + + for (const asset of server_assets) { + entries.push(asset); + } return dedent` export const server_assets = { @@ -738,13 +745,9 @@ function kit({ svelte_config, adapter_in_vite_config }) { .join(',\n')} }; - export const server_assets_content = { - ${entries - .map(([filepath, { data }]) => { - return `${s(filepath)}: ${data}`; - }) - .join(',\n')} - }; + export const server_assets_content = ${devalue.uneval( + Object.fromEntries(entries.map(([filepath, { data }]) => [filepath, data])) + )}; let devalue; diff --git a/packages/kit/src/types/global-private.d.ts b/packages/kit/src/types/global-private.d.ts index b4004125a410..3458e62ab20f 100644 --- a/packages/kit/src/types/global-private.d.ts +++ b/packages/kit/src/types/global-private.d.ts @@ -45,6 +45,7 @@ declare global { /** Allows us to resolve paths relative to the Vite root setting during development */ const __SVELTEKIT_ROOT__: string; const __SVELTEKIT_OUT_DIR__: string; + const __SVELTEKIT_FILES_ASSETS__: string; /** * This makes the use of specific features visible at both dev and build time, in such a * way that we can error when they are not supported by the target platform. From 202661998953350f4a735fce79a941e30b5823ae Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Fri, 24 Apr 2026 14:47:08 +0800 Subject: [PATCH 093/117] fix prerender tests --- .../test/apps/prerendered/package.json | 2 +- packages/adapter-static/test/apps/spa/package.json | 2 +- packages/kit/src/core/postbuild/prerender_entry.js | 14 +++++++------- packages/kit/src/runtime/server/page/load_data.js | 5 ++++- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/adapter-static/test/apps/prerendered/package.json b/packages/adapter-static/test/apps/prerendered/package.json index 6ac8a9517693..e2b887afe266 100644 --- a/packages/adapter-static/test/apps/prerendered/package.json +++ b/packages/adapter-static/test/apps/prerendered/package.json @@ -1,5 +1,5 @@ { - "name": "~TODO~", + "name": "test-static-prerendered", "version": "0.0.1", "private": true, "scripts": { diff --git a/packages/adapter-static/test/apps/spa/package.json b/packages/adapter-static/test/apps/spa/package.json index 4d4d0a6f5123..7860e3591bd1 100644 --- a/packages/adapter-static/test/apps/spa/package.json +++ b/packages/adapter-static/test/apps/spa/package.json @@ -1,5 +1,5 @@ { - "name": "~TODO~", + "name": "test-static-spa", "version": "0.0.1", "private": true, "scripts": { diff --git a/packages/kit/src/core/postbuild/prerender_entry.js b/packages/kit/src/core/postbuild/prerender_entry.js index 25b72a9f440a..1113eb047a1d 100644 --- a/packages/kit/src/core/postbuild/prerender_entry.js +++ b/packages/kit/src/core/postbuild/prerender_entry.js @@ -67,15 +67,15 @@ export class Server extends KitServer { /** @type {Map} */ const dependencies = new Map(); - for (const [key, value] of options.prerendering.dependencies) { - dependencies.set(key, { + for (const [pathname, dependency] of options.prerendering.dependencies) { + dependencies.set(pathname, { response: { - status: value.response.status, - statusText: value.response.statusText, - headers: Object.fromEntries(value.response.headers), - body: await value.response.arrayBuffer() + status: dependency.response.status, + statusText: dependency.response.statusText, + headers: Object.fromEntries(dependency.response.headers), + body: await dependency.response.arrayBuffer() }, - body: value.body + body: dependency.body }); } diff --git a/packages/kit/src/runtime/server/page/load_data.js b/packages/kit/src/runtime/server/page/load_data.js index 57fe70428250..ab5892fdc38a 100644 --- a/packages/kit/src/runtime/server/page/load_data.js +++ b/packages/kit/src/runtime/server/page/load_data.js @@ -291,7 +291,10 @@ export function create_universal_fetch(event, state, fetched, csr, resolve_opts) if (same_origin) { if (state.prerendering) { - dependency = { response, body: null }; + // the prerender code needs to read and serialise the response body + // from the environment to the Vite process so we clone the response + // to ensure its body remains unused until then + dependency = { response: response.clone(), body: null }; state.prerendering.dependencies.set(url.pathname, dependency); } } else if (url.protocol === 'https:' || url.protocol === 'http:') { From e99e307ab632f87734e91e256297382d0a30e327 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Sat, 25 Apr 2026 16:33:30 +0800 Subject: [PATCH 094/117] include base path when fetching against vite dev server --- packages/kit/src/exports/vite/build/vite_server.js | 4 +++- packages/kit/src/exports/vite/dev/ssr_manifest.js | 2 +- packages/kit/src/exports/vite/index.js | 4 +++- packages/kit/src/types/internal.d.ts | 2 ++ 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/kit/src/exports/vite/build/vite_server.js b/packages/kit/src/exports/vite/build/vite_server.js index 889124f5aba3..91633ff4938e 100644 --- a/packages/kit/src/exports/vite/build/vite_server.js +++ b/packages/kit/src/exports/vite/build/vite_server.js @@ -45,6 +45,8 @@ export async function create_build_server({ /** @type {number | undefined} */ let port; + const app_path = `${svelte_config.kit.paths.base}/${svelte_config.kit.appDir}`; + /** * Allows us to perform Node operations from a non-Node environment by sending * a request to the Vite dev server. We can then configure a middleware to @@ -95,7 +97,7 @@ export async function create_build_server({ const native_fetch = globalThis.fetch; export function get(pathname) { - return native_fetch(\`http://localhost:\${port}/${svelte_config.kit.appDir}\${pathname}\`); + return native_fetch(\`http://localhost:\${port}${app_path}\${pathname}\`); } let port${port ? ` = ${port}` : ''}; diff --git a/packages/kit/src/exports/vite/dev/ssr_manifest.js b/packages/kit/src/exports/vite/dev/ssr_manifest.js index d509e02e4f99..9b94750a2476 100644 --- a/packages/kit/src/exports/vite/dev/ssr_manifest.js +++ b/packages/kit/src/exports/vite/dev/ssr_manifest.js @@ -11,7 +11,7 @@ import { runtime_directory } from '../../../runtime/utils.js'; /** @type {SSRManifest} */ export const manifest = { appDir: __SVELTEKIT_APP_DIR__, - appPath: __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, diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 3c5c851732ba..88fdce740122 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -715,12 +715,14 @@ function kit({ svelte_config, adapter_in_vite_config }) { const port = typeof address === 'string' ? Number(address.split(':').at(-1)) : address?.port; + const app_path = `${svelte_config.kit.paths.base}/${svelte_config.kit.appDir}`; + return dedent` // helps us avoid global fetch warnings we emit when the user uses it incorrectly const native_fetch = globalThis.fetch; export function get(pathname) { - return native_fetch(\`http://localhost:\${port}/${svelte_config.kit.appDir}\${pathname}\`); + return native_fetch(\`http://localhost:\${port}${app_path}\${pathname}\`); } let port${port ? ` = ${port}` : ''}; diff --git a/packages/kit/src/types/internal.d.ts b/packages/kit/src/types/internal.d.ts index 0c99719a5899..77f3c7055aef 100644 --- a/packages/kit/src/types/internal.d.ts +++ b/packages/kit/src/types/internal.d.ts @@ -66,7 +66,9 @@ export interface AssetDependencies { } export interface BuildData { + /** The _app directory configured. */ app_dir: string; + /** Path to the _app directory, including any base path. */ app_path: string; base: string; manifest_data: ManifestData; From 60c53d70f830bfb786d2efe1c6205ea8ea775f29 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Sat, 25 Apr 2026 16:48:15 +0800 Subject: [PATCH 095/117] regenerate types --- packages/kit/types/index.d.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 4b399ed80e77..32c25efcd9f9 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -2425,7 +2425,9 @@ declare module '@sveltejs/kit' { } interface BuildData { + /** The _app directory configured. */ app_dir: string; + /** Path to the _app directory, including any base path. */ app_path: string; base: string; manifest_data: ManifestData; From 92e8a8e943bc2973fe95358484e495e4c30f3305 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Sat, 25 Apr 2026 18:16:39 +0800 Subject: [PATCH 096/117] try this --- packages/kit/test/build-errors/prerender.spec.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/kit/test/build-errors/prerender.spec.js b/packages/kit/test/build-errors/prerender.spec.js index 1e9b508a2eb5..e788055bac8b 100644 --- a/packages/kit/test/build-errors/prerender.spec.js +++ b/packages/kit/test/build-errors/prerender.spec.js @@ -2,7 +2,6 @@ import { assert, test } from 'vitest'; import { execSync } from 'node:child_process'; import path from 'node:path'; import { EOL } from 'node:os'; -import { escape_for_regexp } from '../../src/utils/escape.js'; const timeout = 60_000; @@ -26,11 +25,7 @@ test('entry generators should match their own route', { timeout }, () => { stdio: 'pipe', timeout }), - new RegExp( - escape_for_regexp( - `Error: The entries export from /[slug]/[notSpecific] generated entry /whatever/specific, which was matched by /[slug]/specific - see the \`handleEntryGeneratorMismatch\` option in https://svelte.dev/docs/kit/configuration#prerender for more info.${EOL}To suppress or handle this error, implement \`handleEntryGeneratorMismatch\` in https://svelte.dev/docs/kit/configuration#prerender` - ) - ) + `Error: The entries export from /[slug]/[notSpecific] generated entry /whatever/specific, which was matched by /[slug]/specific - see the \`handleEntryGeneratorMismatch\` option in https://svelte.dev/docs/kit/configuration#prerender for more info.${EOL}To suppress or handle this error, implement \`handleEntryGeneratorMismatch\` in https://svelte.dev/docs/kit/configuration#prerender` ); }); From a2bfef87ee8b8832fdc3f7d35afe8b0e7d5a750f Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Sat, 25 Apr 2026 18:35:11 +0800 Subject: [PATCH 097/117] manual escape --- packages/kit/test/build-errors/prerender.spec.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/kit/test/build-errors/prerender.spec.js b/packages/kit/test/build-errors/prerender.spec.js index e788055bac8b..ba0fd837c269 100644 --- a/packages/kit/test/build-errors/prerender.spec.js +++ b/packages/kit/test/build-errors/prerender.spec.js @@ -25,7 +25,9 @@ test('entry generators should match their own route', { timeout }, () => { stdio: 'pipe', timeout }), - `Error: The entries export from /[slug]/[notSpecific] generated entry /whatever/specific, which was matched by /[slug]/specific - see the \`handleEntryGeneratorMismatch\` option in https://svelte.dev/docs/kit/configuration#prerender for more info.${EOL}To suppress or handle this error, implement \`handleEntryGeneratorMismatch\` in https://svelte.dev/docs/kit/configuration#prerender` + new RegExp( + `Error: The entries export from /\\[slug\\]/\\[notSpecific\\] generated entry /whatever/specific, which was matched by /\\[slug\\]/specific - see the \`handleEntryGeneratorMismatch\` option in https://svelte.dev/docs/kit/configuration#prerender for more info.${EOL}To suppress or handle this error, implement \`handleEntryGeneratorMismatch\` in https://svelte.dev/docs/kit/configuration#prerender` + ) ); }); From 6d9d3274d298413057574ded266a3c5aad134bb0 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Sat, 25 Apr 2026 19:11:27 +0800 Subject: [PATCH 098/117] clean up import.meta.hot usage Co-authored-by: Copilot --- packages/kit/src/core/postbuild/prerender.js | 4 +-- .../kit/src/core/postbuild/prerender_entry.js | 4 +-- packages/kit/src/exports/vite/dev/server.js | 14 ++++++++ .../kit/src/exports/vite/dev/ssr_manifest.js | 2 +- packages/kit/src/exports/vite/index.js | 36 ++++--------------- packages/kit/src/exports/vite/types.d.ts | 13 ++----- 6 files changed, 28 insertions(+), 45 deletions(-) diff --git a/packages/kit/src/core/postbuild/prerender.js b/packages/kit/src/core/postbuild/prerender.js index 874fcfeeb071..686be68076c1 100644 --- a/packages/kit/src/core/postbuild/prerender.js +++ b/packages/kit/src/core/postbuild/prerender.js @@ -214,7 +214,7 @@ export default async function prerender({ svelte_config, out, manifest_path, met /** @type {PromiseWithResolvers>} */ const prerender_dependencies = Promise.withResolvers(); - const event = `sveltekit:prerender-dependencies-${encoded}`; + const event = `sveltekit:prerender-dependencies:${encoded}`; /** @param {Record} dependencies */ const listener = (dependencies) => { /** @type {Map} */ @@ -444,7 +444,7 @@ export default async function prerender({ svelte_config, out, manifest_path, met handle_http_error({ status: response.status, path: decoded, referrer, referenceType }); } - vite.environments.ssr.hot.send('sveltekit:prerender-assets-update', file); + vite.environments.ssr.hot.send('sveltekit:prerender-assets', file); saved.set(file, dest); } diff --git a/packages/kit/src/core/postbuild/prerender_entry.js b/packages/kit/src/core/postbuild/prerender_entry.js index 1113eb047a1d..1f2a596d9712 100644 --- a/packages/kit/src/core/postbuild/prerender_entry.js +++ b/packages/kit/src/core/postbuild/prerender_entry.js @@ -18,7 +18,7 @@ export class Server extends KitServer { this.#manifest = manifest; - import.meta.hot?.on('sveltekit:prerender-assets-update', (data) => { + import.meta.hot?.on('sveltekit:prerender-assets', (data) => { manifest.assets.add(data); }); } @@ -80,7 +80,7 @@ export class Server extends KitServer { } import.meta.hot?.send( - `sveltekit:prerender-dependencies-${url.pathname}`, + `sveltekit:prerender-dependencies:${url.pathname}`, Object.fromEntries(dependencies) ); diff --git a/packages/kit/src/exports/vite/dev/server.js b/packages/kit/src/exports/vite/dev/server.js index da6c9a16665f..bfbc3eb787ed 100644 --- a/packages/kit/src/exports/vite/dev/server.js +++ b/packages/kit/src/exports/vite/dev/server.js @@ -3,6 +3,7 @@ import { DEV } from 'esm-env'; import { Server as KitServer } from '../../../runtime/server/index.js'; import { check_feature } from '../../../utils/features.js'; import { SCHEME } from '../../../utils/url.js'; +import { manifest } from './ssr_manifest.js'; const async_local_storage = new AsyncLocalStorage(); @@ -41,3 +42,16 @@ export class Server extends KitServer { return await super.respond(request, options); } } + +import.meta.hot?.on('sveltekit:remote', async (hash) => { + const remote = (await manifest._.remotes[hash]()).default; + + /** @type {Map} */ + const exports = new Map(); + for (const name in remote) { + exports.set(name, { type: remote[name].__.type }); + } + const data = Object.fromEntries(exports); + + import.meta.hot?.send(`sveltekit:remote:${hash}`, data); +}); diff --git a/packages/kit/src/exports/vite/dev/ssr_manifest.js b/packages/kit/src/exports/vite/dev/ssr_manifest.js index 9b94750a2476..a2ea05dd42da 100644 --- a/packages/kit/src/exports/vite/dev/ssr_manifest.js +++ b/packages/kit/src/exports/vite/dev/ssr_manifest.js @@ -182,7 +182,7 @@ async function loud_ssr_load_module(url) { return await import(/* @vite-ignore */ url); } catch (err) { if (err instanceof Error) { - import.meta.hot?.send('sveltekit:ssr-load-module', { + import.meta.hot?.send('sveltekit:ssr-load-module-error', { ...err, // these properties are non-enumerable and will not be // serialized unless we explicitly include them diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 88fdce740122..e92e63f8168d 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -1189,28 +1189,6 @@ function kit({ svelte_config, adapter_in_vite_config }) { fn.__.id = ${s(remote.hash)} + '/' + name; fn.__.name = name; } - ${ - dev_environment?.vite - ? dedent` - if (import.meta.hot) { - const exports = new Map(); - for (const name in $$_self_$$) { - exports.set(name, { type: $$_self_$$[name].__.type }); - } - const data = Object.fromEntries(exports); - const event = 'sveltekit:remote-${remote.hash}-response'; - - // we need to send the data immediately in case of preloads - import.meta.hot.send(event, data); - - // otherwise, we send it when requested - import.meta.hot.on('sveltekit:remote-${remote.hash}-request', async () => { - import.meta.hot.send(event, data); - }); - } - ` - : '' - } `; // Emit a dedicated entry chunk for this remote in SSR builds (prod only) @@ -1239,21 +1217,19 @@ function kit({ svelte_config, adapter_in_vite_config }) { // already loaded) so we can determine what it exports if (dev_environment?.vite) { /** @type {PromiseWithResolvers>} */ - const { promise, resolve } = Promise.withResolvers(); + const load_ssr_remote = Promise.withResolvers(); - const event = `sveltekit:remote-${remote.hash}-response`; + const event = `sveltekit:remote:${remote.hash}`; /** @param {Record} payload */ const listener = (payload) => { - resolve(payload); + load_ssr_remote.resolve(payload); dev_environment?.vite.environments.ssr.hot.off(event, listener); }; dev_environment.vite.environments.ssr.hot.on(event, listener); + dev_environment.vite.environments.ssr.hot.send('sveltekit:remote', remote.hash); - await dev_environment.vite.environments.ssr.transformRequest(id); - - dev_environment.vite.environments.ssr.hot.send(`sveltekit:remote-${remote.hash}-request`); - const exports = await promise; + const exports = await load_ssr_remote.promise; for (const [name, value] of Object.entries(exports)) { const type = value.type; @@ -1671,7 +1647,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { vite }); - vite.environments.ssr.hot.on('sveltekit:ssr-load-module', display_ssr_error_on_client); + vite.environments.ssr.hot.on('sveltekit:ssr-load-module-error', display_ssr_error_on_client); return dev(vite, vite_config, svelte_config, root, dev_environment); }, diff --git a/packages/kit/src/exports/vite/types.d.ts b/packages/kit/src/exports/vite/types.d.ts index c92ea45d0c95..990961748a6d 100644 --- a/packages/kit/src/exports/vite/types.d.ts +++ b/packages/kit/src/exports/vite/types.d.ts @@ -7,21 +7,14 @@ declare module 'vite/types/customEvent.d.ts' { hash: string; file: string; }; + 'sveltekit:remote': string; 'sveltekit:server-assets': { filepath: string; size: number; data: string; }; - - 'sveltekit:ssr-load-module': Error; - 'sveltekit:prerender-assets-update': string; - 'sveltekit:prerender-dependencies': Record< - string, - { - response: SerialisedResponse; - body: null | string | Uint8Array; - } - >; + 'sveltekit:ssr-load-module-error': Error; + 'sveltekit:prerender-assets': string; } } From cf6086fadc1a1ab4e83303c9d7b1e9abc535b11e Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Sat, 25 Apr 2026 19:25:46 +0800 Subject: [PATCH 099/117] correct escaping --- packages/kit/test/build-errors/prerender.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/test/build-errors/prerender.spec.js b/packages/kit/test/build-errors/prerender.spec.js index ba0fd837c269..4c42b9e6d43d 100644 --- a/packages/kit/test/build-errors/prerender.spec.js +++ b/packages/kit/test/build-errors/prerender.spec.js @@ -26,7 +26,7 @@ test('entry generators should match their own route', { timeout }, () => { timeout }), new RegExp( - `Error: The entries export from /\\[slug\\]/\\[notSpecific\\] generated entry /whatever/specific, which was matched by /\\[slug\\]/specific - see the \`handleEntryGeneratorMismatch\` option in https://svelte.dev/docs/kit/configuration#prerender for more info.${EOL}To suppress or handle this error, implement \`handleEntryGeneratorMismatch\` in https://svelte.dev/docs/kit/configuration#prerender` + `Error: The entries export from /\\[slug\\]/\\[notSpecific\\] generated entry /whatever/specific, which was matched by /\\[slug\\]/specific - see the \`handleEntryGeneratorMismatch\` option in https://svelte.dev/docs/kit/configuration#prerender for more info\\.${EOL}To suppress or handle this error, implement \`handleEntryGeneratorMismatch\` in https://svelte\\.dev/docs/kit/configuration#prerender` ) ); }); From 3be94b7d9b74f42cf5b10ba2a2f4cb5b610e2424 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Sat, 25 Apr 2026 19:44:38 +0800 Subject: [PATCH 100/117] allow optimizing server deps now that we use rolldown instead of esbuild --- packages/kit/src/exports/vite/index.js | 45 +++++++++++--------- packages/kit/test/apps/dev-only/test/test.js | 8 ++-- 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index e92e63f8168d..38d1d2df465e 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -292,7 +292,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { let env; /** @type {import('types').ManifestData} */ - let manifest_data; + let build_manifest_data; /** @type {import('types').ServerMetadata | undefined} only set at build time once analysis is finished */ let build_metadata = undefined; @@ -497,7 +497,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { __SVELTEKIT_APP_VERSION_POLL_INTERVAL__: s(kit.version.pollInterval) }; - manifest_data = sync.all(svelte_config, config_env.mode, root).manifest_data; + build_manifest_data = sync.all(svelte_config, config_env.mode, root).manifest_data; } else { new_config.define = { ...define, @@ -1041,6 +1041,8 @@ function kit({ svelte_config, adapter_in_vite_config }) { return; } + const manifest_data = dev_environment?.manifest_data ?? build_manifest_data; + /** @type {Set} */ const entrypoints = new Set(); for (const node of manifest_data.nodes) { @@ -1322,7 +1324,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { ]; export const files = [ - ${manifest_data.assets + ${build_manifest_data.assets .filter((asset) => kit.serviceWorker.files(asset.file)) .map((asset) => `base + ${s(`/${asset.file}`)}`) .join(',\n')} @@ -1385,7 +1387,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { }; // add entry points for every endpoint... - manifest_data.routes.forEach((route) => { + build_manifest_data.routes.forEach((route) => { if (route.endpoint) { const resolved = path.resolve(root, route.endpoint.file); const relative = decodeURIComponent(path.relative(kit.files.routes, resolved)); @@ -1395,7 +1397,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { }); // ...and every component used by pages... - manifest_data.nodes.forEach((node) => { + build_manifest_data.nodes.forEach((node) => { for (const file of [node.component, node.universal, node.server]) { if (file) { const resolved = path.resolve(root, file); @@ -1410,19 +1412,22 @@ function kit({ svelte_config, adapter_in_vite_config }) { }); // ...and every matcher - Object.entries(manifest_data.matchers).forEach(([key, file]) => { + Object.entries(build_manifest_data.matchers).forEach(([key, file]) => { const name = posixify(path.join('entries/matchers', key)); server_input[name] = path.resolve(root, file); }); // ...and the hooks files - if (manifest_data.hooks.server) { - server_input['entries/hooks.server'] = path.resolve(root, manifest_data.hooks.server); + if (build_manifest_data.hooks.server) { + server_input['entries/hooks.server'] = path.resolve( + root, + build_manifest_data.hooks.server + ); } - if (manifest_data.hooks.universal) { + if (build_manifest_data.hooks.universal) { server_input['entries/hooks.universal'] = path.resolve( root, - manifest_data.hooks.universal + build_manifest_data.hooks.universal ); } @@ -1450,7 +1455,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { } else { client_input['entry/start'] = `${runtime_directory}/client/entry.js`; client_input['entry/app'] = `${kit.outDir}/generated/client-optimized/app.js`; - manifest_data.nodes.forEach((node, i) => { + build_manifest_data.nodes.forEach((node, i) => { if (node.component || node.universal) { client_input[`nodes/${i}`] = `${kit.outDir}/generated/client-optimized/nodes/${i}.js`; @@ -1712,7 +1717,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { app_dir: kit.appDir, app_path: `${kit.paths.base.slice(1)}${kit.paths.base ? '/' : ''}${kit.appDir}`, base: kit.paths.base, - manifest_data, + manifest_data: build_manifest_data, out_dir: out, service_worker: service_worker_entry_file ? 'service-worker.js' : null, // TODO make file configurable? client: null, @@ -1726,7 +1731,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { build_data, prerendered: [], relative_path: '.', - routes: manifest_data.routes, + routes: build_manifest_data.routes, remotes, root })};\n` @@ -1737,7 +1742,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { const { metadata } = await analyse({ svelte_config, manifest_path, - manifest_data, + manifest_data: build_manifest_data, server_manifest, tracked_features, out, @@ -1751,7 +1756,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { // create client build write_client_manifest( kit, - manifest_data, + build_manifest_data, `${kit.outDir}/generated/client-optimized`, metadata.nodes ); @@ -1843,7 +1848,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { // similar to that on the client, with as much information computed upfront so that we // don't need to include any code of the actual routes in the server bundle. if (kit.router.resolution === 'server') { - const nodes = manifest_data.nodes.map((node, i) => { + const nodes = build_manifest_data.nodes.map((node, i) => { if (node.component || node.universal) { const entry = `${kit.outDir}/generated/client-optimized/nodes/${i}.js`; const deps = deps_of(entry, true); @@ -1860,7 +1865,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { build_data.client.css = nodes.map((node) => node?.css); build_data.client.routes = compact( - manifest_data.routes.map((route) => { + build_manifest_data.routes.map((route) => { if (!route.page) return; return { @@ -1914,7 +1919,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { build_data, prerendered: [], relative_path: '.', - routes: manifest_data.routes, + routes: build_manifest_data.routes, remotes, root })};\n` @@ -1923,7 +1928,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { // regenerate nodes with the client manifest... build_server_nodes({ out, - manifest_data, + manifest_data: build_manifest_data, server_manifest, client_manifest, paths: kit.paths, @@ -1962,7 +1967,7 @@ function kit({ svelte_config, adapter_in_vite_config }) { build_data, prerendered: prerendered.paths, relative_path: '.', - routes: manifest_data.routes.filter( + routes: build_manifest_data.routes.filter( (route) => prerender_results.prerender_map.get(route.id) !== true ), remotes, diff --git a/packages/kit/test/apps/dev-only/test/test.js b/packages/kit/test/apps/dev-only/test/test.js index 6e2a96dd8782..aeaeacea11f5 100644 --- a/packages/kit/test/apps/dev-only/test/test.js +++ b/packages/kit/test/apps/dev-only/test/test.js @@ -105,7 +105,7 @@ test.describe('Vite', () => { expect(manifest).toHaveProperty('optimized.e2e-test-dep-page-universal'); }); - test('skips optimizing +page.server.js dependencies', async ({ page }) => { + test('optimizes +page.server.js dependencies', async ({ page }) => { await page.goto('/'); await page.getByText('hello world!').waitFor(); @@ -115,7 +115,7 @@ test.describe('Vite', () => { ); const manifest = JSON.parse(fs.readFileSync(manifest_path, 'utf-8')); - expect(manifest).not.toHaveProperty('optimized.e2e-test-dep-page-server'); + expect(manifest).toHaveProperty('optimized.e2e-test-dep-page-server'); }); test('optimizes +layout.svelte dependencies', async ({ page }) => { @@ -144,7 +144,7 @@ test.describe('Vite', () => { expect(manifest).toHaveProperty('optimized.e2e-test-dep-layout-universal'); }); - test('skips optimizing +layout.server.js dependencies', async ({ page }) => { + test('optimizes +layout.server.js dependencies', async ({ page }) => { await page.goto('/'); await page.getByText('hello world!').waitFor(); @@ -154,7 +154,7 @@ test.describe('Vite', () => { ); const manifest = JSON.parse(fs.readFileSync(manifest_path, 'utf-8')); - expect(manifest).not.toHaveProperty('optimized.e2e-test-dep-layout-server'); + expect(manifest).toHaveProperty('optimized.e2e-test-dep-layout-server'); }); test('optimizes +error.svelte dependencies', async ({ page }) => { From 79d0542bc4f5dc8bfdb18929fa4d8f7c82a93388 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Sat, 25 Apr 2026 19:48:08 +0800 Subject: [PATCH 101/117] is chai doing its own escaping? try escaping everything --- packages/kit/test/build-errors/prerender.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/test/build-errors/prerender.spec.js b/packages/kit/test/build-errors/prerender.spec.js index 4c42b9e6d43d..6e25884fd5b0 100644 --- a/packages/kit/test/build-errors/prerender.spec.js +++ b/packages/kit/test/build-errors/prerender.spec.js @@ -26,7 +26,7 @@ test('entry generators should match their own route', { timeout }, () => { timeout }), new RegExp( - `Error: The entries export from /\\[slug\\]/\\[notSpecific\\] generated entry /whatever/specific, which was matched by /\\[slug\\]/specific - see the \`handleEntryGeneratorMismatch\` option in https://svelte.dev/docs/kit/configuration#prerender for more info\\.${EOL}To suppress or handle this error, implement \`handleEntryGeneratorMismatch\` in https://svelte\\.dev/docs/kit/configuration#prerender` + `Error: The entries export from \\/\\[slug\\]\\/\\[notSpecific\\] generated entry \\/whatever\\/specific, which was matched by \\/\\[slug\\]\\/specific - see the \`handleEntryGeneratorMismatch\` option in https:\\/\\/svelte.dev\\/docs\\/kit\\/configuration#prerender for more info\\.${EOL}To suppress or handle this error, implement \`handleEntryGeneratorMismatch\` in https:\\/\\/svelte\\.dev\\/docs\\/kit\\/configuration#prerender` ) ); }); From 3d6bbee0355f112f5c1afcf4a1d2c0bacb3f9f47 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Sat, 25 Apr 2026 20:18:09 +0800 Subject: [PATCH 102/117] give up on chai api --- .../kit/test/build-errors/prerender.spec.js | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/packages/kit/test/build-errors/prerender.spec.js b/packages/kit/test/build-errors/prerender.spec.js index 6e25884fd5b0..1a3b04631ec3 100644 --- a/packages/kit/test/build-errors/prerender.spec.js +++ b/packages/kit/test/build-errors/prerender.spec.js @@ -1,4 +1,4 @@ -import { assert, test } from 'vitest'; +import { assert, expect, test } from 'vitest'; import { execSync } from 'node:child_process'; import path from 'node:path'; import { EOL } from 'node:os'; @@ -18,16 +18,14 @@ test('prerenderable routes must be prerendered', { timeout }, () => { }); test('entry generators should match their own route', { timeout }, () => { - assert.throws( - () => - execSync('pnpm build', { - cwd: path.join(import.meta.dirname, 'apps/prerender-entry-generator-mismatch'), - stdio: 'pipe', - timeout - }), - new RegExp( - `Error: The entries export from \\/\\[slug\\]\\/\\[notSpecific\\] generated entry \\/whatever\\/specific, which was matched by \\/\\[slug\\]\\/specific - see the \`handleEntryGeneratorMismatch\` option in https:\\/\\/svelte.dev\\/docs\\/kit\\/configuration#prerender for more info\\.${EOL}To suppress or handle this error, implement \`handleEntryGeneratorMismatch\` in https:\\/\\/svelte\\.dev\\/docs\\/kit\\/configuration#prerender` - ) + expect(() => + execSync('pnpm build', { + cwd: path.join(import.meta.dirname, 'apps/prerender-entry-generator-mismatch'), + stdio: 'pipe', + timeout + }) + ).toThrow( + `Error: The entries export from /[slug]/[notSpecific] generated entry /whatever/specific, which was matched by /[slug]/specific - see the \`handleEntryGeneratorMismatch\` option in https://svelte.dev/docs/kit/configuration#prerender for more info.${EOL}To suppress or handle this error, implement \`handleEntryGeneratorMismatch\` in https://svelte.dev/docs/kit/configuration#prerender` ); }); From 8ec1aab5d535041dc88d3f4870847f5117cd2b99 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Sat, 25 Apr 2026 21:01:27 +0800 Subject: [PATCH 103/117] fix prerendering --- packages/kit/src/core/postbuild/prerender.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/kit/src/core/postbuild/prerender.js b/packages/kit/src/core/postbuild/prerender.js index 686be68076c1..58a799696aab 100644 --- a/packages/kit/src/core/postbuild/prerender.js +++ b/packages/kit/src/core/postbuild/prerender.js @@ -6,7 +6,7 @@ import { dirname, join } from 'node:path'; import * as devalue from 'devalue'; import { mkdirp, walk } from '../../utils/filesystem.js'; import { noop } from '../../utils/functions.js'; -import { decode_uri, is_root_relative, resolve } from '../../utils/url.js'; +import { decode_pathname, decode_uri, is_root_relative, resolve } from '../../utils/url.js'; import { escape_for_regexp, escape_html } from '../../utils/escape.js'; import { logger } from '../utils.js'; import { get_route_segments } from '../../utils/routing.js'; @@ -299,6 +299,10 @@ export default async function prerender({ svelte_config, out, manifest_path, met svelte_config.kit.prerender.crawl && headers['content-type'] === 'text/html' ) { + // we should use the response URL because the server might have redirected, + // changing the URL's trailing slash state and affect how we resolve the + // relative dependency URLs + const decoded = decode_pathname(new URL(response.url).pathname); const { ids, hrefs } = crawl(body.toString(), decoded); actual_hashlinks.set(decoded, ids); From 0b2255eb4d56a43fe48588a26d33e4042832cf5d Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Sat, 25 Apr 2026 21:13:03 +0800 Subject: [PATCH 104/117] oops don't reuse name --- packages/kit/src/core/postbuild/prerender.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/kit/src/core/postbuild/prerender.js b/packages/kit/src/core/postbuild/prerender.js index 58a799696aab..3fb51909232f 100644 --- a/packages/kit/src/core/postbuild/prerender.js +++ b/packages/kit/src/core/postbuild/prerender.js @@ -6,7 +6,7 @@ import { dirname, join } from 'node:path'; import * as devalue from 'devalue'; import { mkdirp, walk } from '../../utils/filesystem.js'; import { noop } from '../../utils/functions.js'; -import { decode_pathname, decode_uri, is_root_relative, resolve } from '../../utils/url.js'; +import { decode_uri, is_root_relative, resolve } from '../../utils/url.js'; import { escape_for_regexp, escape_html } from '../../utils/escape.js'; import { logger } from '../utils.js'; import { get_route_segments } from '../../utils/routing.js'; @@ -302,8 +302,8 @@ export default async function prerender({ svelte_config, out, manifest_path, met // we should use the response URL because the server might have redirected, // changing the URL's trailing slash state and affect how we resolve the // relative dependency URLs - const decoded = decode_pathname(new URL(response.url).pathname); - const { ids, hrefs } = crawl(body.toString(), decoded); + const response_decoded = decode_uri(new URL(response.url).pathname); + const { ids, hrefs } = crawl(body.toString(), response_decoded); actual_hashlinks.set(decoded, ids); From a00415e42e6728e9059b42fd23d3e9b9d1f19d66 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Sat, 25 Apr 2026 21:41:44 +0800 Subject: [PATCH 105/117] avoid chai completely Co-authored-by: Copilot --- .../kit/test/build-errors/prerender.spec.js | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/packages/kit/test/build-errors/prerender.spec.js b/packages/kit/test/build-errors/prerender.spec.js index 1a3b04631ec3..0096ba5ec4f7 100644 --- a/packages/kit/test/build-errors/prerender.spec.js +++ b/packages/kit/test/build-errors/prerender.spec.js @@ -1,4 +1,4 @@ -import { assert, expect, test } from 'vitest'; +import { expect, test } from 'vitest'; import { execSync } from 'node:child_process'; import path from 'node:path'; import { EOL } from 'node:os'; @@ -6,13 +6,13 @@ import { EOL } from 'node:os'; const timeout = 60_000; test('prerenderable routes must be prerendered', { timeout }, () => { - assert.throws( - () => - execSync('pnpm build', { - cwd: path.join(import.meta.dirname, 'apps/prerenderable-not-prerendered'), - stdio: 'pipe', - timeout - }), + expect(() => + execSync('pnpm build', { + cwd: path.join(import.meta.dirname, 'apps/prerenderable-not-prerendered'), + stdio: 'pipe', + timeout + }) + ).toThrow( /The following routes were marked as prerenderable, but were not prerendered because they were not found while crawling your app:\r?\n {2}- \/\[x\]/gs ); }); @@ -30,13 +30,11 @@ test('entry generators should match their own route', { timeout }, () => { }); test('an error in a `prerender` function should fail the build', { timeout }, () => { - assert.throws( - () => - execSync('pnpm build', { - cwd: path.join(import.meta.dirname, 'apps/prerender-remote-function-error'), - stdio: 'pipe', - timeout - }), - /remote function blew up/ - ); + expect(() => + execSync('pnpm build', { + cwd: path.join(import.meta.dirname, 'apps/prerender-remote-function-error'), + stdio: 'pipe', + timeout + }) + ).toThrow(/remote function blew up/); }); From 4bdf8313bc3dd5b2eac46a4f4af9072a84e29685 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Sat, 25 Apr 2026 22:37:35 +0800 Subject: [PATCH 106/117] regex hell Co-authored-by: Copilot --- .../kit/test/build-errors/prerender.spec.js | 49 ++++++++++--------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/packages/kit/test/build-errors/prerender.spec.js b/packages/kit/test/build-errors/prerender.spec.js index 0096ba5ec4f7..63419fe91a23 100644 --- a/packages/kit/test/build-errors/prerender.spec.js +++ b/packages/kit/test/build-errors/prerender.spec.js @@ -1,40 +1,41 @@ -import { expect, test } from 'vitest'; +import { assert, test } from 'vitest'; import { execSync } from 'node:child_process'; import path from 'node:path'; -import { EOL } from 'node:os'; const timeout = 60_000; test('prerenderable routes must be prerendered', { timeout }, () => { - expect(() => - execSync('pnpm build', { - cwd: path.join(import.meta.dirname, 'apps/prerenderable-not-prerendered'), - stdio: 'pipe', - timeout - }) - ).toThrow( + assert.throws( + () => + execSync('pnpm build', { + cwd: path.join(import.meta.dirname, 'apps/prerenderable-not-prerendered'), + stdio: 'pipe', + timeout + }), /The following routes were marked as prerenderable, but were not prerendered because they were not found while crawling your app:\r?\n {2}- \/\[x\]/gs ); }); test('entry generators should match their own route', { timeout }, () => { - expect(() => - execSync('pnpm build', { - cwd: path.join(import.meta.dirname, 'apps/prerender-entry-generator-mismatch'), - stdio: 'pipe', - timeout - }) - ).toThrow( - `Error: The entries export from /[slug]/[notSpecific] generated entry /whatever/specific, which was matched by /[slug]/specific - see the \`handleEntryGeneratorMismatch\` option in https://svelte.dev/docs/kit/configuration#prerender for more info.${EOL}To suppress or handle this error, implement \`handleEntryGeneratorMismatch\` in https://svelte.dev/docs/kit/configuration#prerender` + assert.throws( + () => + execSync('pnpm build', { + cwd: path.join(import.meta.dirname, 'apps/prerender-entry-generator-mismatch'), + stdio: 'pipe', + timeout + }), + /Error: The entries export from \/\[slug\]\/\[notSpecific\] generated entry \/whatever\/specific, which was matched by \/\[slug\]\/specific - see the `handleEntryGeneratorMismatch` option in https:\/\/svelte\.dev\/docs\/kit\/configuration#prerender for more info\.\nTo suppress or handle this error, implement `handleEntryGeneratorMismatch` in https:\/\/svelte\.dev\/docs\/kit\/configuration#prerender/ ); }); test('an error in a `prerender` function should fail the build', { timeout }, () => { - expect(() => - execSync('pnpm build', { - cwd: path.join(import.meta.dirname, 'apps/prerender-remote-function-error'), - stdio: 'pipe', - timeout - }) - ).toThrow(/remote function blew up/); + assert.throws( + () => + execSync('pnpm build', { + cwd: path.join(import.meta.dirname, 'apps/prerender-remote-function-error'), + stdio: 'pipe', + timeout + }), + /remote function blew up/ + ); }); From 908a4a6072069c09718c7b2420cb547e3b3325db Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Sat, 25 Apr 2026 23:41:00 +0800 Subject: [PATCH 107/117] fix redirected prerendered pages Co-authored-by: Copilot --- packages/kit/src/core/postbuild/prerender.js | 54 ++++++++++++------- .../kit/src/core/postbuild/prerender_entry.js | 35 ++++++------ 2 files changed, 53 insertions(+), 36 deletions(-) diff --git a/packages/kit/src/core/postbuild/prerender.js b/packages/kit/src/core/postbuild/prerender.js index 3fb51909232f..60385060f1a7 100644 --- a/packages/kit/src/core/postbuild/prerender.js +++ b/packages/kit/src/core/postbuild/prerender.js @@ -214,26 +214,42 @@ export default async function prerender({ svelte_config, out, manifest_path, met /** @type {PromiseWithResolvers>} */ const prerender_dependencies = Promise.withResolvers(); - const event = `sveltekit:prerender-dependencies:${encoded}`; - /** @param {Record} dependencies */ - const listener = (dependencies) => { - /** @type {Map} */ - const deserialised = new Map(); - for (const [path, dependency] of Object.entries(dependencies)) { - deserialised.set(path, { - response: new Response(dependency.response.body, { - headers: dependency.response.headers, - status: dependency.response.status, - statusText: dependency.response.statusText - }), - body: dependency.body - }); - } - prerender_dependencies.resolve(deserialised); - vite.environments.ssr.hot.off(event, listener); + /** @param {string} event */ + const create_dependency_handler = (event) => { + /** @param {{ dependencies: Record } | { location: string; }} data */ + const dependency_handler = (data) => { + if ('location' in data) { + const handle_redirected_dependencies = create_dependency_handler( + `sveltekit:prerender-dependencies:${data.location}` + ); + vite.environments.ssr.hot.on( + `sveltekit:prerender-dependencies:${data.location}`, + handle_redirected_dependencies + ); + } else { + /** @type {Map} */ + const deserialised = new Map(); + for (const [path, dependency] of Object.entries(data.dependencies)) { + deserialised.set(path, { + response: new Response(dependency.response.body, { + headers: dependency.response.headers, + status: dependency.response.status, + statusText: dependency.response.statusText + }), + body: dependency.body + }); + } + prerender_dependencies.resolve(deserialised); + } + vite.environments.ssr.hot.off(event, dependency_handler); + }; + return dependency_handler; }; - vite.environments.ssr.hot.on(event, listener); + const event = `sveltekit:prerender-dependencies:${encoded}`; + const handle_dependencies = create_dependency_handler(event); + vite.environments.ssr.hot.on(event, handle_dependencies); + const response = await fetch(`http://localhost:${port}${encoded}`); const encoded_id = response.headers.get('x-sveltekit-routeid'); @@ -300,7 +316,7 @@ export default async function prerender({ svelte_config, out, manifest_path, met headers['content-type'] === 'text/html' ) { // we should use the response URL because the server might have redirected, - // changing the URL's trailing slash state and affect how we resolve the + // changing the URL's trailing slash presence and affecting how we resolve // relative dependency URLs const response_decoded = decode_uri(new URL(response.url).pathname); const { ids, hrefs } = crawl(body.toString(), response_decoded); diff --git a/packages/kit/src/core/postbuild/prerender_entry.js b/packages/kit/src/core/postbuild/prerender_entry.js index 1f2a596d9712..12df5ff469ad 100644 --- a/packages/kit/src/core/postbuild/prerender_entry.js +++ b/packages/kit/src/core/postbuild/prerender_entry.js @@ -65,25 +65,26 @@ export class Server extends KitServer { const response = await super.respond(request, options); - /** @type {Map} */ - const dependencies = new Map(); - for (const [pathname, dependency] of options.prerendering.dependencies) { - dependencies.set(pathname, { - response: { - status: dependency.response.status, - statusText: dependency.response.statusText, - headers: Object.fromEntries(dependency.response.headers), - body: await dependency.response.arrayBuffer() - }, - body: dependency.body - }); + const event = `sveltekit:prerender-dependencies:${url.pathname}`; + if (response.headers.has('x-sveltekit-normalize')) { + import.meta.hot?.send(event, { location: response.headers.get('location') }); + } else { + /** @type {Map} */ + const dependencies = new Map(); + for (const [pathname, dependency] of options.prerendering.dependencies) { + dependencies.set(pathname, { + response: { + status: dependency.response.status, + statusText: dependency.response.statusText, + headers: Object.fromEntries(dependency.response.headers), + body: await dependency.response.arrayBuffer() + }, + body: dependency.body + }); + } + import.meta.hot?.send(event, { dependencies: Object.fromEntries(dependencies) }); } - import.meta.hot?.send( - `sveltekit:prerender-dependencies:${url.pathname}`, - Object.fromEntries(dependencies) - ); - return response; } } From 56daa194536cbf3a4335e4ddb1588944acfeb9a8 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Sat, 25 Apr 2026 23:59:25 +0800 Subject: [PATCH 108/117] revert --- packages/kit/test/build-errors/tsconfig.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/kit/test/build-errors/tsconfig.json b/packages/kit/test/build-errors/tsconfig.json index a0baa4e30c60..eb9d304fd592 100644 --- a/packages/kit/test/build-errors/tsconfig.json +++ b/packages/kit/test/build-errors/tsconfig.json @@ -4,8 +4,7 @@ "checkJs": true, "noEmit": true, "target": "esnext", - "module": "nodenext", - "moduleResolution": "nodenext" + "moduleResolution": "node" }, "include": ["./*"] } From e55644a0e3d110b7f37ca23b5400f370b512713f Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Sun, 26 Apr 2026 00:44:19 +0800 Subject: [PATCH 109/117] fix prerender redirects for good --- packages/kit/src/core/postbuild/prerender.js | 58 ++++++------------- .../kit/src/core/postbuild/prerender_entry.js | 33 +++++------ .../kit/src/exports/vite/build/vite_server.js | 9 +-- 3 files changed, 39 insertions(+), 61 deletions(-) diff --git a/packages/kit/src/core/postbuild/prerender.js b/packages/kit/src/core/postbuild/prerender.js index 60385060f1a7..1bbd9c47bb3d 100644 --- a/packages/kit/src/core/postbuild/prerender.js +++ b/packages/kit/src/core/postbuild/prerender.js @@ -214,43 +214,27 @@ export default async function prerender({ svelte_config, out, manifest_path, met /** @type {PromiseWithResolvers>} */ const prerender_dependencies = Promise.withResolvers(); - /** @param {string} event */ - const create_dependency_handler = (event) => { - /** @param {{ dependencies: Record } | { location: string; }} data */ - const dependency_handler = (data) => { - if ('location' in data) { - const handle_redirected_dependencies = create_dependency_handler( - `sveltekit:prerender-dependencies:${data.location}` - ); - vite.environments.ssr.hot.on( - `sveltekit:prerender-dependencies:${data.location}`, - handle_redirected_dependencies - ); - } else { - /** @type {Map} */ - const deserialised = new Map(); - for (const [path, dependency] of Object.entries(data.dependencies)) { - deserialised.set(path, { - response: new Response(dependency.response.body, { - headers: dependency.response.headers, - status: dependency.response.status, - statusText: dependency.response.statusText - }), - body: dependency.body - }); - } - prerender_dependencies.resolve(deserialised); - } - vite.environments.ssr.hot.off(event, dependency_handler); - }; - return dependency_handler; - }; - const event = `sveltekit:prerender-dependencies:${encoded}`; - const handle_dependencies = create_dependency_handler(event); + /** @param {{ dependencies: Record }} data */ + const handle_dependencies = (data) => { + /** @type {Map} */ + const deserialised = new Map(); + for (const [path, dependency] of Object.entries(data.dependencies)) { + deserialised.set(path, { + response: new Response(dependency.response.body, { + headers: dependency.response.headers, + status: dependency.response.status, + statusText: dependency.response.statusText + }), + body: dependency.body + }); + } + prerender_dependencies.resolve(deserialised); + vite.environments.ssr.hot.off(event, handle_dependencies); + }; vite.environments.ssr.hot.on(event, handle_dependencies); - const response = await fetch(`http://localhost:${port}${encoded}`); + const response = await fetch(`http://localhost:${port}${encoded}`, { redirect: 'manual' }); const encoded_id = response.headers.get('x-sveltekit-routeid'); const decoded_id = encoded_id && decode_uri(encoded_id); @@ -315,11 +299,7 @@ export default async function prerender({ svelte_config, out, manifest_path, met svelte_config.kit.prerender.crawl && headers['content-type'] === 'text/html' ) { - // we should use the response URL because the server might have redirected, - // changing the URL's trailing slash presence and affecting how we resolve - // relative dependency URLs - const response_decoded = decode_uri(new URL(response.url).pathname); - const { ids, hrefs } = crawl(body.toString(), response_decoded); + const { ids, hrefs } = crawl(body.toString(), decoded); actual_hashlinks.set(decoded, ids); diff --git a/packages/kit/src/core/postbuild/prerender_entry.js b/packages/kit/src/core/postbuild/prerender_entry.js index 12df5ff469ad..84622fff17cc 100644 --- a/packages/kit/src/core/postbuild/prerender_entry.js +++ b/packages/kit/src/core/postbuild/prerender_entry.js @@ -65,25 +65,22 @@ export class Server extends KitServer { const response = await super.respond(request, options); - const event = `sveltekit:prerender-dependencies:${url.pathname}`; - if (response.headers.has('x-sveltekit-normalize')) { - import.meta.hot?.send(event, { location: response.headers.get('location') }); - } else { - /** @type {Map} */ - const dependencies = new Map(); - for (const [pathname, dependency] of options.prerendering.dependencies) { - dependencies.set(pathname, { - response: { - status: dependency.response.status, - statusText: dependency.response.statusText, - headers: Object.fromEntries(dependency.response.headers), - body: await dependency.response.arrayBuffer() - }, - body: dependency.body - }); - } - import.meta.hot?.send(event, { dependencies: Object.fromEntries(dependencies) }); + /** @type {Map} */ + const dependencies = new Map(); + for (const [pathname, dependency] of options.prerendering.dependencies) { + dependencies.set(pathname, { + response: { + status: dependency.response.status, + statusText: dependency.response.statusText, + headers: Object.fromEntries(dependency.response.headers), + body: await dependency.response.arrayBuffer() + }, + body: dependency.body + }); } + import.meta.hot?.send(`sveltekit:prerender-dependencies:${url.pathname}`, { + dependencies: Object.fromEntries(dependencies) + }); return response; } diff --git a/packages/kit/src/exports/vite/build/vite_server.js b/packages/kit/src/exports/vite/build/vite_server.js index 91633ff4938e..086bc041ddf6 100644 --- a/packages/kit/src/exports/vite/build/vite_server.js +++ b/packages/kit/src/exports/vite/build/vite_server.js @@ -3,6 +3,8 @@ /** @import { ModuleRunner } from 'vite/module-runner' */ import fs from 'node:fs'; import path, { basename } from 'node:path'; +import { exactRegex } from 'rolldown/filter'; +import sirv from 'sirv'; import { createFetchableDevEnvironment, createServer, @@ -10,7 +12,7 @@ import { createServerModuleRunner, isFetchableDevEnvironment } from 'vite'; -import { exactRegex } from 'rolldown/filter'; +import { getRequest, setResponse } from '@sveltejs/kit/node'; import { sveltekit_env, sveltekit_ipc } from '../module_ids.js'; import { dedent } from '../../../core/sync/utils.js'; import { @@ -20,10 +22,8 @@ import { invalidate_module, remove_static_middlewares } from '../dev/index.js'; -import { getRequest, setResponse } from '@sveltejs/kit/node'; import { s } from '../../../utils/misc.js'; import { get_env } from '../utils.js'; -import sirv from 'sirv'; import { SVELTE_KIT_ASSETS } from '../../../constants.js'; /** @@ -35,7 +35,7 @@ import { SVELTE_KIT_ASSETS } from '../../../constants.js'; * @param {PluginOption} [opts.vite_plugins] * @returns {Promise} */ -export async function create_build_server({ +export function create_build_server({ svelte_config, out, manifest_path, @@ -394,6 +394,7 @@ export async function create_build_server({ svelte_config.kit.adapter?.vite?.plugins ?? plugin_node_environment ].filter(Boolean); + // TODO: run in a separate process so that user code doesn't cause the build to hang return createServer({ configFile: false, command: 'serve', From f23b30050c2f60ebec0bb720d400fc89399f9db7 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Sun, 26 Apr 2026 16:15:11 +0800 Subject: [PATCH 110/117] revert to forked processes Co-authored-by: Copilot --- packages/kit/src/core/adapt/builder.js | 4 +- packages/kit/src/core/postbuild/analyse.js | 12 ++-- packages/kit/src/core/postbuild/fallback.js | 12 +++- packages/kit/src/core/postbuild/prerender.js | 17 +++-- .../kit/src/exports/vite/build/vite_server.js | 1 - packages/kit/src/exports/vite/index.js | 2 - packages/kit/src/utils/fork.js | 70 +++++++++++++++++++ 7 files changed, 100 insertions(+), 18 deletions(-) create mode 100644 packages/kit/src/utils/fork.js diff --git a/packages/kit/src/core/adapt/builder.js b/packages/kit/src/core/adapt/builder.js index 95d88b5895f7..90e740bcc5fb 100644 --- a/packages/kit/src/core/adapt/builder.js +++ b/packages/kit/src/core/adapt/builder.js @@ -118,9 +118,9 @@ export function create_builder({ const manifest_path = `${config.kit.outDir}/output/server/manifest-full.js`; const fallback = await generate_fallback({ - svelte_config: config, manifest_path, - out + out, + root: vite_config.root }); if (existsSync(dest)) { diff --git a/packages/kit/src/core/postbuild/analyse.js b/packages/kit/src/core/postbuild/analyse.js index 7beb2720448d..2002e2873218 100644 --- a/packages/kit/src/core/postbuild/analyse.js +++ b/packages/kit/src/core/postbuild/analyse.js @@ -1,14 +1,17 @@ -/** @import { ManifestData, ServerMetadata, ValidatedConfig } from 'types' */ +/** @import { ManifestData, ServerMetadata } from 'types' */ /** @import { Manifest } from 'vite' */ import * as devalue from 'devalue'; +import { forked } from '../../utils/fork.js'; import { build_server_nodes } from '../../exports/vite/build/build_server.js'; import { create_build_server } from '../../exports/vite/build/vite_server.js'; +import { load_config } from '../config/index.js'; + +export default forked(import.meta.url, analyse); const analyse_entry = import.meta.resolve('./analyse_entry.js'); /** * @param {object} opts Arguments must be serialisable via the structured clone algorithm - * @param {ValidatedConfig} opts.svelte_config * @param {string} opts.manifest_path * @param {ManifestData} opts.manifest_data * @param {Manifest} opts.server_manifest @@ -17,8 +20,7 @@ const analyse_entry = import.meta.resolve('./analyse_entry.js'); * @param {string} opts.root * @returns {Promise<{ metadata: ServerMetadata }>} */ -export default async function analyse({ - svelte_config, +async function analyse({ manifest_path, manifest_data, server_manifest, @@ -29,6 +31,8 @@ export default async function analyse({ // first, build server nodes without the client manifest so we can analyse it build_server_nodes({ out, manifest_data, server_manifest, root }); + const svelte_config = await load_config({ cwd: root }); + const vite = await create_build_server({ svelte_config, out, diff --git a/packages/kit/src/core/postbuild/fallback.js b/packages/kit/src/core/postbuild/fallback.js index 3727693d4f26..1421875985fd 100644 --- a/packages/kit/src/core/postbuild/fallback.js +++ b/packages/kit/src/core/postbuild/fallback.js @@ -1,17 +1,23 @@ -/** @import { ValidatedConfig } from 'types' */ /** @import { PluginOption } from 'vite' */ import { escape_for_regexp } from '../../utils/escape.js'; import { create_build_server } from '../../exports/vite/build/vite_server.js'; +import { load_config } from '../config/index.js'; +import { forked } from '../../utils/fork.js'; + +export default forked(import.meta.url, generate_fallback); const prerender_entry = import.meta.resolve('./prerender_entry.js'); /** * @param {object} opts Arguments must be serialisable via the structured clone algorithm - * @param {ValidatedConfig} opts.svelte_config * @param {string} opts.manifest_path * @param {string} opts.out + * @param {string} opts.root + * @returns {Promise} */ -export default async function generate_fallback({ svelte_config, manifest_path, out }) { +async function generate_fallback({ manifest_path, out, root }) { + const svelte_config = await load_config({ cwd: root }); + /** @type {PluginOption} */ const plugin_generate_fallback = { name: 'vite-plugin-sveltekit-compile:generate-fallback', diff --git a/packages/kit/src/core/postbuild/prerender.js b/packages/kit/src/core/postbuild/prerender.js index 1bbd9c47bb3d..a64ecd244acb 100644 --- a/packages/kit/src/core/postbuild/prerender.js +++ b/packages/kit/src/core/postbuild/prerender.js @@ -1,9 +1,8 @@ -/** @import { Logger, PrerenderDependency, Prerendered, PrerenderMap, ServerMetadata, ValidatedConfig } from 'types' */ +/** @import { Logger, PrerenderDependency, Prerendered, PrerenderMap, ServerMetadata } from 'types' */ /** @import { PluginOption } from 'vite' */ /** @import { SerialisedResponse } from '../../exports/vite/types.js' */ import { existsSync, readFileSync, statSync, writeFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; -import * as devalue from 'devalue'; import { mkdirp, walk } from '../../utils/filesystem.js'; import { noop } from '../../utils/functions.js'; import { decode_uri, is_root_relative, resolve } from '../../utils/url.js'; @@ -12,10 +11,15 @@ import { logger } from '../utils.js'; import { get_route_segments } from '../../utils/routing.js'; import { queue } from './queue.js'; import { crawl } from './crawl.js'; +import { forked } from '../../utils/fork.js'; +import * as devalue from 'devalue'; import generate_fallback from './fallback.js'; import { posixify } from '../../utils/os.js'; import { create_app_dir_matcher } from '../../exports/vite/dev/index.js'; import { create_build_server } from '../../exports/vite/build/vite_server.js'; +import { load_config } from '../config/index.js'; + +export default forked(import.meta.url, prerender); // https://html.spec.whatwg.org/multipage/browsing-the-web.html#scrolling-to-a-fragment // "If fragment is the empty string, then return the special value top of the document." @@ -27,14 +31,13 @@ const prerender_entry = import.meta.resolve('./prerender_entry.js'); /** * @param {object} opts Arguments must be serialisable via the structured clone algorithm - * @param {ValidatedConfig} opts.svelte_config * @param {string} opts.out * @param {string} opts.manifest_path * @param {ServerMetadata} opts.metadata * @param {boolean} opts.verbose * @param {string} opts.root */ -export default async function prerender({ svelte_config, out, manifest_path, metadata, verbose }) { +async function prerender({ out, manifest_path, metadata, verbose, root }) { /** * @template {{message: string}} T * @template {Omit} K @@ -81,11 +84,13 @@ export default async function prerender({ svelte_config, out, manifest_path, met } } + const svelte_config = await load_config({ cwd: root }); + if (svelte_config.kit.router.type === 'hash') { const fallback = await generate_fallback({ - svelte_config, manifest_path, - out + out, + root }); const file = output_filename('/', true); diff --git a/packages/kit/src/exports/vite/build/vite_server.js b/packages/kit/src/exports/vite/build/vite_server.js index 086bc041ddf6..37c08d062f79 100644 --- a/packages/kit/src/exports/vite/build/vite_server.js +++ b/packages/kit/src/exports/vite/build/vite_server.js @@ -394,7 +394,6 @@ export function create_build_server({ svelte_config.kit.adapter?.vite?.plugins ?? plugin_node_environment ].filter(Boolean); - // TODO: run in a separate process so that user code doesn't cause the build to hang return createServer({ configFile: false, command: 'serve', diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 38d1d2df465e..1fd40eeceb8e 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -1740,7 +1740,6 @@ function kit({ svelte_config, adapter_in_vite_config }) { log.info('Analysing routes'); const { metadata } = await analyse({ - svelte_config, manifest_path, manifest_data: build_manifest_data, server_manifest, @@ -1941,7 +1940,6 @@ function kit({ svelte_config, adapter_in_vite_config }) { // ...and prerender const prerender_results = await prerender({ - svelte_config, out, manifest_path, metadata, diff --git a/packages/kit/src/utils/fork.js b/packages/kit/src/utils/fork.js new file mode 100644 index 000000000000..918eded41e5f --- /dev/null +++ b/packages/kit/src/utils/fork.js @@ -0,0 +1,70 @@ +import { fileURLToPath } from 'node:url'; +import { Worker, parentPort } from 'node:worker_threads'; +import process from 'node:process'; + +/** + * Runs a task in a subprocess so any dangling stuff gets killed upon completion. + * The subprocess needs to be the file `forked` is called in, and `forked` needs to be called eagerly at the top level. + * @template T + * @template U + * @param {string} module `import.meta.url` of the file + * @param {(opts: T) => Promise} callback The function that is invoked in the subprocess + * @returns {(opts: T) => Promise} A function that when called starts the subprocess + */ +export function forked(module, callback) { + if (process.env.SVELTEKIT_FORK && parentPort) { + parentPort.on( + 'message', + /** @param {any} data */ async (data) => { + if (data?.type === 'args' && data.module === module) { + parentPort?.postMessage({ + type: 'result', + module, + payload: await callback(data.payload) + }); + } + } + ); + + parentPort.postMessage({ type: 'ready', module }); + } + + /** + * @param {T} opts + * @returns {Promise} + */ + return function (opts) { + return new Promise((fulfil, reject) => { + const worker = new Worker(fileURLToPath(module), { + env: { + ...process.env, + SVELTEKIT_FORK: 'true' + } + }); + + worker.on( + 'message', + /** @param {any} data */ (data) => { + if (data?.type === 'ready' && data.module === module) { + worker.postMessage({ + type: 'args', + module, + payload: opts + }); + } + + if (data?.type === 'result' && data.module === module) { + worker.unref(); + fulfil(data.payload); + } + } + ); + + worker.on('exit', (code) => { + if (code) { + reject(new Error(`Failed with code ${code}`)); + } + }); + }); + }; +} From a8c7be93e0693b1df6d035a353756620b32d709e Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Sun, 26 Apr 2026 16:25:38 +0800 Subject: [PATCH 111/117] optional \r to accomodate windows --- packages/kit/test/build-errors/prerender.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/test/build-errors/prerender.spec.js b/packages/kit/test/build-errors/prerender.spec.js index 63419fe91a23..09be3d111de6 100644 --- a/packages/kit/test/build-errors/prerender.spec.js +++ b/packages/kit/test/build-errors/prerender.spec.js @@ -24,7 +24,7 @@ test('entry generators should match their own route', { timeout }, () => { stdio: 'pipe', timeout }), - /Error: The entries export from \/\[slug\]\/\[notSpecific\] generated entry \/whatever\/specific, which was matched by \/\[slug\]\/specific - see the `handleEntryGeneratorMismatch` option in https:\/\/svelte\.dev\/docs\/kit\/configuration#prerender for more info\.\nTo suppress or handle this error, implement `handleEntryGeneratorMismatch` in https:\/\/svelte\.dev\/docs\/kit\/configuration#prerender/ + /Error: The entries export from \/\[slug\]\/\[notSpecific\] generated entry \/whatever\/specific, which was matched by \/\[slug\]\/specific - see the `handleEntryGeneratorMismatch` option in https:\/\/svelte\.dev\/docs\/kit\/configuration#prerender for more info\.\r?\nTo suppress or handle this error, implement `handleEntryGeneratorMismatch` in https:\/\/svelte\.dev\/docs\/kit\/configuration#prerender/ ); }); From 44e4075987a857d801a0961001dec31c379c7118 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Sun, 26 Apr 2026 17:33:20 +0800 Subject: [PATCH 112/117] update manifest data when file is changed Co-authored-by: Copilot --- packages/kit/src/exports/vite/dev/index.js | 12 ++++++++++-- packages/kit/src/exports/vite/index.js | 14 ++++++++++++++ packages/kit/src/exports/vite/types.d.ts | 5 +++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index f42201e67b7a..29c13b9fadc9 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -107,8 +107,16 @@ export function dev(vite, vite_config, svelte_config, root, dev_environment) { // Unless it's a file where the trailing slash page option might have changed if (timeout || restarting || !/\+(page|layout|server).*$/.test(file)) return; sync.update(svelte_config, manifest_data, file, root); - // TODO: perform a partial update instead of invalidating the whole virtual module? - void invalidate_module(vite, sveltekit_manifest_data); + + const nodes_page_options = manifest_data.nodes.map((node) => node.page_options); + const endpoints_page_options = manifest_data.routes.map( + (route) => route.endpoint?.page_options + ); + vite.environments.ssr.hot.send('sveltekit:manifest-data', { + nodes_page_options, + endpoints_page_options + }); + invalidate_module(vite, sveltekit_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 1fd40eeceb8e..41ee650e421a 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -802,6 +802,20 @@ function kit({ svelte_config, adapter_in_vite_config }) { }; export const mime_types = ${s(get_mime_lookup(manifest_data))}; + + import.meta.hot?.on( + 'sveltekit:manifest-data', + ({ nodes_page_options, endpoints_page_options }) => { + for (let i = 0; i < nodes_page_options.length; i++) { + manifest_data.nodes[i].page_options = nodes_page_options[i]; + } + + for (let i = 0; i < endpoints_page_options.length; i++) { + const endpoint = manifest_data.routes[i].endpoint; + if (endpoint) endpoint.page_options = endpoints_page_options[i]; + } + } + ); `; } diff --git a/packages/kit/src/exports/vite/types.d.ts b/packages/kit/src/exports/vite/types.d.ts index 990961748a6d..ae47b0796b6c 100644 --- a/packages/kit/src/exports/vite/types.d.ts +++ b/packages/kit/src/exports/vite/types.d.ts @@ -1,4 +1,5 @@ import 'vite/types/customEvent.d.ts'; +import type { PageOptions } from './static_analysis/index.js'; declare module 'vite/types/customEvent.d.ts' { interface CustomEventMap { @@ -13,6 +14,10 @@ declare module 'vite/types/customEvent.d.ts' { size: number; data: string; }; + 'sveltekit:manifest-data': { + nodes_page_options: Array; + endpoints_page_options: Array; + } 'sveltekit:ssr-load-module-error': Error; 'sveltekit:prerender-assets': string; } From e4449c7a94f4cfab0b47a0b0857d5d5b59055d15 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Sun, 26 Apr 2026 17:35:37 +0800 Subject: [PATCH 113/117] format --- packages/kit/src/exports/vite/types.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/src/exports/vite/types.d.ts b/packages/kit/src/exports/vite/types.d.ts index ae47b0796b6c..1276f53cf8d1 100644 --- a/packages/kit/src/exports/vite/types.d.ts +++ b/packages/kit/src/exports/vite/types.d.ts @@ -17,7 +17,7 @@ declare module 'vite/types/customEvent.d.ts' { 'sveltekit:manifest-data': { nodes_page_options: Array; endpoints_page_options: Array; - } + }; 'sveltekit:ssr-load-module-error': Error; 'sveltekit:prerender-assets': string; } From 3aa39e5c096df8e32027979f3d2fe98f041f82f8 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Mon, 27 Apr 2026 02:18:09 +0800 Subject: [PATCH 114/117] add comment to explain --- packages/kit/src/exports/vite/build/vite_server.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/kit/src/exports/vite/build/vite_server.js b/packages/kit/src/exports/vite/build/vite_server.js index 37c08d062f79..6d504de83461 100644 --- a/packages/kit/src/exports/vite/build/vite_server.js +++ b/packages/kit/src/exports/vite/build/vite_server.js @@ -27,12 +27,17 @@ import { get_env } from '../utils.js'; import { SVELTE_KIT_ASSETS } from '../../../constants.js'; /** + * Spins up a Vite dev server along with the build output so that we can run + * analysis and prerendering in the environment itself. This helps us avoid + * runtime errors when the user imports non-Node runtime APIs such as `cloudflare:workers`. + * We achieve this by using Vite's `resolveId` hook to intercept module resolution + * and provide a `Server` class that runs our custom instructions. * @param {object} opts * @param {ValidatedConfig} opts.svelte_config * @param {string} opts.out * @param {string} opts.manifest_path - * @param {string} opts.server_path - * @param {PluginOption} [opts.vite_plugins] + * @param {string} opts.server_path path to the module with our custom Server export + * @param {PluginOption} [opts.vite_plugins] additional plugins to customise the Vite behaviour * @returns {Promise} */ export function create_build_server({ From 97e9c08eb0e769b004a382a6afdf590d931fd058 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Mon, 27 Apr 2026 02:27:28 +0800 Subject: [PATCH 115/117] improve docs --- .../docs/25-build-and-deploy/99-writing-adapters.md | 10 +++++----- 1 file changed, 5 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 62ed7acb7bf2..e941118305c2 100644 --- a/documentation/docs/25-build-and-deploy/99-writing-adapters.md +++ b/documentation/docs/25-build-and-deploy/99-writing-adapters.md @@ -59,11 +59,11 @@ 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 server +## Configuring the development and preview experience -By default, SvelteKit runs your server code through a Node.js runtime during development and preview. You can change this by adding a Vite plugin to run code and respond to requests from [a different SSR environment](https://vite.dev/guide/api-environment-runtimes). +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 default Vite environment SvelteKit uses is named `ssr`. You can customise it by referencing it in the `config` hook of your Vite plugin. +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 @@ -72,7 +72,7 @@ config(userConfig) { } ``` -You can also create your own development server entry file by importing `Server` from `sveltekit:server`, `env` from `sveltekit:env`, and `manifest` from `sveltekit:server-manifest`. +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'; @@ -91,7 +91,7 @@ export default { async fetch(request) { return await server.respond(request, { getClientAddress: () => { - return request.headers.get('your-platform-exposes-the-remote-address') + return request.headers.get('how-your-platform-exposes-the-remote-address') } }); } From a5507cb553a84dcc24a9c9e49122ffbcab5fc47f Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Mon, 27 Apr 2026 23:50:57 +0800 Subject: [PATCH 116/117] Apply suggestion from @teemingc --- .changeset/short-chefs-stare.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/short-chefs-stare.md b/.changeset/short-chefs-stare.md index 2d2b4828693e..32bf84b5b390 100644 --- a/.changeset/short-chefs-stare.md +++ b/.changeset/short-chefs-stare.md @@ -2,4 +2,4 @@ '@sveltejs/kit': major --- -feat: use the Vite Environment API in development +feat: use the Vite Environment API in development, preview, build analysis and prerendering From f68d5ce5ec5dec1bccb3adf7be7cfb3d284696e7 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Tue, 28 Apr 2026 04:15:16 +0800 Subject: [PATCH 117/117] fix indentation --- packages/kit/src/exports/vite/build/vite_server.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/kit/src/exports/vite/build/vite_server.js b/packages/kit/src/exports/vite/build/vite_server.js index 6d504de83461..c5bd6c275639 100644 --- a/packages/kit/src/exports/vite/build/vite_server.js +++ b/packages/kit/src/exports/vite/build/vite_server.js @@ -98,16 +98,16 @@ export function create_build_server({ }, handler() { return dedent` - // helps us avoid global fetch warnings we emit when the user uses it incorrectly - const native_fetch = globalThis.fetch; + // helps us avoid global fetch warnings we emit when the user uses it incorrectly + const native_fetch = globalThis.fetch; - export function get(pathname) { - return native_fetch(\`http://localhost:\${port}${app_path}\${pathname}\`); - } + export function get(pathname) { + return native_fetch(\`http://localhost:\${port}${app_path}\${pathname}\`); + } let port${port ? ` = ${port}` : ''}; import.meta.hot?.on('sveltekit:port', (update) => { port = update }); - `; + `; } } };