From 8931c20aaf579d7196cab2e0a2fb0411d1d00a9f Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Tue, 31 Mar 2026 19:08:25 +0800 Subject: [PATCH 1/3] fix and test --- packages/kit/src/exports/vite/index.js | 660 +++++++++--------- packages/kit/test/apps/options/package.json | 4 +- .../test/apps/options/unit-test/vite.spec.js | 19 + .../test/apps/options/vite.custom.config.js | 5 +- pnpm-lock.yaml | 3 + 5 files changed, 364 insertions(+), 327 deletions(-) create mode 100644 packages/kit/test/apps/options/unit-test/vite.spec.js diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 728ee73ae606..54808007c966 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -241,189 +241,192 @@ async function kit({ svelte_config }) { * Build the SvelteKit-provided Vite config to be merged with the user's vite.config.js file. * @see https://vitejs.dev/guide/api-plugin.html#config */ - config(config, config_env) { - initial_config = config; - vite_config_env = config_env; - is_build = config_env.command === 'build'; - - env = get_env(kit.env, vite_config_env.mode); - - const allow = new Set([ - 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(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: cwd, - server: { - cors: { preflightContinue: true }, - fs: { - allow: [...allow] + config: { + order: 'pre', + handler(config, config_env) { + initial_config = config; + vite_config_env = config_env; + is_build = config_env.command === 'build'; + + env = get_env(kit.env, vite_config_env.mode); + + const allow = new Set([ + 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(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: cwd, + server: { + cors: { preflightContinue: true }, + fs: { + allow: [...allow] + }, + sourcemapIgnoreList, + watch: { + ignored: [ + // Ignore all siblings of config.kit.outDir/generated + `${posixify(kit.outDir)}/!(generated)` + ] + } + }, + preview: { + cors: { preflightContinue: true } + }, + optimizeDeps: { + entries: [ + `${kit.files.routes}/**/+*.{svelte,js,ts}`, + `!${kit.files.routes}/**/+*server.*` + ], + 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 + '@sveltejs/kit', + // exclude kit features so that libraries using them work even when they are prebundled + // this does not affect app code, just handling of imported libraries that use $app or $env + '$app', + '$env' + ] }, - sourcemapIgnoreList, - watch: { - ignored: [ - // Ignore all siblings of config.kit.outDir/generated - `${posixify(kit.outDir)}/!(generated)` + ssr: { + noExternal: [ + // 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 from resolving 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', + // This forces `$app/*` modules to be bundled, since they depend on + // virtual modules like `__sveltekit/environment` (this isn't a valid bare + // import, but it works with vite-node's externalization logic, which + // uses basic concatenation) + '@sveltejs/kit/src/runtime' ] } - }, - preview: { - cors: { preflightContinue: true } - }, - optimizeDeps: { - entries: [ - `${kit.files.routes}/**/+*.{svelte,js,ts}`, - `!${kit.files.routes}/**/+*server.*` - ], - 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 - '@sveltejs/kit', - // exclude kit features so that libraries using them work even when they are prebundled - // this does not affect app code, just handling of imported libraries that use $app or $env - '$app', - '$env' - ] - }, - ssr: { - noExternal: [ - // 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 from resolving 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', - // This forces `$app/*` modules to be bundled, since they depend on - // virtual modules like `__sveltekit/environment` (this isn't a valid bare - // import, but it works with vite-node's externalization logic, which - // uses basic concatenation) - '@sveltejs/kit/src/runtime' - ] - } - }; + }; - if (kit.experimental.remoteFunctions) { - // treat .remote.js files as empty for the purposes of prebundling - // detects rolldown to avoid a warning message in vite 8 beta - const remote_id_filter = new RegExp( - `.remote(${kit.moduleExtensions.join('|')})$`.replaceAll('.', '\\.') - ); - new_config.optimizeDeps ??= {}; // for some reason ts says this could be undefined even though it was set above - if (is_rolldown) { - // @ts-ignore - new_config.optimizeDeps.rolldownOptions ??= {}; - // @ts-ignore - new_config.optimizeDeps.rolldownOptions.plugins ??= []; - // @ts-ignore - new_config.optimizeDeps.rolldownOptions.plugins.push({ - name: 'vite-plugin-sveltekit-setup:optimize-remote-functions', - load: { - filter: { id: remote_id_filter }, - handler() { - return ''; + if (kit.experimental.remoteFunctions) { + // treat .remote.js files as empty for the purposes of prebundling + // detects rolldown to avoid a warning message in vite 8 beta + const remote_id_filter = new RegExp( + `.remote(${kit.moduleExtensions.join('|')})$`.replaceAll('.', '\\.') + ); + new_config.optimizeDeps ??= {}; // for some reason ts says this could be undefined even though it was set above + if (is_rolldown) { + // @ts-ignore + new_config.optimizeDeps.rolldownOptions ??= {}; + // @ts-ignore + new_config.optimizeDeps.rolldownOptions.plugins ??= []; + // @ts-ignore + new_config.optimizeDeps.rolldownOptions.plugins.push({ + name: 'vite-plugin-sveltekit-setup:optimize-remote-functions', + load: { + filter: { id: remote_id_filter }, + handler() { + return ''; + } } - } - }); - } else { - new_config.optimizeDeps.esbuildOptions ??= {}; - new_config.optimizeDeps.esbuildOptions.plugins ??= []; - new_config.optimizeDeps.esbuildOptions.plugins.push({ - name: 'vite-plugin-sveltekit-setup:optimize-remote-functions', - setup(build) { - build.onLoad({ filter: remote_id_filter }, () => ({ contents: '' })); - } - }); + }); + } else { + new_config.optimizeDeps.esbuildOptions ??= {}; + new_config.optimizeDeps.esbuildOptions.plugins ??= []; + new_config.optimizeDeps.esbuildOptions.plugins.push({ + name: 'vite-plugin-sveltekit-setup:optimize-remote-functions', + setup(build) { + build.onLoad({ filter: remote_id_filter }, () => ({ contents: '' })); + } + }); + } } - } - const define = { - __SVELTEKIT_APP_DIR__: s(kit.appDir), - __SVELTEKIT_EMBEDDED__: s(kit.embedded), - __SVELTEKIT_FORK_PRELOADS__: s(kit.experimental.forkPreloads), - __SVELTEKIT_PATHS_ASSETS__: s(kit.paths.assets), - __SVELTEKIT_PATHS_BASE__: s(kit.paths.base), - __SVELTEKIT_PATHS_RELATIVE__: s(kit.paths.relative), - __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) - }; - - if (is_build) { - if (!new_config.build) new_config.build = {}; - new_config.build.ssr = !secondary_build_started; - - new_config.define = { - ...define, - __SVELTEKIT_ADAPTER_NAME__: s(kit.adapter?.name), - __SVELTEKIT_APP_VERSION_FILE__: s(`${kit.appDir}/version.json`), - __SVELTEKIT_APP_VERSION_POLL_INTERVAL__: s(kit.version.pollInterval), - __SVELTEKIT_PAYLOAD__: new_config.build.ssr - ? '{}' - : `globalThis.__sveltekit_${version_hash}` + const define = { + __SVELTEKIT_APP_DIR__: s(kit.appDir), + __SVELTEKIT_EMBEDDED__: s(kit.embedded), + __SVELTEKIT_FORK_PRELOADS__: s(kit.experimental.forkPreloads), + __SVELTEKIT_PATHS_ASSETS__: s(kit.paths.assets), + __SVELTEKIT_PATHS_BASE__: s(kit.paths.base), + __SVELTEKIT_PATHS_RELATIVE__: s(kit.paths.relative), + __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) }; - if (!secondary_build_started) { - manifest_data = sync.all(svelte_config, config_env.mode).manifest_data; - // During the initial server build we don't know yet - new_config.define.__SVELTEKIT_HAS_SERVER_LOAD__ = 'true'; - new_config.define.__SVELTEKIT_HAS_UNIVERSAL_LOAD__ = 'true'; + if (is_build) { + if (!new_config.build) new_config.build = {}; + new_config.build.ssr = !secondary_build_started; + + new_config.define = { + ...define, + __SVELTEKIT_ADAPTER_NAME__: s(kit.adapter?.name), + __SVELTEKIT_APP_VERSION_FILE__: s(`${kit.appDir}/version.json`), + __SVELTEKIT_APP_VERSION_POLL_INTERVAL__: s(kit.version.pollInterval), + __SVELTEKIT_PAYLOAD__: new_config.build.ssr + ? '{}' + : `globalThis.__sveltekit_${version_hash}` + }; + + if (!secondary_build_started) { + manifest_data = sync.all(svelte_config, config_env.mode).manifest_data; + // During the initial server build we don't know yet + new_config.define.__SVELTEKIT_HAS_SERVER_LOAD__ = 'true'; + new_config.define.__SVELTEKIT_HAS_UNIVERSAL_LOAD__ = 'true'; + } else { + const nodes = Object.values( + /** @type {import('types').ServerMetadata} */ (build_metadata).nodes + ); + + // Through the finished analysis we can now check if any node has server or universal load functions + const has_server_load = nodes.some((node) => node.has_server_load); + const has_universal_load = nodes.some((node) => node.has_universal_load); + + new_config.define.__SVELTEKIT_HAS_SERVER_LOAD__ = s(has_server_load); + new_config.define.__SVELTEKIT_HAS_UNIVERSAL_LOAD__ = s(has_universal_load); + } } else { - const nodes = Object.values( - /** @type {import('types').ServerMetadata} */ (build_metadata).nodes - ); + new_config.define = { + ...define, + __SVELTEKIT_APP_VERSION_POLL_INTERVAL__: '0', + __SVELTEKIT_PAYLOAD__: 'globalThis.__sveltekit_dev', + __SVELTEKIT_HAS_SERVER_LOAD__: 'true', + __SVELTEKIT_HAS_UNIVERSAL_LOAD__: 'true' + }; - // Through the finished analysis we can now check if any node has server or universal load functions - const has_server_load = nodes.some((node) => node.has_server_load); - const has_universal_load = nodes.some((node) => node.has_universal_load); + // @ts-ignore this prevents a reference error if `client.js` is imported on the server + globalThis.__sveltekit_dev = {}; - new_config.define.__SVELTEKIT_HAS_SERVER_LOAD__ = s(has_server_load); - new_config.define.__SVELTEKIT_HAS_UNIVERSAL_LOAD__ = s(has_universal_load); + // 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', + 'set-cookie-parser' + ]; } - } else { - new_config.define = { - ...define, - __SVELTEKIT_APP_VERSION_POLL_INTERVAL__: '0', - __SVELTEKIT_PAYLOAD__: 'globalThis.__sveltekit_dev', - __SVELTEKIT_HAS_SERVER_LOAD__: 'true', - __SVELTEKIT_HAS_UNIVERSAL_LOAD__: 'true' - }; - // @ts-ignore this prevents a reference error if `client.js` is imported on the server - globalThis.__sveltekit_dev = {}; + warn_overridden_config(config, new_config); - // 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', - 'set-cookie-parser' - ]; + return new_config; } - - warn_overridden_config(config, new_config); - - return new_config; }, /** @@ -814,182 +817,189 @@ async function kit({ svelte_config }) { * Build the SvelteKit-provided Vite config to be merged with the user's vite.config.js file. * @see https://vitejs.dev/guide/api-plugin.html#config */ - config(config) { - /** @type {import('vite').UserConfig} */ - let new_config; - - const kit_paths_base = kit.paths.base || '/'; - - if (is_build) { - const ssr = /** @type {boolean} */ (config.build?.ssr); - const prefix = `${kit.appDir}/immutable`; - - /** @type {Record} */ - const input = {}; - - if (ssr) { - input.index = `${runtime_directory}/server/index.js`; - input.internal = `${kit.outDir}/generated/server/internal.js`; - input['remote-entry'] = `${runtime_directory}/app/server/remote/index.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); + config: { + // avoids overwriting the base setting that's also set by Vitest + order: 'pre', + handler(config) { + /** @type {import('vite').UserConfig} */ + let new_config; + + const kit_paths_base = kit.paths.base || '/'; + + if (is_build) { + const ssr = /** @type {boolean} */ (config.build?.ssr); + const prefix = `${kit.appDir}/immutable`; + + /** @type {Record} */ + const input = {}; + + if (ssr) { + input.index = `${runtime_directory}/server/index.js`; + input.internal = `${kit.outDir}/generated/server/internal.js`; + input['remote-entry'] = `${runtime_directory}/app/server/remote/index.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 = relative.startsWith('..') - ? posixify(path.join('entries/fallbacks', path.basename(file))) - : posixify(path.join('entries/pages', relative.replace(/\.js$/, ''))); + const name = posixify( + path.join('entries/endpoints', 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); - }); + // ...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 the hooks files - if (manifest_data.hooks.server) { - input['entries/hooks.server'] = path.resolve(manifest_data.hooks.server); - } - if (manifest_data.hooks.universal) { - input['entries/hooks.universal'] = path.resolve(manifest_data.hooks.universal); - } + // ...and every matcher + Object.entries(manifest_data.matchers).forEach(([key, file]) => { + const name = posixify(path.join('entries/matchers', key)); + input[name] = path.resolve(file); + }); - // ...and the server instrumentation file - const server_instrumentation = resolve_entry( - path.join(kit.files.src, 'instrumentation.server') - ); - if (server_instrumentation) { - const { adapter } = kit; - if (adapter && !adapter.supports?.instrumentation?.()) { - throw new Error(`${server_instrumentation} is unsupported in ${adapter.name}.`); + // ...and the hooks files + if (manifest_data.hooks.server) { + input['entries/hooks.server'] = path.resolve(manifest_data.hooks.server); } - if (!kit.experimental.instrumentation.server) { - error_for_missing_config( - '`instrumentation.server.js`', - 'kit.experimental.instrumentation.server', - 'true' - ); + if (manifest_data.hooks.universal) { + input['entries/hooks.universal'] = path.resolve(manifest_data.hooks.universal); } - input['instrumentation.server'] = server_instrumentation; - } - } else if (svelte_config.kit.output.bundleStrategy !== 'split') { - input['bundle'] = `${runtime_directory}/client/bundle.js`; - } else { - input['entry/start'] = `${runtime_directory}/client/entry.js`; - input['entry/app'] = `${kit.outDir}/generated/client-optimized/app.js`; - manifest_data.nodes.forEach((node, i) => { - if (node.component || node.universal) { - input[`nodes/${i}`] = `${kit.outDir}/generated/client-optimized/nodes/${i}.js`; + + // ...and the server instrumentation file + const server_instrumentation = resolve_entry( + path.join(kit.files.src, 'instrumentation.server') + ); + if (server_instrumentation) { + const { adapter } = kit; + if (adapter && !adapter.supports?.instrumentation?.()) { + throw new Error(`${server_instrumentation} is unsupported in ${adapter.name}.`); + } + if (!kit.experimental.instrumentation.server) { + error_for_missing_config( + '`instrumentation.server.js`', + 'kit.experimental.instrumentation.server', + 'true' + ); + } + input['instrumentation.server'] = server_instrumentation; } - }); - } + } else if (svelte_config.kit.output.bundleStrategy !== 'split') { + input['bundle'] = `${runtime_directory}/client/bundle.js`; + } else { + input['entry/start'] = `${runtime_directory}/client/entry.js`; + input['entry/app'] = `${kit.outDir}/generated/client-optimized/app.js`; + manifest_data.nodes.forEach((node, i) => { + if (node.component || node.universal) { + input[`nodes/${i}`] = `${kit.outDir}/generated/client-optimized/nodes/${i}.js`; + } + }); + } - // see the kit.output.preloadStrategy option for details on why we have multiple options here - const ext = kit.output.preloadStrategy === 'preload-mjs' ? 'mjs' : '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 - // causing us to do a more expensive hydration check. - const client_base = - kit.paths.relative !== false || kit.paths.assets ? './' : kit_paths_base; - - const inline = !ssr && svelte_config.kit.output.bundleStrategy === 'inline'; - const split = ssr || svelte_config.kit.output.bundleStrategy === 'split'; - - new_config = { - base: ssr ? assets_base(kit) : client_base, - build: { - copyPublicDir: !ssr, - cssCodeSplit: svelte_config.kit.output.bundleStrategy !== 'inline', - cssMinify: initial_config.build?.minify == null ? true : !!initial_config.build.minify, - manifest: true, - outDir: `${out}/${ssr ? 'server' : 'client'}`, - rollupOptions: { - input: inline ? input['bundle'] : input, - output: { - format: inline ? 'iife' : 'esm', - name: `__sveltekit_${version_hash}.app`, - entryFileNames: ssr ? '[name].js' : `${prefix}/[name].[hash].${ext}`, - chunkFileNames: ssr ? 'chunks/[name].js' : `${prefix}/chunks/[hash].${ext}`, - assetFileNames: `${prefix}/assets/[name].[hash][extname]`, - hoistTransitiveImports: false, - sourcemapIgnoreList, - inlineDynamicImports: is_rolldown ? undefined : !split - }, - preserveEntrySignatures: 'strict', - onwarn(warning, handler) { - if ( - (is_rolldown - ? warning.code === 'IMPORT_IS_UNDEFINED' - : warning.code === 'MISSING_EXPORT') && - warning.id === `${kit.outDir}/generated/client-optimized/app.js` - ) { - // ignore e.g. undefined `handleError` hook when - // referencing `client_hooks.handleError` - return; + // see the kit.output.preloadStrategy option for details on why we have multiple options here + const ext = kit.output.preloadStrategy === 'preload-mjs' ? 'mjs' : '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 + // causing us to do a more expensive hydration check. + const client_base = + kit.paths.relative !== false || kit.paths.assets ? './' : kit_paths_base; + + const inline = !ssr && svelte_config.kit.output.bundleStrategy === 'inline'; + const split = ssr || svelte_config.kit.output.bundleStrategy === 'split'; + + new_config = { + base: ssr ? assets_base(kit) : client_base, + build: { + copyPublicDir: !ssr, + cssCodeSplit: svelte_config.kit.output.bundleStrategy !== 'inline', + cssMinify: + initial_config.build?.minify == null ? true : !!initial_config.build.minify, + manifest: true, + outDir: `${out}/${ssr ? 'server' : 'client'}`, + rollupOptions: { + input: inline ? input['bundle'] : input, + output: { + format: inline ? 'iife' : 'esm', + name: `__sveltekit_${version_hash}.app`, + entryFileNames: ssr ? '[name].js' : `${prefix}/[name].[hash].${ext}`, + chunkFileNames: ssr ? 'chunks/[name].js' : `${prefix}/chunks/[hash].${ext}`, + assetFileNames: `${prefix}/assets/[name].[hash][extname]`, + hoistTransitiveImports: false, + sourcemapIgnoreList, + inlineDynamicImports: is_rolldown ? undefined : !split + }, + preserveEntrySignatures: 'strict', + onwarn(warning, handler) { + if ( + (is_rolldown + ? warning.code === 'IMPORT_IS_UNDEFINED' + : warning.code === 'MISSING_EXPORT') && + warning.id === `${kit.outDir}/generated/client-optimized/app.js` + ) { + // ignore e.g. undefined `handleError` hook when + // referencing `client_hooks.handleError` + return; + } + + handler(warning); } - - handler(warning); - } + }, + ssrEmitAssets: true, + target: ssr ? 'node18.13' : undefined }, - ssrEmitAssets: true, - target: ssr ? 'node18.13' : undefined - }, - publicDir: kit.files.assets, - worker: { - rollupOptions: { - output: { - entryFileNames: `${prefix}/workers/[name]-[hash].js`, - chunkFileNames: `${prefix}/workers/chunks/[hash].js`, - assetFileNames: `${prefix}/workers/assets/[name]-[hash][extname]`, - hoistTransitiveImports: false + publicDir: kit.files.assets, + worker: { + rollupOptions: { + output: { + entryFileNames: `${prefix}/workers/[name]-[hash].js`, + chunkFileNames: `${prefix}/workers/chunks/[hash].js`, + assetFileNames: `${prefix}/workers/assets/[name]-[hash][extname]`, + hoistTransitiveImports: false + } } } - } - }; + }; - // we must reference Vite 8 options conditionally. Otherwise, older Vite - // versions throw an error about unknown config options - if (is_rolldown && new_config?.build?.rollupOptions?.output) { - // @ts-ignore only available in Vite 8 - new_config.build.rollupOptions.output.codeSplitting = split; + // we must reference Vite 8 options conditionally. Otherwise, older Vite + // versions throw an error about unknown config options + if (is_rolldown && new_config?.build?.rollupOptions?.output) { + // @ts-ignore only available in Vite 8 + new_config.build.rollupOptions.output.codeSplitting = split; + } + } else { + new_config = { + appType: 'custom', + base: kit_paths_base, + build: { + rollupOptions: { + // Vite dependency crawler needs an explicit JS entry point + // even though server otherwise works without it + input: `${runtime_directory}/client/entry.js` + } + }, + publicDir: kit.files.assets + }; } - } else { - new_config = { - appType: 'custom', - base: kit_paths_base, - build: { - rollupOptions: { - // Vite dependency crawler needs an explicit JS entry point - // even though server otherwise works without it - input: `${runtime_directory}/client/entry.js` - } - }, - publicDir: kit.files.assets - }; - } - warn_overridden_config(config, new_config); + warn_overridden_config(config, new_config); - return new_config; + return new_config; + } }, /** diff --git a/packages/kit/test/apps/options/package.json b/packages/kit/test/apps/options/package.json index 04ce65e2a148..fc97c8a99370 100644 --- a/packages/kit/test/apps/options/package.json +++ b/packages/kit/test/apps/options/package.json @@ -11,6 +11,7 @@ "test": "pnpm test:dev && pnpm test:build", "test:dev": "DEV=true playwright test && DEV=true PATHS_ASSETS=https://cdn.example.com/stuff playwright test", "test:build": "playwright test && PATHS_ASSETS=https://cdn.example.com/stuff playwright test", + "test:unit": "vitest run --config vite.custom.config.js", "test:server-side-route-resolution:dev": "rm -rf test/errors.json && DEV=true ROUTER_RESOLUTION=server playwright test", "test:server-side-route-resolution:build": "rm -rf test/errors.json && ROUTER_RESOLUTION=server playwright test" }, @@ -21,7 +22,8 @@ "svelte": "catalog:", "svelte-check": "catalog:", "typescript": "^5.5.4", - "vite": "catalog:" + "vite": "catalog:", + "vitest": "catalog:" }, "type": "module" } diff --git a/packages/kit/test/apps/options/unit-test/vite.spec.js b/packages/kit/test/apps/options/unit-test/vite.spec.js new file mode 100644 index 000000000000..01c506767ad3 --- /dev/null +++ b/packages/kit/test/apps/options/unit-test/vite.spec.js @@ -0,0 +1,19 @@ +import { test, expect } from 'vitest'; +import { spawnSync } from 'node:child_process'; +import { fileURLToPath } from 'node:url'; +import path from 'node:path'; + +// TODO: change to import.meta.dir in version-3 branch +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const cwd = path.resolve(__dirname, '..'); + +test('no overridden options warning', () => { + const result = spawnSync('vitest', ['run', '--config', './vite.custom.config.js', '-t', 'noop'], { + cwd, + encoding: 'utf-8' + }); + + expect(result.error).toBeUndefined(); + expect(result.stderr).not.toContain('overridden by SvelteKit'); + expect(result.stderr).toBe(''); +}); diff --git a/packages/kit/test/apps/options/vite.custom.config.js b/packages/kit/test/apps/options/vite.custom.config.js index 74a16f7f1ab2..873ed692d5ec 100644 --- a/packages/kit/test/apps/options/vite.custom.config.js +++ b/packages/kit/test/apps/options/vite.custom.config.js @@ -1,7 +1,7 @@ import * as path from 'node:path'; import { sveltekit } from '@sveltejs/kit/vite'; -/** @type {import('vite').UserConfig} */ +/** @type {import('vitest/config').ViteUserConfig} */ const config = { build: { minify: false, @@ -13,6 +13,9 @@ const config = { fs: { allow: [path.resolve('../../../src')] } + }, + test: { + include: ['./unit-test/*.spec.js'] } }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9a033365221a..b376400493ee 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -842,6 +842,9 @@ importers: vite: specifier: 'catalog:' version: 6.4.1(@types/node@18.19.119)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0) + vitest: + specifier: 'catalog:' + version: 4.0.16(@opentelemetry/api@1.9.0)(@types/node@18.19.119)(@vitest/browser-playwright@4.0.16)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0) packages/kit/test/apps/options-2: devDependencies: From 8ee1e19affb9f584c88652806b81d7eb1822a80c Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Tue, 31 Mar 2026 19:09:19 +0800 Subject: [PATCH 2/3] changeset --- .changeset/fluffy-words-wonder.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fluffy-words-wonder.md diff --git a/.changeset/fluffy-words-wonder.md b/.changeset/fluffy-words-wonder.md new file mode 100644 index 000000000000..894c227fb317 --- /dev/null +++ b/.changeset/fluffy-words-wonder.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +fix: avoid false-positive overridden Vite `base` setting warning when setting a `paths.base` in `svelte.config.js` From 5154d6662f1eed95ae07d4e65bc9ac73f20d2140 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 1 Apr 2026 04:41:06 +0000 Subject: [PATCH 3/3] chore: autofix lint --- packages/kit/test/apps/options/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/test/apps/options/package.json b/packages/kit/test/apps/options/package.json index 54ddd7a766d1..3e7c004eea33 100644 --- a/packages/kit/test/apps/options/package.json +++ b/packages/kit/test/apps/options/package.json @@ -23,7 +23,7 @@ "svelte-check": "catalog:", "typescript": "catalog:", "vite": "catalog:", - "vitest": "catalog:" + "vitest": "catalog:" }, "type": "module" }