async handler(_options, bundle) {
				if (secondary_build_started) return; // only run this once

				const verbose = vite_config.logLevel === 'info';
				const log = logger({ verbose });

				/** @type {import('vite').Manifest} */
				const server_manifest = JSON.parse(read(`${out}/server/${vite_config.build.manifest}`));

				/** @type {import('types').BuildData} */
				const build_data = {
					app_dir: kit.appDir,
					app_path: `${kit.paths.base.slice(1)}${kit.paths.base ? '/' : ''}${kit.appDir}`,
					manifest_data,
					out_dir: out,
					service_worker: service_worker_entry_file ? 'service-worker.js' : null, // TODO make file configurable?
					client: null,
					server_manifest
				};

				const manifest_path = `${out}/server/manifest-full.js`;
				fs.writeFileSync(
					manifest_path,
					`export const manifest = ${generate_manifest({
						build_data,
						prerendered: [],
						relative_path: '.',
						routes: manifest_data.routes
					})};\n`
				);

				log.info('Analysing routes');

				const { metadata, static_exports } = await analyse({
					hash: kit.router.type === 'hash',
					manifest_path,
					manifest_data,
					server_manifest,
					tracked_features,
					env: { ...env.private, ...env.public },
					out,
					output_config: svelte_config.output
				});

				log.info('Building app');

				// create client build
				write_client_manifest(
					kit,
					manifest_data,
					`${kit.outDir}/generated/client-optimized`,
					metadata.nodes
				);

				secondary_build_started = true;

				const { output: client_chunks } = /** @type {import('vite').Rollup.RollupOutput} */ (
					await vite.build({
						configFile: vite_config.configFile,
						// CLI args
						mode: vite_config_env.mode,
						logLevel: vite_config.logLevel,
						clearScreen: vite_config.clearScreen,
						build: {
							minify: initial_config.build?.minify,
							assetsInlineLimit: vite_config.build.assetsInlineLimit,
							sourcemap: vite_config.build.sourcemap
						},
						optimizeDeps: {
							force: vite_config.optimizeDeps.force
						}
					})
				);

				copy(
					`${out}/server/${kit.appDir}/immutable/assets`,
					`${out}/client/${kit.appDir}/immutable/assets`
				);

				/** @type {import('vite').Manifest} */
				const client_manifest = JSON.parse(read(`${out}/client/${vite_config.build.manifest}`));

				/**
				 * @param {string} entry
				 * @param {boolean} [add_dynamic_css]
				 */
				const deps_of = (entry, add_dynamic_css = false) =>
					find_deps(client_manifest, posixify(path.relative('.', entry)), add_dynamic_css);

				if (svelte_config.kit.output.bundleStrategy === 'split') {
					const start = deps_of(`${runtime_directory}/client/entry.js`);
					const app = deps_of(`${kit.outDir}/generated/client-optimized/app.js`);

					build_data.client = {
						start: start.file,
						app: app.file,
						imports: [...start.imports, ...app.imports],
						stylesheets: [...start.stylesheets, ...app.stylesheets],
						fonts: [...start.fonts, ...app.fonts],
						uses_env_dynamic_public: client_chunks.some(
							(chunk) => chunk.type === 'chunk' && chunk.modules[env_dynamic_public]
						)
					};

					// 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') {
						const nodes = 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);
								const file = resolve_symlinks(
									client_manifest,
									`${kit.outDir}/generated/client-optimized/nodes/${i}.js`
								).chunk.file;

								return { file, css: deps.stylesheets };
							}
						});
						build_data.client.nodes = nodes.map((node) => node?.file);
						build_data.client.css = nodes.map((node) => node?.css);

						build_data.client.routes = 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 ? [metadata.nodes[l].has_server_load, l] : undefined
									),
									errors: route.page.errors,
									leaf: [metadata.nodes[route.page.leaf].has_server_load, route.page.leaf]
								};
							})
						);
					}
				} else {
					const start = deps_of(`${runtime_directory}/client/bundle.js`);

					build_data.client = {
						start: start.file,
						imports: start.imports,
						stylesheets: start.stylesheets,
						fonts: start.fonts,
						uses_env_dynamic_public: client_chunks.some(
							(chunk) => chunk.type === 'chunk' && chunk.modules[env_dynamic_public]
						)
					};

					if (svelte_config.kit.output.bundleStrategy === 'inline') {
						const style = /** @type {import('rollup').OutputAsset} */ (
							client_chunks.find(
								(chunk) =>
									chunk.type === 'asset' &&
									chunk.names.length === 1 &&
									chunk.names[0] === 'style.css'
							)
						);

						build_data.client.inline = {
							script: read(`${out}/client/${start.file}`),
							style: /** @type {string | undefined} */ (style?.source)
						};
					}
				}

				// regenerate manifest now that we have client entry...
				fs.writeFileSync(
					manifest_path,
					`export const manifest = ${generate_manifest({
						build_data,
						prerendered: [],
						relative_path: '.',
						routes: manifest_data.routes
					})};\n`
				);

				// regenerate nodes with the client manifest...
				await build_server_nodes(
					out,
					kit,
					manifest_data,
					server_manifest,
					client_manifest,
					bundle,
					client_chunks,
					svelte_config.kit.output,
					static_exports
				);

				// ...and prerender
				const { prerendered, prerender_map } = await prerender({
					hash: kit.router.type === 'hash',
					out,
					manifest_path,
					metadata,
					verbose,
					env: { ...env.private, ...env.public }
				});

				// generate a new manifest that doesn't include prerendered pages
				fs.writeFileSync(
					`${out}/server/manifest.js`,
					`export const manifest = ${generate_manifest({
						build_data,
						prerendered: prerendered.paths,
						relative_path: '.',
						routes: manifest_data.routes.filter((route) => prerender_map.get(route.id) !== true)
					})};\n`
				);

				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');

					await build_service_worker(
						out,
						kit,
						{
							...vite_config,
							build: {
								...vite_config.build,
								minify: initial_config.build?.minify ?? 'esbuild'
							}
						},
						manifest_data,
						service_worker_entry_file,
						prerendered,
						client_manifest
					);
				}

				// we need to defer this to closeBundle, so that adapters copy files
				// created by other Vite plugins
				finalise = async () => {
					console.log(
						`\nRun ${colors
							.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_map,
							log,
							vite_config
						);
					} else {
						console.log(colors.bold().yellow('\nNo adapter specified'));

						const link = colors.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`
						);
					}

					secondary_build_started = false;
				};
			}
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 server build. We
then use this hook to kick off builds for the client and service worker.