Repository: sveltejs/kit
Pull request #8590 (2023-01-18 23:05:40)

change dependency workspace:^
this will hopefully prevent changesets PRs like https://github.com/sveltejs/kit/pull/8588 from failing

changeset-bot[bot]: ###  ⚠️  No Changeset found

Latest commit: b69440cb742f7fa0517fd2f9111a31beb1f9721a

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. **If these changes should result in a version bump, you need to add a changeset.**

<details><summary>This PR includes no changesets</summary>

  When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

</details>

[Click here to learn what changesets are, and how to add one](https://github.com/changesets/changesets/blob/master/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/sveltejs/kit/new/fix-adapter-vercel-dependency?filename=.changeset/shy-elephants-vanish.md&value=---%0A%22kit.svelte.dev%22%3A%20patch%0A---%0A%0Achange%20dependency%20workspace%3A%5E%0A)



Patch:
diff --git a/packages/kit/src/exports/vite/build/build_server.js b/packages/kit/src/exports/vite/build/build_server.js
index 12614cce1..940f7719f 100644
--- a/packages/kit/src/exports/vite/build/build_server.js
+++ b/packages/kit/src/exports/vite/build/build_server.js
@@ -1,17 +1,9 @@
-import fs from 'fs';
-import path from 'path';
-import { mergeConfig } from 'vite';
+import fs from 'node:fs';
+import path from 'node:path';
+import * as vite from 'vite';
 import { mkdirp, posixify } from '../../../utils/filesystem.js';
-import { get_vite_config } from '../utils.js';
-import {
-	create_build,
-	find_deps,
-	get_default_build_config,
-	is_http_method,
-	resolve_symlinks
-} from './utils.js';
+import { find_deps, is_http_method, resolve_symlinks } from './utils.js';
 import { s } from '../../../utils/misc.js';
-import { runtime_directory } from '../../../core/utils.js';
 
 /**
  * @param {{
@@ -26,49 +18,22 @@ import { runtime_directory } from '../../../core/utils.js';
 export async function build_server(options, client) {
 	const { config, vite_config, vite_config_env, manifest_data, output_dir } = options;
 
-	/** @type {Record<string, string>} */
-	const input = {
-		index: `${runtime_directory}/server/index.js`,
-		internal: `${config.kit.outDir}/generated/server-internal.js`
-	};
-
-	// add entry points for every endpoint...
-	manifest_data.routes.forEach((route) => {
-		if (route.endpoint) {
-			const resolved = path.resolve(route.endpoint.file);
-			const relative = decodeURIComponent(path.relative(config.kit.files.routes, resolved));
-			const name = posixify(path.join('entries/endpoints', relative.replace(/\.js$/, '')));
-			input[name] = resolved;
-		}
-	});
-
-	// ...and every component used by pages...
-	manifest_data.nodes.forEach((node) => {
-		for (const file of [node.component, node.universal, node.server]) {
-			if (file) {
-				const resolved = path.resolve(file);
-				const relative = decodeURIComponent(path.relative(config.kit.files.routes, resolved));
-
-				const name = relative.startsWith('..')
-					? posixify(path.join('entries/fallbacks', path.basename(file)))
-					: posixify(path.join('entries/pages', relative.replace(/\.js$/, '')));
-				input[name] = resolved;
+	const { output } = /** @type {import('rollup').RollupOutput} */ (
+		await vite.build({
+			// CLI args
+			configFile: vite_config.configFile,
+			mode: vite_config_env.mode,
+			logLevel: config.logLevel,
+			clearScreen: config.clearScreen,
+			build: {
+				ssr: true
 			}
-		}
-	});
-
-	// ...and every matcher
-	Object.entries(manifest_data.matchers).forEach(([key, file]) => {
-		const name = posixify(path.join('entries/matchers', key));
-		input[name] = path.resolve(file);
-	});
-
-	const merged_config = mergeConfig(
-		get_default_build_config({ config, input, ssr: true, outDir: `${output_dir}/server` }),
-		await get_vite_config(vite_config, vite_config_env)
+		})
 	);
 
-	const { chunks } = await create_build(merged_config);
+	const chunks = /** @type {import('rollup').OutputChunk[]} */ (
+		output.filter((chunk) => chunk.type === 'chunk')
+	);
 
 	/** @type {import('vite').Manifest} */
 	const vite_manifest = JSON.parse(
diff --git a/packages/kit/src/exports/vite/build/utils.js b/packages/kit/src/exports/vite/build/utils.js
index bf4751b83..f1af2cdc1 100644
--- a/packages/kit/src/exports/vite/build/utils.js
+++ b/packages/kit/src/exports/vite/build/utils.js
@@ -1,33 +1,5 @@
-import fs from 'fs';
-import path from 'path';
-import * as vite from 'vite';
-import { get_config_aliases, get_app_aliases } from '../utils.js';
-
-/**
- * @typedef {import('rollup').RollupOutput} RollupOutput
- * @typedef {import('rollup').OutputChunk} OutputChunk
- * @typedef {import('rollup').OutputAsset} OutputAsset
- */
-
-/**
- * Invokes Vite.
- * @param {import('vite').UserConfig} config
- */
-export async function create_build(config) {
-	const { output } = /** @type {RollupOutput} */ (
-		await vite.build({ ...config, configFile: false })
-	);
-
-	const chunks = output.filter(
-		/** @returns {output is OutputChunk} */ (output) => output.type === 'chunk'
-	);
-
-	const assets = output.filter(
-		/** @returns {output is OutputAsset} */ (output) => output.type === 'asset'
-	);
-
-	return { chunks, assets };
-}
+import fs from 'node:fs';
+import path from 'node:path';
 
 /**
  * Adds transitive JS and CSS dependencies to the js and css inputs.
@@ -107,111 +79,6 @@ export function resolve_symlinks(manifest, file) {
 	return { chunk, file };
 }
 
-/**
- * Partial Vite configuration that we use by default for setting up the build.
- * Can be used in a non-SvelteKit Vite server and build process such as Storybook.
- * @param {{
- *   config: import('types').ValidatedConfig;
- *   ssr: boolean;
- * }} options
- * @return {import('vite').UserConfig}
- */
-export function get_build_setup_config({ config, ssr }) {
-	return {
-		build: {
-			// don't use the default name to avoid collisions with 'static/manifest.json'
-			manifest: 'vite-manifest.json',
-			ssr
-		},
-		define: {
-			__SVELTEKIT_ADAPTER_NAME__: JSON.stringify(config.kit.adapter?.name),
-			__SVELTEKIT_APP_VERSION_FILE__: JSON.stringify(`${config.kit.appDir}/version.json`),
-			__SVELTEKIT_APP_VERSION_POLL_INTERVAL__: JSON.stringify(config.kit.version.pollInterval),
-			__SVELTEKIT_DEV__: 'false',
-			__SVELTEKIT_EMBEDDED__: config.kit.embedded ? 'true' : 'false'
-		},
-		resolve: {
-			alias: [...get_app_aliases(config.kit), ...get_config_aliases(config.kit)]
-		},
-		optimizeDeps: {
-			exclude: ['@sveltejs/kit']
-		},
-		ssr: {
-			noExternal: [
-				// TODO document why this is necessary
-				'@sveltejs/kit',
-				// This ensures that esm-env is inlined into the server output with the
-				// export conditions resolved correctly through Vite. This prevents adapters
-				// that bundle later on to resolve the export conditions incorrectly
-				// and for example include browser-only code in the server output
-				// because they for example use esbuild.build with `platform: 'browser'`
-				'esm-env'
-			]
-		}
-	};
-}
-
-/**
- * Partial Vite configuration that we use by default for setting up the build.
- * Cannot be used in a non-SvelteKit Vite server and build process such as Storybook.
- * @param {{
- *   config: import('types').ValidatedConfig;
- *   input: Record<string, string>;
- *   ssr: boolean;
- *   outDir: string;
- * }} options
- * @return {import('vite').UserConfig}
- */
-export function get_build_compile_config({ config, input, ssr, outDir }) {
-	const prefix = `${config.kit.appDir}/immutable`;
-
-	return {
-		appType: 'custom',
-		base: ssr ? assets_base(config.kit) : './',
-		build: {
-			cssCodeSplit: true,
-			outDir,
-			rollupOptions: {
-				input,
-				output: {
-					format: 'esm',
-					entryFileNames: ssr ? '[name].js' : `${prefix}/[name]-[hash].js`,
-					chunkFileNames: ssr ? 'chunks/[name].js' : `${prefix}/chunks/[name]-[hash].js`,
-					assetFileNames: `${prefix}/assets/[name]-[hash][extname]`,
-					hoistTransitiveImports: false
-				},
-				preserveEntrySignatures: 'strict'
-			},
-			target: ssr ? 'node16.14' : undefined
-		},
-		publicDir: ssr ? false : config.kit.files.assets,
-		worker: {
-			rollupOptions: {
-				output: {
-					entryFileNames: `${prefix}/workers/[name]-[hash].js`,
-					chunkFileNames: `${prefix}/workers/chunks/[name]-[hash].js`,
-					assetFileNames: `${prefix}/workers/assets/[name]-[hash][extname]`,
-					hoistTransitiveImports: false
-				}
-			}
-		}
-	};
-}
-
-/**
- * The Vite configuration that we use by default for building.
- * @param {{
- *   config: import('types').ValidatedConfig;
- *   input: Record<string, string>;
- *   ssr: boolean;
- *   outDir: string;
- * }} options
- * @return {import('vite').UserConfig}
- */
-export function get_default_build_config(options) {
-	return vite.mergeConfig(get_build_setup_config(options), get_build_compile_config(options));
-}
-
 /**
  * @param {import('types').ValidatedKitConfig} config
  * @returns {string}
diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js
index f20be5190..21b7f7bc7 100644
--- a/packages/kit/src/exports/vite/dev/index.js
+++ b/packages/kit/src/exports/vite/dev/index.js
@@ -408,15 +408,16 @@ export async function dev(vite, vite_config, svelte_config) {
 					return;
 				}
 
+				// we have to import `Server` before calling `set_paths`
+				const { Server } = /** @type {import('types').ServerModule} */ (
+					await vite.ssrLoadModule(`${runtime_base}/server/index.js`)
+				);
+
 				const { set_paths, set_version, set_fix_stack_trace } =
 					/** @type {import('types').ServerInternalModule} */ (
 						await vite.ssrLoadModule(`${runtime_base}/shared.js`)
 					);
 
-				const { Server } = /** @type {import('types').ServerModule} */ (
-					await vite.ssrLoadModule(`${runtime_base}/server/index.js`)
-				);
-
 				set_paths({
 					base: svelte_config.kit.paths.base,
 					assets
diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js
index bc0506edd..409f2399b 100644
--- a/packages/kit/src/exports/vite/index.js
+++ b/packages/kit/src/exports/vite/index.js
@@ -8,20 +8,19 @@ import colors from 'kleur';
 import * as vite from 'vite';
 
 import { mkdirp, posixify, resolve_entry, rimraf } from '../../utils/filesystem.js';
-import { SVELTE_KIT_ASSETS } from '../../constants.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_base, runtime_directory, logger } from '../../core/utils.js';
+import { runtime_directory, logger } from '../../core/utils.js';
 import { load_config } from '../../core/config/index.js';
 import { generate_manifest } from '../../core/generate_manifest/index.js';
 import { build_server } from './build/build_server.js';
 import { build_service_worker } from './build/build_service_worker.js';
-import { find_deps, get_build_setup_config, get_build_compile_config } from './build/utils.js';
+import { assets_base, find_deps } from './build/utils.js';
 import { dev } from './dev/index.js';
 import { is_illegal, module_guard, normalize_id } from './graph_analysis/index.js';
 import { preview } from './preview/index.js';
-import { get_config_aliases, get_app_aliases, get_env } from './utils.js';
+import { get_config_aliases, get_env } from './utils.js';
 
 export { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
 
@@ -153,6 +152,9 @@ export async function sveltekit() {
  * @return {import('vite').Plugin[]}
  */
 function kit({ svelte_config }) {
+	const { kit } = svelte_config;
+	const out = `${kit.outDir}/output`;
+
 	/** @type {import('vite').ResolvedConfig} */
 	let vite_config;
 
@@ -180,78 +182,8 @@ function kit({ svelte_config }) {
 	/** @type {{ public: Record<string, string>; private: Record<string, string> }} */
 	let env;
 
-	/**
-	 * @type {{
-	 *   output_dir: string;
-	 *   client_out_dir: string;
-	 * }}
-	 */
-	let paths;
-
 	let completed_build = false;
 
-	function vite_client_build_config() {
-		/** @type {Record<string, string>} */
-		const input = {
-			// Put unchanging assets in immutable directory. We don't set that in the
-			// outDir so that other plugins can add mutable assets to the bundle
-			start: `${runtime_directory}/client/start.js`
-		};
-
-		manifest_data.nodes.forEach((node) => {
-			if (node.component) {
-				const resolved = path.resolve(node.component);
-				const relative = decodeURIComponent(
-					path.relative(svelte_config.kit.files.routes, resolved)
-				);
-
-				const name = relative.startsWith('..')
-					? path.basename(node.component)
-					: posixify(path.join('pages', relative));
-				input[`components/${name}`] = resolved;
-			}
-
-			if (node.universal) {
-				const resolved = path.resolve(node.universal);
-				const relative = decodeURIComponent(
-					path.relative(svelte_config.kit.files.routes, resolved)
-				);
-
-				const name = relative.startsWith('..')
-					? path.basename(node.universal)
-					: posixify(path.join('pages', relative));
-				input[`modules/${name}`] = resolved;
-			}
-		});
-
-		return get_build_compile_config({
-			config: svelte_config,
-			input,
-			ssr: false,
-			outDir: `${paths.client_out_dir}`
-		});
-	}
-
-	/**
-	 * @param {import('rollup').OutputAsset[]} assets
-	 * @param {import('rollup').OutputChunk[]} chunks
-	 */
-	function client_build_info(assets, chunks) {
-		/** @type {import('vite').Manifest} */
-		const vite_manifest = JSON.parse(
-			fs.readFileSync(`${paths.client_out_dir}/${vite_config.build.manifest}`, 'utf-8')
-		);
-
-		const entry_id = posixify(path.relative('.', `${runtime_directory}/client/start.js`));
-
-		return {
-			assets,
-			chunks,
-			entry: find_deps(vite_manifest, entry_id, false),
-			vite_manifest
-		};
-	}
-
 	/** @type {import('vite').Plugin} */
 	const plugin_setup = {
 		name: 'vite-plugin-sveltekit-setup',
@@ -262,55 +194,33 @@ function kit({ svelte_config }) {
 		 */
 		async config(config, config_env) {
 			vite_config_env = config_env;
-
-			env = get_env(svelte_config.kit.env, vite_config_env.mode);
-
-			// The config is created in build_server for SSR mode and passed inline
-			if (config.build?.ssr) return;
-
 			is_build = config_env.command === 'build';
 
-			paths = {
-				output_dir: `${svelte_config.kit.outDir}/output`,
-				client_out_dir: `${svelte_config.kit.outDir}/output/client`
-			};
-
-			if (is_build) {
-				manifest_data = (await sync.all(svelte_config, config_env.mode)).manifest_data;
-
-				const new_config = get_build_setup_config({ config: svelte_config, ssr: false });
-
-				const warning = warn_overridden_config(config, new_config);
-				if (warning) console.error(warning + '\n');
-
-				return new_config;
-			}
+			env = get_env(kit.env, vite_config_env.mode);
 
 			const allow = new Set([
-				svelte_config.kit.files.lib,
-				svelte_config.kit.files.routes,
-				svelte_config.kit.outDir,
+				kit.files.lib,
+				kit.files.routes,
+				kit.outDir,
 				path.resolve('src'), // TODO this isn't correct if user changed all his files to sth else than src (like in test/options)
 				path.resolve('node_modules'),
 				path.resolve(vite.searchForWorkspaceRoot(cwd), 'node_modules')
 			]);
+
 			// We can only add directories to the allow list, so we find out
 			// if there's a client hooks file and pass its directory
-			const client_hooks = resolve_entry(svelte_config.kit.files.hooks.client);
-			if (client_hooks) {
-				allow.add(path.dirname(client_hooks));
-			}
+			const client_hooks = resolve_entry(kit.files.hooks.client);
+			if (client_hooks) allow.add(path.dirname(client_hooks));
 
 			// dev and preview config can be shared
 			/** @type {import('vite').UserConfig} */
-			const result = {
-				define: {
-					__SVELTEKIT_APP_VERSION_POLL_INTERVAL__: '0',
-					__SVELTEKIT_DEV__: config_env.command === 'serve',
-					__SVELTEKIT_EMBEDDED__: svelte_config.kit.embedded ? 'true' : 'false'
-				},
+			const new_config = {
 				resolve: {
-					alias: [...get_app_aliases(svelte_config.kit), ...get_config_aliases(svelte_config.kit)]
+					alias: [
+						{ find: '__GENERATED__', replacement: path.posix.join(kit.outDir, 'generated') },
+						{ find: '$app', replacement: `${runtime_directory}/app` },
+						...get_config_aliases(kit)
+					]
 				},
 				root: cwd,
 				server: {
@@ -320,17 +230,10 @@ function kit({ svelte_config }) {
 					watch: {
 						ignored: [
 							// Ignore all siblings of config.kit.outDir/generated
-							`${posixify(svelte_config.kit.outDir)}/!(generated)`
+							`${posixify(kit.outDir)}/!(generated)`
 						]
 					}
 				},
-				ssr: {
-					// Without this, Vite will treat `@sveltejs/kit` as noExternal if it's
-					// a linked dependency, and that causes modules to be imported twice
-					// under different IDs, which breaks a bunch of stuff
-					// https://github.com/vitejs/vite/pull/9296
-					external: ['@sveltejs/kit', 'cookie', 'set-cookie-parser']
-				},
 				optimizeDeps: {
 					exclude: [
 						'@sveltejs/kit',
@@ -342,12 +245,64 @@ function kit({ svelte_config }) {
 				}
 			};
 
-			const warning = warn_overridden_config(config, result);
-			if (warning) console.error(warning);
+			if (is_build) {
+				new_config.define = {
+					__SVELTEKIT_ADAPTER_NAME__: JSON.stringify(kit.adapter?.name),
+					__SVELTEKIT_APP_VERSION_FILE__: JSON.stringify(`${kit.appDir}/version.json`),
+					__SVELTEKIT_APP_VERSION_POLL_INTERVAL__: JSON.stringify(kit.version.pollInterval),
+					__SVELTEKIT_DEV__: 'false',
+					__SVELTEKIT_EMBEDDED__: kit.embedded ? 'true' : 'false'
+				};
+
+				new_config.ssr = {
+					noExternal: [
+						// TODO document why this is necessary
+						'@sveltejs/kit',
+						// This ensures that esm-env is inlined into the server output with the
+						// export conditions resolved correctly through Vite. This prevents adapters
+						// that bundle later on to resolve the export conditions incorrectly
+						// and for example include browser-only code in the server output
+						// because they for example use esbuild.build with `platform: 'browser'`
+						'esm-env'
+					]
+				};
+			} else {
+				new_config.define = {
+					__SVELTEKIT_APP_VERSION_POLL_INTERVAL__: '0',
+					__SVELTEKIT_DEV__: 'true',
+					__SVELTEKIT_EMBEDDED__: kit.embedded ? 'true' : 'false'
+				};
+
+				new_config.ssr = {
+					// Without this, Vite will treat `@sveltejs/kit` as noExternal if it's
+					// a linked dependency, and that causes modules to be imported twice
+					// under different IDs, which breaks a bunch of stuff
+					// https://github.com/vitejs/vite/pull/9296
+					external: ['@sveltejs/kit', 'cookie', 'set-cookie-parser']
+				};
+			}
+
+			warn_overridden_config(config, new_config);
 
-			return result;
+			return new_config;
 		},
 
+		/**
+		 * Stores the final config.
+		 */
+		configResolved(config) {
+			vite_config = config;
+
+			// This is a hack to prevent Vite from nuking useful logs,
+			// pending https://github.com/vitejs/vite/issues/9378
+			config.logger.warn('');
+		}
+	};
+
+	/** @type {import('vite').Plugin} */
+	const plugin_virtual_modules = {
+		name: 'vite-plugin-sveltekit-virtual-modules',
+
 		async resolveId(id) {
 			// treat $env/static/[public|private] as virtual
 			if (id.startsWith('$env/') || id === '$service-worker') return `\0${id}`;
@@ -356,7 +311,7 @@ function kit({ svelte_config }) {
 		async load(id, options) {
 			if (options?.ssr === false && process.env.TEST !== 'true') {
 				const normalized_cwd = vite.normalizePath(cwd);
-				const normalized_lib = vite.normalizePath(svelte_config.kit.files.lib);
+				const normalized_lib = vite.normalizePath(kit.files.lib);
 				if (
 					is_illegal(id, {
 						cwd: normalized_cwd,
@@ -387,62 +342,6 @@ function kit({ svelte_config }) {
 				case '\0$service-worker':
 					return create_service_worker_module(svelte_config);
 			}
-		},
-
-		/**
-		 * Stores the final config.
-		 */
-		configResolved(config) {
-			vite_config = config;
-
-			// This is a hack to prevent Vite from nuking useful logs,
-			// pending https://github.com/vitejs/vite/issues/9378
-			config.logger.warn('');
-		},
-
-		/**
-		 * Clears the output directories.
-		 */
-		buildStart() {
-			if (vite_config.build.ssr) return;
-
-			// Reset for new build. Goes here because `build --watch` calls buildStart but not config
-			completed_build = false;
-
-			if (is_build) {
-				if (!vite_config.build.watch) {
-					rimraf(paths.output_dir);
-				}
-				mkdirp(paths.output_dir);
-			}
-		},
-
-		generateBundle() {
-			if (vite_config.build.ssr) return;
-
-			this.emitFile({
-				type: 'asset',
-				fileName: `${svelte_config.kit.appDir}/version.json`,
-				source: JSON.stringify({ version: svelte_config.kit.version.name })
-			});
-		},
-
-		/**
-		 * @see https://vitejs.dev/guide/api-plugin.html#configureserver
-		 */
-		async configureServer(vite) {
-			const { set_paths, set_version } = await vite.ssrLoadModule(`${runtime_base}/shared.js`);
-
-			// set `import { base, assets } from '$app/paths'`
-			const { base, assets } = svelte_config.kit.paths;
-
-			set_paths({
-				base,
-				assets: assets ? SVELTE_KIT_ASSETS : base
-			});
-
-			// set `import { version } from '$app/environment'`
-			set_version(svelte_config.kit.version.name);
 		}
 	};
 
@@ -455,37 +354,129 @@ function kit({ svelte_config }) {
 		 * @see https://vitejs.dev/guide/api-plugin.html#config
 		 */
 		async config(config, config_env) {
-			// The config is created in build_server for SSR mode and passed inline
-			if (config.build?.ssr) return;
+			/** @type {import('vite').UserConfig} */
+			let new_config;
 
-			if (config_env.command === 'build') {
-				const new_config = vite_client_build_config();
+			if (is_build) {
+				manifest_data = (await sync.all(svelte_config, config_env.mode)).manifest_data;
 
-				const warning = warn_overridden_config(config, new_config);
-				if (warning) console.error(warning + '\n');
+				const ssr = config.build?.ssr ?? false;
+				const prefix = `${kit.appDir}/immutable`;
 
-				return new_config;
-			}
+				/** @type {Record<string, string>} */
+				const input = {};
 
-			// dev and preview config can be shared
-			/** @type {import('vite').UserConfig} */
-			const result = {
-				appType: 'custom',
-				base: svelte_config.kit.paths.base,
-				build: {
-					rollupOptions: {
-						// Vite dependency crawler needs an explicit JS entry point
-						// eventhough server otherwise works without it
-						input: `${runtime_directory}/client/start.js`
+				if (ssr) {
+					input.index = `${runtime_directory}/server/index.js`;
+					input.internal = `${kit.outDir}/generated/server-internal.js`;
+
+					// add entry points for every endpoint...
+					manifest_data.routes.forEach((route) => {
+						if (route.endpoint) {
+							const resolved = path.resolve(route.endpoint.file);
+							const relative = decodeURIComponent(path.relative(kit.files.routes, resolved));
+							const name = posixify(path.join('entries/endpoints', relative.replace(/\.js$/, '')));
+							input[name] = resolved;
+						}
+					});
+
+					// ...and every component used by pages...
+					manifest_data.nodes.forEach((node) => {
+						for (const file of [node.component, node.universal, node.server]) {
+							if (file) {
+								const resolved = path.resolve(file);
+								const relative = decodeURIComponent(path.relative(kit.files.routes, resolved));
+
+								const name = relative.startsWith('..')
+									? posixify(path.join('entries/fallbacks', path.basename(file)))
+									: posixify(path.join('entries/pages', relative.replace(/\.js$/, '')));
+								input[name] = resolved;
+							}
+						}
+					});
+
+					// ...and every matcher
+					Object.entries(manifest_data.matchers).forEach(([key, file]) => {
+						const name = posixify(path.join('entries/matchers', key));
+						input[name] = path.resolve(file);
+					});
+				} else {
+					/** @type {Record<string, string>} */
+					input.start = `${runtime_directory}/client/start.js`;
+
+					manifest_data.nodes.forEach((node) => {
+						if (node.component) {
+							const resolved = path.resolve(node.component);
+							const relative = decodeURIComponent(path.relative(kit.files.routes, resolved));
+
+							const name = relative.startsWith('..')
+								? path.basename(node.component)
+								: posixify(path.join('pages', relative));
+							input[`components/${name}`] = resolved;
+						}
+
+						if (node.universal) {
+							const resolved = path.resolve(node.universal);
+							const relative = decodeURIComponent(path.relative(kit.files.routes, resolved));
+
+							const name = relative.startsWith('..')
+								? path.basename(node.universal)
+								: posixify(path.join('pages', relative));
+							input[`modules/${name}`] = resolved;
+						}
+					});
+				}
+
+				new_config = {
+					base: ssr ? assets_base(kit) : './',
+					build: {
+						cssCodeSplit: true,
+						outDir: `${out}/${ssr ? 'server' : 'client'}`,
+						rollupOptions: {
+							input,
+							output: {
+								format: 'esm',
+								entryFileNames: ssr ? '[name].js' : `${prefix}/[name]-[hash].js`,
+								chunkFileNames: ssr ? 'chunks/[name].js' : `${prefix}/chunks/[name]-[hash].js`,
+								assetFileNames: `${prefix}/assets/[name]-[hash][extname]`,
+								hoistTransitiveImports: false
+							},
+							preserveEntrySignatures: 'strict'
+						},
+						target: ssr ? 'node16.14' : undefined,
+						// don't use the default name to avoid collisions with 'static/manifest.json'
+						manifest: 'vite-manifest.json'
+					},
+					publicDir: ssr ? false : kit.files.assets,
+					worker: {
+						rollupOptions: {
+							output: {
+								entryFileNames: `${prefix}/workers/[name]-[hash].js`,
+								chunkFileNames: `${prefix}/workers/chunks/[name]-[hash].js`,
+								assetFileNames: `${prefix}/workers/assets/[name]-[hash][extname]`,
+								hoistTransitiveImports: false
+							}
+						}
 					}
-				},
-				publicDir: svelte_config.kit.files.assets
-			};
+				};
+			} else {
+				new_config = {
+					appType: 'custom',
+					base: kit.paths.base,
+					build: {
+						rollupOptions: {
+							// Vite dependency crawler needs an explicit JS entry point
+							// eventhough server otherwise works without it
+							input: `${runtime_directory}/client/start.js`
+						}
+					},
+					publicDir: kit.files.assets
+				};
+			}
 
-			const warning = warn_overridden_config(config, result);
-			if (warning) console.error(warning);
+			warn_overridden_config(config, new_config);
 
-			return result;
+			return new_config;
 		},
 
 		/**
@@ -504,6 +495,33 @@ function kit({ svelte_config }) {
 			return preview(vite, vite_config, svelte_config);
 		},
 
+		/**
+		 * Clears the output directories.
+		 */
+		buildStart() {
+			if (vite_config.build.ssr) return;
+
+			// Reset for new build. Goes here because `build --watch` calls buildStart but not config
+			completed_build = false;
+
+			if (is_build) {
+				if (!vite_config.build.watch) {
+					rimraf(out);
+				}
+				mkdirp(out);
+			}
+		},
+
+		generateBundle() {
+			if (vite_config.build.ssr) return;
+
+			this.emitFile({
+				type: 'asset',
+				fileName: `${kit.appDir}/version.json`,
+				source: JSON.stringify({ version: kit.version.name })
+			});
+		},
+
 		/**
 		 * Vite builds a single bundle. We need three bundles: client, server, and service worker.
 		 * The user's package.json scripts will invoke the Vite CLI to execute the client build. We
@@ -516,13 +534,11 @@ function kit({ svelte_config }) {
 
 				const guard = module_guard(this, {
 					cwd: vite.normalizePath(process.cwd()),
-					lib: vite.normalizePath(svelte_config.kit.files.lib)
+					lib: vite.normalizePath(kit.files.lib)
 				});
 
 				manifest_data.nodes.forEach((_node, i) => {
-					const id = vite.normalizePath(
-						path.resolve(svelte_config.kit.outDir, `generated/nodes/${i}.js`)
-					);
+					const id = vite.normalizePath(path.resolve(kit.outDir, `generated/nodes/${i}.js`));
 
 					guard.check(id);
 				});
@@ -544,26 +560,40 @@ function kit({ svelte_config }) {
 					vite_config,
 					vite_config_env,
 					manifest_data,
-					output_dir: paths.output_dir
+					output_dir: out
 				};
-				const client = client_build_info(assets, chunks);
+
+				/** @type {import('vite').Manifest} */
+				const vite_manifest = JSON.parse(
+					fs.readFileSync(`${out}/client/${vite_config.build.manifest}`, 'utf-8')
+				);
+
+				const client = {
+					assets,
+					chunks,
+					entry: find_deps(
+						vite_manifest,
+						posixify(path.relative('.', `${runtime_directory}/client/start.js`)),
+						false
+					),
+					vite_manifest
+				};
+
 				const server = await build_server(options, client);
 
-				const service_worker_entry_file = resolve_entry(svelte_config.kit.files.serviceWorker);
+				const service_worker_entry_file = resolve_entry(kit.files.serviceWorker);
 
 				/** @type {import('types').BuildData} */
 				build_data = {
-					app_dir: svelte_config.kit.appDir,
-					app_path: `${svelte_config.kit.paths.base.slice(1)}${
-						svelte_config.kit.paths.base ? '/' : ''
-					}${svelte_config.kit.appDir}`,
+					app_dir: kit.appDir,
+					app_path: `${kit.paths.base.slice(1)}${kit.paths.base ? '/' : ''}${kit.appDir}`,
 					manifest_data,
 					service_worker: !!service_worker_entry_file ? 'service-worker.js' : null, // TODO make file configurable?
 					client,
 					server
 				};
 
-				const manifest_path = `${paths.output_dir}/server/manifest-full.js`;
+				const manifest_path = `${out}/server/manifest-full.js`;
 				fs.writeFileSync(
 					manifest_path,
 					`export const manifest = ${generate_manifest({
@@ -575,7 +605,7 @@ function kit({ svelte_config }) {
 
 				log.info('Prerendering');
 				await new Promise((fulfil, reject) => {
-					const results_path = `${svelte_config.kit.outDir}/generated/prerendered.json`;
+					const results_path = `${kit.outDir}/generated/prerendered.json`;
 
 					// do prerendering in a subprocess so any dangling stuff gets killed upon completion
 					const script = fileURLToPath(new URL('../../core/postbuild/index.js', import.meta.url));
@@ -615,7 +645,7 @@ function kit({ svelte_config }) {
 
 				// generate a new manifest that doesn't include prerendered pages
 				fs.writeFileSync(
-					`${paths.output_dir}/server/manifest.js`,
+					`${out}/server/manifest.js`,
 					`export const manifest = ${generate_manifest({
 						build_data,
 						relative_path: '.',
@@ -624,7 +654,7 @@ function kit({ svelte_config }) {
 				);
 
 				if (service_worker_entry_file) {
-					if (svelte_config.kit.paths.assets) {
+					if (kit.paths.assets) {
 						throw new Error('Cannot use service worker alongside config.kit.paths.assets');
 					}
 
@@ -659,7 +689,7 @@ function kit({ svelte_config }) {
 					return;
 				}
 
-				if (svelte_config.kit.adapter) {
+				if (kit.adapter) {
 					const { adapt } = await import('../../core/adapt/index.js');
 					await adapt(svelte_config, build_data, prerendered, prerender_map, { log });
 				} else {
@@ -672,13 +702,13 @@ function kit({ svelte_config }) {
 				}
 
 				// avoid making the manifest available to users
-				fs.unlinkSync(`${paths.output_dir}/client/${vite_config.build.manifest}`);
-				fs.unlinkSync(`${paths.output_dir}/server/${vite_config.build.manifest}`);
+				fs.unlinkSync(`${out}/client/${vite_config.build.manifest}`);
+				fs.unlinkSync(`${out}/server/${vite_config.build.manifest}`);
 			}
 		}
 	};
 
-	return [plugin_setup, plugin_compile];
+	return [plugin_setup, plugin_virtual_modules, plugin_compile];
 }
 
 /** @param {import('rollup').OutputBundle} bundle */
@@ -706,9 +736,9 @@ function warn_overridden_config(config, resolved_config) {
 	const overridden = find_overridden_config(config, resolved_config, enforced_config, '', []);
 
 	if (overridden.length > 0) {
-		return (
+		console.error(
 			colors.bold().red('The following Vite config options will be overridden by SvelteKit:') +
-			overridden.map((key) => `\n  - ${key}`).join('')
+				overridden.map((key) => `\n  - ${key}`).join('')
 		);
 	}
 }
diff --git a/packages/kit/src/exports/vite/utils.js b/packages/kit/src/exports/vite/utils.js
index 1d040ff31..db05c969a 100644
--- a/packages/kit/src/exports/vite/utils.js
+++ b/packages/kit/src/exports/vite/utils.js
@@ -1,80 +1,8 @@
 import path from 'path';
-import { loadConfigFromFile, loadEnv, mergeConfig } from 'vite';
-import { runtime_directory } from '../../core/utils.js';
+import { loadEnv } from 'vite';
 import { posixify } from '../../utils/filesystem.js';
 import { negotiate } from '../../utils/http.js';
 
-/**
- * @param {import('vite').ResolvedConfig} config
- * @param {import('vite').ConfigEnv} config_env
- * @return {Promise<import('vite').UserConfig>}
- */
-export async function get_vite_config(config, config_env) {
-	const loaded = await loadConfigFromFile(
-		config_env,
-		config.configFile,
-		undefined,
-		config.logLevel
-	);
-
-	if (!loaded) {
-		throw new Error('Could not load Vite config');
-	}
-	return mergeConfig(loaded.config, {
-		// CLI opts
-		mode: config_env.mode,
-		logLevel: config.logLevel,
-		clearScreen: config.clearScreen,
-		optimizeDeps: { force: config.optimizeDeps.force }
-	});
-}
-
-/**
- * Takes zero or more objects and returns a new object that has all the values
- * deeply merged together. None of the original objects will be mutated at any
- * level, and the returned object will have no references to the original
- * objects at any depth. If there's a conflict the last one wins, except for
- * arrays which will be combined.
- * @param {...Object} objects
- * @returns {Record<string, any>} the merged object
- */
-export function deep_merge(...objects) {
-	const result = {};
-	/** @type {string[]} */
-	objects.forEach((o) => merge_into(result, o));
-	return result;
-}
-
-/**
- * Merges b into a, recursively, mutating a.
- * @param {Record<string, any>} a
- * @param {Record<string, any>} b
- */
-function merge_into(a, b) {
-	/**
-	 * Checks for "plain old Javascript object", typically made as an object
-	 * literal. Excludes Arrays and built-in types like Buffer.
-	 * @param {any} x
-	 */
-	const is_plain_object = (x) => typeof x === 'object' && x.constructor === Object;
-
-	for (const prop in b) {
-		if (is_plain_object(b[prop])) {
-			if (!is_plain_object(a[prop])) {
-				a[prop] = {};
-			}
-			merge_into(a[prop], b[prop]);
-		} else if (Array.isArray(b[prop])) {
-			if (!Array.isArray(a[prop])) {
-				a[prop] = [];
-			}
-			a[prop].push(...b[prop]);
-		} else {
-			a[prop] = b[prop];
-		}
-	}
-}
-
 /**
  * Transforms kit.alias to a valid vite.resolve.alias array.
  *
@@ -115,21 +43,6 @@ export function get_config_aliases(config) {
 	return alias;
 }
 
-/**
- * Returns Vite aliases for generated and runtime files.
- *
- * @param {import('types').ValidatedKitConfig} config
- * */
-export function get_app_aliases(config) {
-	/** @type {import('vite').Alias[]} */
-	const alias = [
-		{ find: '__GENERATED__', replacement: path.posix.join(config.outDir, 'generated') },
-		{ find: '$app', replacement: `${runtime_directory}/app` }
-	];
-
-	return alias;
-}
-
 /**
  * @param {string} str
  */
diff --git a/packages/kit/src/exports/vite/utils.spec.js b/packages/kit/src/exports/vite/utils.spec.js
index 690b7710f..bfd83e4e6 100644
--- a/packages/kit/src/exports/vite/utils.spec.js
+++ b/packages/kit/src/exports/vite/utils.spec.js
@@ -3,175 +3,7 @@ import { test } from 'uvu';
 import * as assert from 'uvu/assert';
 import { validate_config } from '../../core/config/index.js';
 import { posixify } from '../../utils/filesystem.js';
-import { deep_merge, get_app_aliases, get_config_aliases } from './utils.js';
-
-test('basic test no conflicts', async () => {
-	const merged = deep_merge(
-		{
-			version: 1,
-			animalSounds: {
-				cow: 'moo'
-			}
-		},
-		{
-			animalSounds: {
-				duck: 'quack'
-			},
-			locale: 'en_US'
-		}
-	);
-
-	assert.equal(merged, {
-		version: 1,
-		locale: 'en_US',
-		animalSounds: {
-			cow: 'moo',
-			duck: 'quack'
-		}
-	});
-});
-
-test('three way merge no conflicts', async () => {
-	const merged = deep_merge(
-		{
-			animalSounds: {
-				cow: 'moo'
-			}
-		},
-		{
-			animalSounds: {
-				duck: 'quack'
-			}
-		},
-		{
-			animalSounds: {
-				dog: {
-					singular: 'bark',
-					plural: 'barks'
-				}
-			}
-		}
-	);
-	assert.equal(merged, {
-		animalSounds: {
-			cow: 'moo',
-			duck: 'quack',
-			dog: {
-				singular: 'bark',
-				plural: 'barks'
-			}
-		}
-	});
-});
-
-test('merge with conflicts', async () => {
-	const merged = deep_merge(
-		{
-			person: {
-				firstName: 'John',
-				lastName: 'Doe',
-				address: {
-					line1: '123 Main St',
-					city: 'Seattle',
-					state: 'WA'
-				}
-			}
-		},
-		{
-			person: {
-				middleInitial: 'Q',
-				address: '123 Main St, Seattle, WA'
-			}
-		}
-	);
-	assert.equal(merged, {
-		person: {
-			firstName: 'John',
-			middleInitial: 'Q',
-			lastName: 'Doe',
-			address: '123 Main St, Seattle, WA'
-		}
-	});
-});
-
-test('merge with arrays', async () => {
-	const merged = deep_merge(
-		{
-			paths: ['/foo', '/bar']
-		},
-		{
-			paths: ['/alpha', '/beta']
-		}
-	);
-	assert.equal(merged, {
-		paths: ['/foo', '/bar', '/alpha', '/beta']
-	});
-});
-
-test('empty', async () => {
-	const merged = deep_merge();
-	assert.equal(merged, {});
-});
-
-test('mutability safety', () => {
-	const input1 = {
-		person: {
-			firstName: 'John',
-			lastName: 'Doe',
-			address: {
-				line1: '123 Main St',
-				city: 'Seattle'
-			}
-		}
-	};
-	const input2 = {
-		person: {
-			middleInitial: 'L',
-			lastName: 'Smith',
-			address: {
-				state: 'WA'
-			}
-		}
-	};
-	const snapshot1 = JSON.stringify(input1);
-	const snapshot2 = JSON.stringify(input2);
-
-	const merged = deep_merge(input1, input2);
-
-	// Mess with the result
-	merged.person.middleInitial = 'Z';
-	merged.person.address.zipCode = '98103';
-	merged.person = {};
-
-	// Make sure nothing in the inputs changed
-	assert.snapshot(snapshot1, JSON.stringify(input1));
-	assert.snapshot(snapshot2, JSON.stringify(input2));
-});
-
-test('merge buffer', () => {
-	const merged = deep_merge(
-		{
-			x: Buffer.from('foo', 'utf-8')
-		},
-		{
-			y: 12345
-		}
-	);
-	assert.equal(Object.keys(merged), ['x', 'y']);
-});
-
-test('merge including toString', () => {
-	const merged = deep_merge(
-		{
-			toString: () => '',
-			constructor: () => ''
-		},
-		{
-			y: 12345
-		}
-	);
-	assert.equal(Object.keys(merged), ['toString', 'constructor', 'y']);
-});
+import { get_config_aliases } from './utils.js';
 
 test('transform kit.alias to resolve.alias', () => {
 	const config = validate_config({
@@ -186,8 +18,7 @@ test('transform kit.alias to resolve.alias', () => {
 		}
 	});
 
-	// combine aliases from config with generated and runtime aliases
-	const aliases = [...get_app_aliases(config.kit), ...get_config_aliases(config.kit)];
+	const aliases = get_config_aliases(config.kit);
 
 	const transformed = aliases.map((entry) => {
 		const replacement = posixify(path.relative('.', entry.replacement));
@@ -199,8 +30,6 @@ test('transform kit.alias to resolve.alias', () => {
 	});
 
 	assert.equal(transformed, [
-		{ find: '__GENERATED__', replacement: '.svelte-kit/generated' },
-		{ find: '$app', replacement: 'src/runtime/app' },
 		{ find: '$lib', replacement: 'src/lib' },
 		{ find: 'simpleKey', replacement: 'simple/value' },
 		{ find: /^key$/.toString(), replacement: 'value' },
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 36bdea80f..9a072ab17 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -682,7 +682,7 @@ importers:
 
   sites/kit.svelte.dev:
     specifiers:
-      '@sveltejs/adapter-vercel': workspace:^1.0.4
+      '@sveltejs/adapter-vercel': workspace:^
       '@sveltejs/amp': workspace:^
       '@sveltejs/kit': workspace:^
       '@sveltejs/site-kit': ^3.1.0
diff --git a/sites/kit.svelte.dev/package.json b/sites/kit.svelte.dev/package.json
index fa89f6627..74011f10e 100644
--- a/sites/kit.svelte.dev/package.json
+++ b/sites/kit.svelte.dev/package.json
@@ -10,7 +10,7 @@
 		"test": "uvu src \"(spec\\.js|test[\\\\/]index\\.js)\""
 	},
 	"devDependencies": {
-		"@sveltejs/adapter-vercel": "workspace:^1.0.4",
+		"@sveltejs/adapter-vercel": "workspace:^",
 		"@sveltejs/amp": "workspace:^",
 		"@sveltejs/kit": "workspace:^",
 		"@sveltejs/site-kit": "^3.1.0",


Test patch:
diff --git a/packages/kit/test/apps/basics/src/routes/load/no-server-load/+layout.svelte b/packages/kit/test/apps/basics/src/routes/load/no-server-load/+layout.svelte
new file mode 100644
index 000000000..c551b1022
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/load/no-server-load/+layout.svelte
@@ -0,0 +1,4 @@
+<a href="/load/no-server-load/a">a</a>
+<a href="/load/no-server-load/b">b</a>
+
+<slot />
diff --git a/packages/kit/test/apps/basics/src/routes/load/no-server-load/a/+page.server.js b/packages/kit/test/apps/basics/src/routes/load/no-server-load/a/+page.server.js
new file mode 100644
index 000000000..77ab0a02e
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/load/no-server-load/a/+page.server.js
@@ -0,0 +1,1 @@
+export const ssr = true;
diff --git a/packages/kit/test/apps/basics/src/routes/load/no-server-load/a/+page.svelte b/packages/kit/test/apps/basics/src/routes/load/no-server-load/a/+page.svelte
new file mode 100644
index 000000000..57f9d4079
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/load/no-server-load/a/+page.svelte
@@ -0,0 +1,1 @@
+<h1>a</h1>
diff --git a/packages/kit/test/apps/basics/src/routes/load/no-server-load/b/+page.server.js b/packages/kit/test/apps/basics/src/routes/load/no-server-load/b/+page.server.js
new file mode 100644
index 000000000..77ab0a02e
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/load/no-server-load/b/+page.server.js
@@ -0,0 +1,1 @@
+export const ssr = true;
diff --git a/packages/kit/test/apps/basics/src/routes/load/no-server-load/b/+page.svelte b/packages/kit/test/apps/basics/src/routes/load/no-server-load/b/+page.svelte
new file mode 100644
index 000000000..e96ed3db8
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/load/no-server-load/b/+page.svelte
@@ -0,0 +1,1 @@
+<h1>b</h1>
diff --git a/packages/kit/test/apps/basics/test/client.test.js b/packages/kit/test/apps/basics/test/client.test.js
index f4fa88f80..9338d77f3 100644
--- a/packages/kit/test/apps/basics/test/client.test.js
+++ b/packages/kit/test/apps/basics/test/client.test.js
@@ -661,6 +661,21 @@ test.describe('Load', () => {
 			);
 		});
 	}
+
+	if (!process.env.DEV) {
+		test.skip('does not fetch __data.json if no server load function exists', async ({
+			page,
+			clicknav
+		}) => {
+			await page.goto('/load/no-server-load/a');
+
+			const pathnames = [];
+			page.on('request', (r) => pathnames.push(new URL(r.url()).pathname));
+			await clicknav('[href="/load/no-server-load/b"]');
+
+			expect(pathnames).not.toContain(`/load/no-server-load/b/__data.json`);
+		});
+	}
 });
 
 test.describe('Page options', () => {