From 30ae1c7a327a29c272c3690ff50edb4bc29fa4b2 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 21 Jul 2026 10:57:58 -0400 Subject: [PATCH 01/34] breaking: overhaul tsconfig --- packages/kit/src/cli.js | 16 +-- packages/kit/src/constants.js | 2 +- packages/kit/src/core/sync/sync.js | 16 +-- packages/kit/src/core/sync/write_env.js | 9 +- .../kit/src/core/sync/write_non_ambient.js | 19 ++-- packages/kit/src/core/sync/write_tsconfig.js | 97 ++++++------------- 6 files changed, 62 insertions(+), 97 deletions(-) diff --git a/packages/kit/src/cli.js b/packages/kit/src/cli.js index eb99f6f257e8..fd7e219d9cc6 100755 --- a/packages/kit/src/cli.js +++ b/packages/kit/src/cli.js @@ -75,19 +75,19 @@ if (!command) { } if (command === 'sync') { - // create placeholder .svelte-kit/tsconfig.json if necessary, to squelch warnings. + // create placeholder node_modules/$app/tsconfig/tsconfig.json if necessary, to squelch warnings. // this isn't bulletproof — if someone has some esoteric config, it will continue // to harmlessly warn — but we handle the 90% case and clean up after ourselves - const sveltekit_dir = '.svelte-kit'; - const base_tsconfig = `${sveltekit_dir}/tsconfig.json`; + const dir = 'node_modules/$app/tsconfig'; + const base_tsconfig = `${dir}/tsconfig.json`; const base_tsconfig_json = '{}'; - const sveltekit_dir_exists = fs.existsSync(sveltekit_dir); + const sveltekit_dir_exists = fs.existsSync(dir); const base_tsconfig_exists = fs.existsSync(base_tsconfig); if (!base_tsconfig_exists) { try { - fs.mkdirSync('.svelte-kit'); + fs.mkdirSync(dir, { recursive: true }); } catch { // ignore } @@ -100,7 +100,7 @@ if (command === 'sync') { const sveltekit_config = extract_svelte_config(vite_config); const sync = await import('./core/sync/sync.js'); - sync.all_types(sveltekit_config); + sync.all_types(sveltekit_config, vite_config.root); const explicit_env_entry = resolve_explicit_env_entry(sveltekit_config.kit); await sync.env(sveltekit_config.kit, explicit_env_entry, vite_config.root, values.mode); @@ -113,8 +113,8 @@ if (command === 'sync') { fs.unlinkSync(base_tsconfig); } - if (!sveltekit_dir_exists && fs.readdirSync(sveltekit_dir).length === 0) { - fs.rmSync(sveltekit_dir, { recursive: true }); + if (!sveltekit_dir_exists && fs.readdirSync(dir).length === 0) { + fs.rmSync(dir, { recursive: true }); } } } else { diff --git a/packages/kit/src/constants.js b/packages/kit/src/constants.js index af1704f08390..de40d97c04f7 100644 --- a/packages/kit/src/constants.js +++ b/packages/kit/src/constants.js @@ -4,7 +4,7 @@ */ export const SVELTE_KIT_ASSETS = '/_svelte_kit_assets'; -export const GENERATED_COMMENT = '// this file is generated — do not edit it\n'; +export const GENERATED_COMMENT = '// this file is generated — do not edit it'; export const ENDPOINT_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'HEAD']; diff --git a/packages/kit/src/core/sync/sync.js b/packages/kit/src/core/sync/sync.js index 0adf76e3c5ac..cbc6a525bb18 100644 --- a/packages/kit/src/core/sync/sync.js +++ b/packages/kit/src/core/sync/sync.js @@ -37,7 +37,7 @@ export function create(config, root) { write_client_manifest(config.kit, manifest_data, `${output}/client`); write_server(config, output, root); write_all_types(config, manifest_data, root); - write_non_ambient(config.kit, manifest_data); + write_non_ambient(config.kit, manifest_data, root); return { manifest_data }; } @@ -81,13 +81,13 @@ export function all(config, root) { /** * Run sync.init and then generate all type files. * @param {import('types').ValidatedConfig} config + * @param {string} root */ -export function all_types(config) { - const cwd = process.cwd(); - init(config, cwd); - const manifest_data = create_manifest_data({ config, cwd }); - write_all_types(config, manifest_data, cwd); - write_non_ambient(config.kit, manifest_data); +export function all_types(config, root) { + init(config, root); + const manifest_data = create_manifest_data({ config, cwd: root }); + write_all_types(config, manifest_data, root); + write_non_ambient(config.kit, manifest_data, root); } /** @@ -100,7 +100,7 @@ export function all_types(config) { export async function env(kit, entry, root, mode) { const env_config = await load_explicit_env(kit, entry, root, mode); - write_env(kit, entry, env_config); + write_env(kit, entry, env_config, root); return env_config; } diff --git a/packages/kit/src/core/sync/write_env.js b/packages/kit/src/core/sync/write_env.js index 1a6946a81982..160806cc5dc0 100644 --- a/packages/kit/src/core/sync/write_env.js +++ b/packages/kit/src/core/sync/write_env.js @@ -13,13 +13,16 @@ const DOCS = '// See https://svelte.dev/docs/kit/environment-variables for more * @param {import('types').ValidatedKitConfig} kit * @param {string | null} entry * @param {Record> | null} env_config + * @param {string} root */ -export function write_env(kit, entry, env_config) { +export function write_env(kit, entry, env_config, root) { const content = []; - const out = path.join(kit.outDir, 'env.d.ts'); + + const dir = path.join(root, 'node_modules/$app/types'); + const out = path.join(dir, 'env.d.ts'); if (entry && env_config) { - const relative = posixify(path.relative(kit.outDir, entry)); + const relative = posixify(path.relative(dir, entry)); content.push( `// This file is generated from ${relative}.\n${DOCS}`, create_explicit_env_types(env_config, relative, 'private'), diff --git a/packages/kit/src/core/sync/write_non_ambient.js b/packages/kit/src/core/sync/write_non_ambient.js index 3010cc281f7f..1ff6578be0c0 100644 --- a/packages/kit/src/core/sync/write_non_ambient.js +++ b/packages/kit/src/core/sync/write_non_ambient.js @@ -51,8 +51,6 @@ function get_pathnames_for_trailing_slash(pathname, route) { // people could use to type their own components. // The T generic is needed or else there's a "all declarations must have identical type parameters" error. const template = ` -${GENERATED_COMMENT} - declare module "svelte/elements" { export interface HTMLAttributes { 'data-sveltekit-keepfocus'?: true | false | '' | undefined | null; @@ -72,8 +70,6 @@ declare module "svelte/elements" { 'data-sveltekit-replacestate'?: true | false | '' | undefined | null; } } - -export {}; `; /** @@ -256,10 +252,15 @@ function generate_app_types(manifest_data, config) { * Writes non-ambient declarations to the output directory * @param {import('types').ValidatedKitConfig} config * @param {import('types').ManifestData} manifest_data + * @param {string} root */ -export function write_non_ambient(config, manifest_data) { - const app_types = generate_app_types(manifest_data, config); - const content = [template, app_types].join('\n\n'); - - write_if_changed(path.join(config.outDir, 'non-ambient.d.ts'), content); +export function write_non_ambient(config, manifest_data, root) { + const content = [ + GENERATED_COMMENT, + `import '@sveltejs/kit';\nimport './env';`, + generate_app_types(manifest_data, config), + template.trim() + ].join('\n\n'); + + write_if_changed(path.join(root, 'node_modules/$app/types/index.d.ts'), content); } diff --git a/packages/kit/src/core/sync/write_tsconfig.js b/packages/kit/src/core/sync/write_tsconfig.js index 324a7f33690d..5c7045c155e4 100644 --- a/packages/kit/src/core/sync/write_tsconfig.js +++ b/packages/kit/src/core/sync/write_tsconfig.js @@ -4,6 +4,7 @@ import { styleText } from 'node:util'; import { posixify } from '../../utils/os.js'; import { read_package_imports, normalize_import_value } from '../../utils/imports.js'; import { write_if_changed } from './utils.js'; +import { exclude } from 'rolldown/filter'; /** * @param {string} cwd @@ -38,77 +39,31 @@ function remove_trailing_slashstar(file) { /** * Generates the tsconfig that the user's tsconfig inherits from. * @param {import('types').ValidatedKitConfig} kit - * @param {string} cwd + * @param {string} root */ -export function write_tsconfig(kit, cwd) { - const out = path.join(kit.outDir, 'tsconfig.json'); +export function write_tsconfig(kit, root) { + const out = path.join(root, 'node_modules/$app/tsconfig/tsconfig.json'); - const user_config = load_user_tsconfig(cwd); - if (user_config) validate_user_config(cwd, out, user_config); + const user_config = load_user_tsconfig(root); + if (user_config) validate_user_config(user_config); - write_if_changed(out, JSON.stringify(get_tsconfig(kit, cwd), null, '\t')); + write_if_changed(out, JSON.stringify(get_tsconfig(out, kit, root), null, '\t')); } /** * Generates the tsconfig that the user's tsconfig inherits from. + * @param {string} out The file we're writing to * @param {import('types').ValidatedKitConfig} kit * @param {string} cwd */ -export function get_tsconfig(kit, cwd) { +export function get_tsconfig(out, kit, cwd) { /** @param {string} file */ - const config_relative = (file) => posixify(path.relative(kit.outDir, file)); - - const include = new Set([ - 'ambient.d.ts', // careful: changing this name would be a breaking change, because it's referenced in the service-workers documentation - 'env.d.ts', - 'non-ambient.d.ts', - './types/**/$types.d.ts', - config_relative('vite.config.js'), - config_relative('vite.config.ts') - ]); - const src_includes = [kit.files.routes, kit.files.src].filter((dir) => { - const relative = path.relative(kit.files.src, dir); - return !relative || relative.startsWith('..'); - }); - for (const dir of src_includes) { - include.add(config_relative(`${dir}/**/*.js`)); - include.add(config_relative(`${dir}/**/*.ts`)); - include.add(config_relative(`${dir}/**/*.svelte`)); - } - - // Test folder is a special case - we advocate putting tests in a top-level test folder - // and it's not configurable (should we make it?) - const test_folder = project_relative(cwd, 'test'); - include.add(config_relative(`${test_folder}/**/*.js`)); - include.add(config_relative(`${test_folder}/**/*.ts`)); - include.add(config_relative(`${test_folder}/**/*.svelte`)); - const tests_folder = project_relative(cwd, 'tests'); - include.add(config_relative(`${tests_folder}/**/*.js`)); - include.add(config_relative(`${tests_folder}/**/*.ts`)); - include.add(config_relative(`${tests_folder}/**/*.svelte`)); - - const exclude = [config_relative('node_modules/**')]; - // Add service worker to exclude list so that worker types references in it don't spill over into the rest of the app - // (i.e. suddenly ServiceWorkerGlobalScope would be available throughout the app, and some types might even clash) - if (path.extname(kit.files.serviceWorker)) { - exclude.push(config_relative(kit.files.serviceWorker)); - } else { - exclude.push(config_relative(`${kit.files.serviceWorker}.js`)); - exclude.push(config_relative(`${kit.files.serviceWorker}/**/*.js`)); - exclude.push(config_relative(`${kit.files.serviceWorker}.ts`)); - exclude.push(config_relative(`${kit.files.serviceWorker}/**/*.ts`)); - exclude.push(config_relative(`${kit.files.serviceWorker}.d.ts`)); - exclude.push(config_relative(`${kit.files.serviceWorker}/**/*.d.ts`)); - } + const config_relative = (file) => posixify(path.relative(path.dirname(out), file)); const config = { compilerOptions: { - // generated options - paths: { - ...get_tsconfig_paths(kit, cwd), - '$app/types': ['./types/index.d.ts'] - }, - rootDirs: [config_relative('.'), './types'], + rootDirs: [config_relative('.'), config_relative(`${kit.outDir}/types`)], + types: ['$app/types'], // essential options // svelte-preprocess cannot figure out whether you have a value or a type, so tell TypeScript @@ -119,6 +74,12 @@ export function get_tsconfig(kit, cwd) { // Vite compiles modules one at a time isolatedModules: true, + // recommended options + allowJs: true, + checkJs: true, + forceConsistentCasingInFileNames: true, + resolveJsonModule: true, + // This is required for svelte-package to work as expected // Can be overwritten lib: ['esnext', 'DOM', 'DOM.Iterable'], @@ -127,8 +88,13 @@ export function get_tsconfig(kit, cwd) { noEmit: true, // prevent tsconfig error "overwriting input files" - Vite handles the build and ignores this target: 'esnext' }, - include: [...include], - exclude + exclude: [ + config_relative( + path.extname(kit.files.serviceWorker) + ? kit.files.serviceWorker + : `${kit.files.serviceWorker}/**` + ) + ] }; return kit.typescript.config(config) ?? config; @@ -150,18 +116,16 @@ function load_user_tsconfig(cwd) { } /** - * @param {string} cwd - * @param {string} out * @param {{ kind: string, options: any }} config */ -function validate_user_config(cwd, out, config) { +function validate_user_config(config) { // we need to check that the user's tsconfig extends the framework config const extend = config.options.extends; const extends_framework_config = typeof extend === 'string' - ? path.resolve(cwd, extend) === out + ? extend === '$app/tsconfig' : Array.isArray(extend) - ? extend.some((e) => path.resolve(cwd, e) === out) + ? extend.includes('$app/tsconfig') : false; const options = config.options.compilerOptions || {}; @@ -180,16 +144,13 @@ function validate_user_config(cwd, out, config) { ); } } else { - let relative = posixify(path.relative(cwd, out)); - if (!relative.startsWith('./')) relative = './' + relative; - console.warn( styleText( ['bold', 'yellow'], `Your ${config.kind} should extend the configuration generated by SvelteKit:` ) ); - console.warn(`{\n "extends": "${relative}"\n}`); + console.warn(`{\n "extends": "$app/tsconfig"\n}`); } } From 7226b1737bdcefc7dd19bc9d5e45c0bc8e684f09 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 21 Jul 2026 11:40:35 -0400 Subject: [PATCH 02/34] WIP --- packages/kit/src/core/sync/write_tsconfig.js | 1 + .../kit/src/core/sync/write_tsconfig.spec.js | 47 ++++--------------- .../kit/src/core/sync/write_types/index.js | 2 +- .../src/core/sync/write_types/index.spec.js | 2 +- 4 files changed, 11 insertions(+), 41 deletions(-) diff --git a/packages/kit/src/core/sync/write_tsconfig.js b/packages/kit/src/core/sync/write_tsconfig.js index 5c7045c155e4..205875862aec 100644 --- a/packages/kit/src/core/sync/write_tsconfig.js +++ b/packages/kit/src/core/sync/write_tsconfig.js @@ -62,6 +62,7 @@ export function get_tsconfig(out, kit, cwd) { const config = { compilerOptions: { + paths: get_tsconfig_paths(kit, cwd), rootDirs: [config_relative('.'), config_relative(`${kit.outDir}/types`)], types: ['$app/types'], diff --git a/packages/kit/src/core/sync/write_tsconfig.spec.js b/packages/kit/src/core/sync/write_tsconfig.spec.js index 3234717dfecc..0a661c4c6976 100644 --- a/packages/kit/src/core/sync/write_tsconfig.spec.js +++ b/packages/kit/src/core/sync/write_tsconfig.spec.js @@ -16,12 +16,11 @@ test('Creates tsconfig path aliases from kit.alias', () => { }); // Use a cwd without a package.json so no `#`-prefixed imports are picked up - const { compilerOptions } = get_tsconfig(kit, import.meta.dirname); + const { compilerOptions } = get_tsconfig('dir/tsconfig.json', kit, import.meta.dirname); // No `#`-prefixed path aliases because the package.json at the test cwd // doesn't have an `imports` field expect(compilerOptions.paths).toEqual({ - '$app/types': ['./types/index.d.ts'], simpleKey: ['../simple/value'], 'simpleKey/*': ['../simple/value/*'], key: ['../value'], @@ -34,12 +33,15 @@ test('Creates tsconfig path aliases from kit.alias', () => { test('Creates tsconfig path aliases from package.json import map', () => { const { kit } = validate_config({}); - const { compilerOptions } = get_tsconfig(kit, import.meta.dirname + '/write_tsconfig_test'); + const { compilerOptions } = get_tsconfig( + 'dir/tsconfig.json', + kit, + import.meta.dirname + '/write_tsconfig_test' + ); // No `#`-prefixed path aliases because the package.json at the test cwd // doesn't have an `imports` field expect(compilerOptions.paths).toEqual({ - '$app/types': ['./types/index.d.ts'], '#lib': ['../src/lib'], '#lib/*': ['../src/lib/*'] }); @@ -56,7 +58,7 @@ test('Allows generated tsconfig to be mutated', () => { } }); - const config = get_tsconfig(kit, '.'); + const config = get_tsconfig('dir/tsconfig.json', kit, '.'); // @ts-expect-error assert.equal(config.extends, 'some/other/tsconfig.json'); @@ -74,41 +76,8 @@ test('Allows generated tsconfig to be replaced', () => { } }); - const config = get_tsconfig(kit, '.'); + const config = get_tsconfig('dir/tsconfig.json', kit, '.'); // @ts-expect-error assert.equal(config.extends, 'some/other/tsconfig.json'); }); - -test('Creates tsconfig include from kit.files', () => { - const { kit } = validate_config({ - kit: { - files: { - routes: 'app' - } - } - }); - - const { include } = get_tsconfig(kit, '.'); - - expect(include).toEqual([ - 'ambient.d.ts', - 'env.d.ts', - 'non-ambient.d.ts', - './types/**/$types.d.ts', - '../vite.config.js', - '../vite.config.ts', - '../app/**/*.js', - '../app/**/*.ts', - '../app/**/*.svelte', - '../src/**/*.js', - '../src/**/*.ts', - '../src/**/*.svelte', - '../test/**/*.js', - '../test/**/*.ts', - '../test/**/*.svelte', - '../tests/**/*.js', - '../tests/**/*.ts', - '../tests/**/*.svelte' - ]); -}); diff --git a/packages/kit/src/core/sync/write_types/index.js b/packages/kit/src/core/sync/write_types/index.js index 05b4561a240b..184766fbf85c 100644 --- a/packages/kit/src/core/sync/write_types/index.js +++ b/packages/kit/src/core/sync/write_types/index.js @@ -609,7 +609,7 @@ function generate_params_type(params, outdir, config) { (param) => `${/^\w+$/.test(param.name) ? param.name : `'${param.name}'`}${param.optional ? '?' : ''}: ${ param.matcher - ? `import('@sveltejs/kit').MatcherParam<(typeof import('${params_import}').params)[${JSON.stringify(param.matcher)}]>` + ? `Kit.MatcherParam<(typeof import('${params_import}').params)[${JSON.stringify(param.matcher)}]>` : 'string' }${param.optional ? ' | undefined' : ''}` ) diff --git a/packages/kit/src/core/sync/write_types/index.spec.js b/packages/kit/src/core/sync/write_types/index.spec.js index 90fd987ac6c8..36b219f4b767 100644 --- a/packages/kit/src/core/sync/write_types/index.spec.js +++ b/packages/kit/src/core/sync/write_types/index.spec.js @@ -32,7 +32,7 @@ function run_test(dir) { }); write_all_types(initial, manifest, root); - write_non_ambient(initial.kit, manifest); + write_non_ambient(initial.kit, manifest, root); } test('Creates correct $types', { timeout: 60000 }, () => { From 7562c03bb25a7bf743dd880c072e4da575ce3ef3 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 21 Jul 2026 12:12:34 -0400 Subject: [PATCH 03/34] fix --- .../kit/src/core/sync/write_non_ambient.js | 11 ++++--- .../src/core/sync/write_types/index.spec.js | 31 +++++++++++-------- .../write_types/test/app-types/params.d.ts | 5 +++ .../write_types/test/app-types/tsconfig.json | 2 +- 4 files changed, 31 insertions(+), 18 deletions(-) create mode 100644 packages/kit/src/core/sync/write_types/test/app-types/params.d.ts diff --git a/packages/kit/src/core/sync/write_non_ambient.js b/packages/kit/src/core/sync/write_non_ambient.js index 1ff6578be0c0..59595c88e37e 100644 --- a/packages/kit/src/core/sync/write_non_ambient.js +++ b/packages/kit/src/core/sync/write_non_ambient.js @@ -76,8 +76,9 @@ declare module "svelte/elements" { * Generate app types interface extension * @param {import('types').ManifestData} manifest_data * @param {import('types').ValidatedKitConfig} config + * @param {string} dir */ -function generate_app_types(manifest_data, config) { +function generate_app_types(manifest_data, config, dir) { /** @type {Map} */ const matcher_types = new Map(); @@ -92,7 +93,7 @@ function generate_app_types(manifest_data, config) { resolve_entry(config.files.params) ?? config.files.params.replace(/\.(js|ts)$/, '') + '.js'; - return posixify(path.relative(config.outDir, params_file)); + return posixify(path.relative(dir, params_file)); }; type = `import('@sveltejs/kit').MatcherParam<(typeof import('${path_to_params()}').params)[${JSON.stringify(matcher)}]>`; @@ -255,12 +256,14 @@ function generate_app_types(manifest_data, config) { * @param {string} root */ export function write_non_ambient(config, manifest_data, root) { + const dir = path.join(root, 'node_modules/$app/types'); + const content = [ GENERATED_COMMENT, `import '@sveltejs/kit';\nimport './env';`, - generate_app_types(manifest_data, config), + generate_app_types(manifest_data, config, dir), template.trim() ].join('\n\n'); - write_if_changed(path.join(root, 'node_modules/$app/types/index.d.ts'), content); + write_if_changed(path.join(dir, 'index.d.ts'), content); } diff --git a/packages/kit/src/core/sync/write_types/index.spec.js b/packages/kit/src/core/sync/write_types/index.spec.js index 36b219f4b767..8fb374562584 100644 --- a/packages/kit/src/core/sync/write_types/index.spec.js +++ b/packages/kit/src/core/sync/write_types/index.spec.js @@ -2,12 +2,13 @@ import { execSync } from 'node:child_process'; import fs from 'node:fs'; import path from 'node:path'; import process from 'node:process'; -import { assert, expect, test } from 'vitest'; +import { assert, describe, expect, test } from 'vitest'; import { rimraf } from '../../../utils/filesystem.js'; import create_manifest_data from '../create_manifest_data/index.js'; import { tweak_types, write_all_types } from './index.js'; import { write_non_ambient } from '../write_non_ambient.js'; import { validate_config } from '../../config/index.js'; +import { write_env } from '../write_env.js'; const cwd = path.join(import.meta.dirname, 'test'); @@ -33,9 +34,10 @@ function run_test(dir) { write_all_types(initial, manifest, root); write_non_ambient(initial.kit, manifest, root); + write_env(initial.kit, '', {}, root); } -test('Creates correct $types', { timeout: 60000 }, () => { +describe('Creates correct $types', () => { // To save us from creating a real SvelteKit project for each of the tests, // we first run the type generation directly for each test case, and then // call `tsc` to check that the generated types are valid. @@ -44,17 +46,20 @@ test('Creates correct $types', { timeout: 60000 }, () => { .filter((dir) => fs.statSync(`${cwd}/${dir}`).isDirectory()); for (const dir of directories) { - run_test(dir); - try { - // we skip lib check if MATRIX_VITE is set and not 'current' because overrides for vite can cause type mismatches - const skipLibCheck = process.env.MATRIX_VITE != null && process.env.MATRIX_VITE !== 'current'; - execSync(`pnpm testtypes${skipLibCheck ? ' --skipLibCheck' : ''}`, { - cwd: path.join(cwd, dir) - }); - } catch (e) { - console.error(/** @type {any} */ (e).stdout.toString()); - throw new Error(`${dir} type tests failed`, { cause: e }); - } + test(dir, { timeout: 60000 }, () => { + run_test(dir); + try { + // we skip lib check if MATRIX_VITE is set and not 'current' because overrides for vite can cause type mismatches + const skipLibCheck = + process.env.MATRIX_VITE != null && process.env.MATRIX_VITE !== 'current'; + execSync(`pnpm testtypes${skipLibCheck ? ' --skipLibCheck' : ''}`, { + cwd: path.join(cwd, dir) + }); + } catch (e) { + console.error(/** @type {any} */ (e).stdout.toString()); + throw e; + } + }); } }); diff --git a/packages/kit/src/core/sync/write_types/test/app-types/params.d.ts b/packages/kit/src/core/sync/write_types/test/app-types/params.d.ts new file mode 100644 index 000000000000..b205a0d836ac --- /dev/null +++ b/packages/kit/src/core/sync/write_types/test/app-types/params.d.ts @@ -0,0 +1,5 @@ +export const params: { + locale: { + schema: any; + }; +}; diff --git a/packages/kit/src/core/sync/write_types/test/app-types/tsconfig.json b/packages/kit/src/core/sync/write_types/test/app-types/tsconfig.json index efd87c2e6516..db3fd82b4d9d 100644 --- a/packages/kit/src/core/sync/write_types/test/app-types/tsconfig.json +++ b/packages/kit/src/core/sync/write_types/test/app-types/tsconfig.json @@ -15,6 +15,6 @@ }, "types": ["node"] }, - "include": ["./**/*.js", "./**/*.ts", ".svelte-kit/non-ambient.d.ts"], + "include": ["./**/*.js", "./**/*.ts", ".svelte-kit/non-ambient.d.ts", "node_modules/$app/types"], "exclude": ["..svelte-kit/**"] } From 7beb8f1c8e183172ae34f0dc1247d8067d84c7d6 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 21 Jul 2026 12:23:05 -0400 Subject: [PATCH 04/34] fix --- .../kit/src/core/sync/write_types/test/app-types/params.d.ts | 5 ----- .../sync/write_types/test/param-type-inference/tsconfig.json | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) delete mode 100644 packages/kit/src/core/sync/write_types/test/app-types/params.d.ts diff --git a/packages/kit/src/core/sync/write_types/test/app-types/params.d.ts b/packages/kit/src/core/sync/write_types/test/app-types/params.d.ts deleted file mode 100644 index b205a0d836ac..000000000000 --- a/packages/kit/src/core/sync/write_types/test/app-types/params.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const params: { - locale: { - schema: any; - }; -}; diff --git a/packages/kit/src/core/sync/write_types/test/param-type-inference/tsconfig.json b/packages/kit/src/core/sync/write_types/test/param-type-inference/tsconfig.json index a65da5db03a8..f477e5594cef 100644 --- a/packages/kit/src/core/sync/write_types/test/param-type-inference/tsconfig.json +++ b/packages/kit/src/core/sync/write_types/test/param-type-inference/tsconfig.json @@ -14,6 +14,6 @@ }, "types": ["node"] }, - "include": ["./**/*.js", "./**/*.ts", ".svelte-kit/non-ambient.d.ts"], + "include": ["./**/*.js", "./**/*.ts", ".svelte-kit/non-ambient.d.ts", "node_modules/$app/types"], "exclude": ["..svelte-kit/**"] } From 8ec3b4b8bfd59722928977ee875e54fdd2077f70 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 21 Jul 2026 12:51:44 -0400 Subject: [PATCH 05/34] fix --- packages/kit/test/apps/async/vite.config.js | 2 +- packages/kit/test/apps/options/vite.custom.config.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/kit/test/apps/async/vite.config.js b/packages/kit/test/apps/async/vite.config.js index be69d36e3c2c..63d214b9b235 100644 --- a/packages/kit/test/apps/async/vite.config.js +++ b/packages/kit/test/apps/async/vite.config.js @@ -21,7 +21,7 @@ export default defineConfig({ }, typescript: { config(config) { - config.include.push('../unit-test/*.js', '../test/*.js', '../playwright.config.js'); + config.include = ['**', '../unit-test/*.js', '../test/*.js', '../playwright.config.js']; } } }) diff --git a/packages/kit/test/apps/options/vite.custom.config.js b/packages/kit/test/apps/options/vite.custom.config.js index 9ac7b8a758ef..a2fe99ae4608 100644 --- a/packages/kit/test/apps/options/vite.custom.config.js +++ b/packages/kit/test/apps/options/vite.custom.config.js @@ -54,7 +54,7 @@ const config = { }, typescript: { config(config) { - config.include.push('../vite.custom.config.js', '../playwright.config.js'); + config.include = ['**', '../vite.custom.config.js', '../playwright.config.js']; } } }) From 6af0b20178f2800c2e7857fff52616764a0719fd Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 21 Jul 2026 15:17:11 -0400 Subject: [PATCH 06/34] fix --- packages/kit/test/apps/amp/tsconfig.json | 2 +- packages/kit/test/apps/async/tsconfig.json | 2 +- packages/kit/test/apps/basics/tsconfig.json | 2 +- packages/kit/test/apps/basics/vite.config.js | 2 +- packages/kit/test/apps/dev-only/tsconfig.json | 2 +- packages/kit/test/apps/embed/tsconfig.json | 2 +- packages/kit/test/apps/hash-based-routing/tsconfig.json | 2 +- packages/kit/test/apps/no-ssr/tsconfig.json | 2 +- packages/kit/test/apps/options-2/tsconfig.json | 2 +- packages/kit/test/apps/options-3/tsconfig.json | 2 +- .../kit/test/apps/prerendered-app-error-pages/jsconfig.json | 2 +- packages/kit/test/apps/writes/tsconfig.json | 2 +- .../test/build-errors/apps/env-private-dynamic/tsconfig.json | 2 +- .../build-errors/apps/env-private-service-worker/tsconfig.json | 2 +- packages/kit/test/build-errors/apps/env-private/tsconfig.json | 2 +- .../apps/prerender-entry-generator-mismatch/tsconfig.json | 2 +- .../apps/prerender-remote-function-error/tsconfig.json | 2 +- .../apps/prerenderable-incorrect-fragment/tsconfig.json | 2 +- .../apps/prerenderable-not-prerendered/tsconfig.json | 2 +- .../apps/remote-function-without-flag/tsconfig.json | 2 +- .../apps/server-only-folder-dynamic-import/tsconfig.json | 2 +- .../kit/test/build-errors/apps/server-only-folder/tsconfig.json | 2 +- .../apps/server-only-module-dynamic-import/tsconfig.json | 2 +- .../kit/test/build-errors/apps/server-only-module/tsconfig.json | 2 +- .../kit/test/build-errors/apps/server-only-shared/tsconfig.json | 2 +- packages/kit/test/build-errors/apps/syntax-error/tsconfig.json | 2 +- packages/kit/test/prerendering/basics/tsconfig.json | 2 +- packages/kit/test/prerendering/options/tsconfig.json | 2 +- packages/kit/test/prerendering/paths-base/tsconfig.json | 2 +- 29 files changed, 29 insertions(+), 29 deletions(-) diff --git a/packages/kit/test/apps/amp/tsconfig.json b/packages/kit/test/apps/amp/tsconfig.json index b1096bf168cd..a14103c5fc1b 100644 --- a/packages/kit/test/apps/amp/tsconfig.json +++ b/packages/kit/test/apps/amp/tsconfig.json @@ -4,5 +4,5 @@ "checkJs": true, "noEmit": true }, - "extends": "./.svelte-kit/tsconfig.json" + "extends": "$app/tsconfig" } diff --git a/packages/kit/test/apps/async/tsconfig.json b/packages/kit/test/apps/async/tsconfig.json index 85de622f1339..1b6ff59e897e 100644 --- a/packages/kit/test/apps/async/tsconfig.json +++ b/packages/kit/test/apps/async/tsconfig.json @@ -6,5 +6,5 @@ "resolveJsonModule": true, "rewriteRelativeImportExtensions": true }, - "extends": "./.svelte-kit/tsconfig.json" + "extends": "$app/tsconfig" } diff --git a/packages/kit/test/apps/basics/tsconfig.json b/packages/kit/test/apps/basics/tsconfig.json index 4cf3b2a60fe9..947c9ce5579f 100644 --- a/packages/kit/test/apps/basics/tsconfig.json +++ b/packages/kit/test/apps/basics/tsconfig.json @@ -5,5 +5,5 @@ "esModuleInterop": true, "resolveJsonModule": true }, - "extends": "./.svelte-kit/tsconfig.json" + "extends": "$app/tsconfig" } diff --git a/packages/kit/test/apps/basics/vite.config.js b/packages/kit/test/apps/basics/vite.config.js index 3d5dd55594df..e97c0be11345 100644 --- a/packages/kit/test/apps/basics/vite.config.js +++ b/packages/kit/test/apps/basics/vite.config.js @@ -88,7 +88,7 @@ export default defineConfig({ typescript: { config: (config) => { - config.include.push('../unit-test'); + config.include = ['**', '../unit-test']; } } }) diff --git a/packages/kit/test/apps/dev-only/tsconfig.json b/packages/kit/test/apps/dev-only/tsconfig.json index 4cf3b2a60fe9..947c9ce5579f 100644 --- a/packages/kit/test/apps/dev-only/tsconfig.json +++ b/packages/kit/test/apps/dev-only/tsconfig.json @@ -5,5 +5,5 @@ "esModuleInterop": true, "resolveJsonModule": true }, - "extends": "./.svelte-kit/tsconfig.json" + "extends": "$app/tsconfig" } diff --git a/packages/kit/test/apps/embed/tsconfig.json b/packages/kit/test/apps/embed/tsconfig.json index 1d665886266b..5dd124a3b02a 100644 --- a/packages/kit/test/apps/embed/tsconfig.json +++ b/packages/kit/test/apps/embed/tsconfig.json @@ -6,5 +6,5 @@ "noEmit": true, "resolveJsonModule": true }, - "extends": "./.svelte-kit/tsconfig.json" + "extends": "$app/tsconfig" } diff --git a/packages/kit/test/apps/hash-based-routing/tsconfig.json b/packages/kit/test/apps/hash-based-routing/tsconfig.json index 1d665886266b..5dd124a3b02a 100644 --- a/packages/kit/test/apps/hash-based-routing/tsconfig.json +++ b/packages/kit/test/apps/hash-based-routing/tsconfig.json @@ -6,5 +6,5 @@ "noEmit": true, "resolveJsonModule": true }, - "extends": "./.svelte-kit/tsconfig.json" + "extends": "$app/tsconfig" } diff --git a/packages/kit/test/apps/no-ssr/tsconfig.json b/packages/kit/test/apps/no-ssr/tsconfig.json index 1d665886266b..5dd124a3b02a 100644 --- a/packages/kit/test/apps/no-ssr/tsconfig.json +++ b/packages/kit/test/apps/no-ssr/tsconfig.json @@ -6,5 +6,5 @@ "noEmit": true, "resolveJsonModule": true }, - "extends": "./.svelte-kit/tsconfig.json" + "extends": "$app/tsconfig" } diff --git a/packages/kit/test/apps/options-2/tsconfig.json b/packages/kit/test/apps/options-2/tsconfig.json index b1096bf168cd..a14103c5fc1b 100644 --- a/packages/kit/test/apps/options-2/tsconfig.json +++ b/packages/kit/test/apps/options-2/tsconfig.json @@ -4,5 +4,5 @@ "checkJs": true, "noEmit": true }, - "extends": "./.svelte-kit/tsconfig.json" + "extends": "$app/tsconfig" } diff --git a/packages/kit/test/apps/options-3/tsconfig.json b/packages/kit/test/apps/options-3/tsconfig.json index b1096bf168cd..a14103c5fc1b 100644 --- a/packages/kit/test/apps/options-3/tsconfig.json +++ b/packages/kit/test/apps/options-3/tsconfig.json @@ -4,5 +4,5 @@ "checkJs": true, "noEmit": true }, - "extends": "./.svelte-kit/tsconfig.json" + "extends": "$app/tsconfig" } diff --git a/packages/kit/test/apps/prerendered-app-error-pages/jsconfig.json b/packages/kit/test/apps/prerendered-app-error-pages/jsconfig.json index 5c34c9263152..726d608e639d 100644 --- a/packages/kit/test/apps/prerendered-app-error-pages/jsconfig.json +++ b/packages/kit/test/apps/prerendered-app-error-pages/jsconfig.json @@ -1,5 +1,5 @@ { - "extends": "./.svelte-kit/tsconfig.json", + "extends": "$app/tsconfig", "compilerOptions": { "allowJs": true, "checkJs": true, diff --git a/packages/kit/test/apps/writes/tsconfig.json b/packages/kit/test/apps/writes/tsconfig.json index 1d665886266b..5dd124a3b02a 100644 --- a/packages/kit/test/apps/writes/tsconfig.json +++ b/packages/kit/test/apps/writes/tsconfig.json @@ -6,5 +6,5 @@ "noEmit": true, "resolveJsonModule": true }, - "extends": "./.svelte-kit/tsconfig.json" + "extends": "$app/tsconfig" } diff --git a/packages/kit/test/build-errors/apps/env-private-dynamic/tsconfig.json b/packages/kit/test/build-errors/apps/env-private-dynamic/tsconfig.json index 8e767983eeb2..ff33d4b1df33 100644 --- a/packages/kit/test/build-errors/apps/env-private-dynamic/tsconfig.json +++ b/packages/kit/test/build-errors/apps/env-private-dynamic/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "./.svelte-kit/tsconfig.json", + "extends": "$app/tsconfig", "compilerOptions": { "allowJs": true, "checkJs": true, diff --git a/packages/kit/test/build-errors/apps/env-private-service-worker/tsconfig.json b/packages/kit/test/build-errors/apps/env-private-service-worker/tsconfig.json index 8e767983eeb2..ff33d4b1df33 100644 --- a/packages/kit/test/build-errors/apps/env-private-service-worker/tsconfig.json +++ b/packages/kit/test/build-errors/apps/env-private-service-worker/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "./.svelte-kit/tsconfig.json", + "extends": "$app/tsconfig", "compilerOptions": { "allowJs": true, "checkJs": true, diff --git a/packages/kit/test/build-errors/apps/env-private/tsconfig.json b/packages/kit/test/build-errors/apps/env-private/tsconfig.json index 8e767983eeb2..ff33d4b1df33 100644 --- a/packages/kit/test/build-errors/apps/env-private/tsconfig.json +++ b/packages/kit/test/build-errors/apps/env-private/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "./.svelte-kit/tsconfig.json", + "extends": "$app/tsconfig", "compilerOptions": { "allowJs": true, "checkJs": true, diff --git a/packages/kit/test/build-errors/apps/prerender-entry-generator-mismatch/tsconfig.json b/packages/kit/test/build-errors/apps/prerender-entry-generator-mismatch/tsconfig.json index b1096bf168cd..a14103c5fc1b 100644 --- a/packages/kit/test/build-errors/apps/prerender-entry-generator-mismatch/tsconfig.json +++ b/packages/kit/test/build-errors/apps/prerender-entry-generator-mismatch/tsconfig.json @@ -4,5 +4,5 @@ "checkJs": true, "noEmit": true }, - "extends": "./.svelte-kit/tsconfig.json" + "extends": "$app/tsconfig" } diff --git a/packages/kit/test/build-errors/apps/prerender-remote-function-error/tsconfig.json b/packages/kit/test/build-errors/apps/prerender-remote-function-error/tsconfig.json index d1eb33103f2d..4ab2e9e5330d 100644 --- a/packages/kit/test/build-errors/apps/prerender-remote-function-error/tsconfig.json +++ b/packages/kit/test/build-errors/apps/prerender-remote-function-error/tsconfig.json @@ -5,5 +5,5 @@ "noEmit": true, "rewriteRelativeImportExtensions": true }, - "extends": "./.svelte-kit/tsconfig.json" + "extends": "$app/tsconfig" } diff --git a/packages/kit/test/build-errors/apps/prerenderable-incorrect-fragment/tsconfig.json b/packages/kit/test/build-errors/apps/prerenderable-incorrect-fragment/tsconfig.json index b1096bf168cd..a14103c5fc1b 100644 --- a/packages/kit/test/build-errors/apps/prerenderable-incorrect-fragment/tsconfig.json +++ b/packages/kit/test/build-errors/apps/prerenderable-incorrect-fragment/tsconfig.json @@ -4,5 +4,5 @@ "checkJs": true, "noEmit": true }, - "extends": "./.svelte-kit/tsconfig.json" + "extends": "$app/tsconfig" } diff --git a/packages/kit/test/build-errors/apps/prerenderable-not-prerendered/tsconfig.json b/packages/kit/test/build-errors/apps/prerenderable-not-prerendered/tsconfig.json index b1096bf168cd..a14103c5fc1b 100644 --- a/packages/kit/test/build-errors/apps/prerenderable-not-prerendered/tsconfig.json +++ b/packages/kit/test/build-errors/apps/prerenderable-not-prerendered/tsconfig.json @@ -4,5 +4,5 @@ "checkJs": true, "noEmit": true }, - "extends": "./.svelte-kit/tsconfig.json" + "extends": "$app/tsconfig" } diff --git a/packages/kit/test/build-errors/apps/remote-function-without-flag/tsconfig.json b/packages/kit/test/build-errors/apps/remote-function-without-flag/tsconfig.json index 8e767983eeb2..ff33d4b1df33 100644 --- a/packages/kit/test/build-errors/apps/remote-function-without-flag/tsconfig.json +++ b/packages/kit/test/build-errors/apps/remote-function-without-flag/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "./.svelte-kit/tsconfig.json", + "extends": "$app/tsconfig", "compilerOptions": { "allowJs": true, "checkJs": true, diff --git a/packages/kit/test/build-errors/apps/server-only-folder-dynamic-import/tsconfig.json b/packages/kit/test/build-errors/apps/server-only-folder-dynamic-import/tsconfig.json index 8e767983eeb2..ff33d4b1df33 100644 --- a/packages/kit/test/build-errors/apps/server-only-folder-dynamic-import/tsconfig.json +++ b/packages/kit/test/build-errors/apps/server-only-folder-dynamic-import/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "./.svelte-kit/tsconfig.json", + "extends": "$app/tsconfig", "compilerOptions": { "allowJs": true, "checkJs": true, diff --git a/packages/kit/test/build-errors/apps/server-only-folder/tsconfig.json b/packages/kit/test/build-errors/apps/server-only-folder/tsconfig.json index 8e767983eeb2..ff33d4b1df33 100644 --- a/packages/kit/test/build-errors/apps/server-only-folder/tsconfig.json +++ b/packages/kit/test/build-errors/apps/server-only-folder/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "./.svelte-kit/tsconfig.json", + "extends": "$app/tsconfig", "compilerOptions": { "allowJs": true, "checkJs": true, diff --git a/packages/kit/test/build-errors/apps/server-only-module-dynamic-import/tsconfig.json b/packages/kit/test/build-errors/apps/server-only-module-dynamic-import/tsconfig.json index 8e767983eeb2..ff33d4b1df33 100644 --- a/packages/kit/test/build-errors/apps/server-only-module-dynamic-import/tsconfig.json +++ b/packages/kit/test/build-errors/apps/server-only-module-dynamic-import/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "./.svelte-kit/tsconfig.json", + "extends": "$app/tsconfig", "compilerOptions": { "allowJs": true, "checkJs": true, diff --git a/packages/kit/test/build-errors/apps/server-only-module/tsconfig.json b/packages/kit/test/build-errors/apps/server-only-module/tsconfig.json index 8e767983eeb2..ff33d4b1df33 100644 --- a/packages/kit/test/build-errors/apps/server-only-module/tsconfig.json +++ b/packages/kit/test/build-errors/apps/server-only-module/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "./.svelte-kit/tsconfig.json", + "extends": "$app/tsconfig", "compilerOptions": { "allowJs": true, "checkJs": true, diff --git a/packages/kit/test/build-errors/apps/server-only-shared/tsconfig.json b/packages/kit/test/build-errors/apps/server-only-shared/tsconfig.json index 8e767983eeb2..ff33d4b1df33 100644 --- a/packages/kit/test/build-errors/apps/server-only-shared/tsconfig.json +++ b/packages/kit/test/build-errors/apps/server-only-shared/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "./.svelte-kit/tsconfig.json", + "extends": "$app/tsconfig", "compilerOptions": { "allowJs": true, "checkJs": true, diff --git a/packages/kit/test/build-errors/apps/syntax-error/tsconfig.json b/packages/kit/test/build-errors/apps/syntax-error/tsconfig.json index 8e767983eeb2..ff33d4b1df33 100644 --- a/packages/kit/test/build-errors/apps/syntax-error/tsconfig.json +++ b/packages/kit/test/build-errors/apps/syntax-error/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "./.svelte-kit/tsconfig.json", + "extends": "$app/tsconfig", "compilerOptions": { "allowJs": true, "checkJs": true, diff --git a/packages/kit/test/prerendering/basics/tsconfig.json b/packages/kit/test/prerendering/basics/tsconfig.json index b1096bf168cd..a14103c5fc1b 100644 --- a/packages/kit/test/prerendering/basics/tsconfig.json +++ b/packages/kit/test/prerendering/basics/tsconfig.json @@ -4,5 +4,5 @@ "checkJs": true, "noEmit": true }, - "extends": "./.svelte-kit/tsconfig.json" + "extends": "$app/tsconfig" } diff --git a/packages/kit/test/prerendering/options/tsconfig.json b/packages/kit/test/prerendering/options/tsconfig.json index b1096bf168cd..a14103c5fc1b 100644 --- a/packages/kit/test/prerendering/options/tsconfig.json +++ b/packages/kit/test/prerendering/options/tsconfig.json @@ -4,5 +4,5 @@ "checkJs": true, "noEmit": true }, - "extends": "./.svelte-kit/tsconfig.json" + "extends": "$app/tsconfig" } diff --git a/packages/kit/test/prerendering/paths-base/tsconfig.json b/packages/kit/test/prerendering/paths-base/tsconfig.json index b1096bf168cd..a14103c5fc1b 100644 --- a/packages/kit/test/prerendering/paths-base/tsconfig.json +++ b/packages/kit/test/prerendering/paths-base/tsconfig.json @@ -4,5 +4,5 @@ "checkJs": true, "noEmit": true }, - "extends": "./.svelte-kit/tsconfig.json" + "extends": "$app/tsconfig" } From 7e0526957cde0d1549cc8ebb6a7b484d1a22fde3 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 21 Jul 2026 15:48:25 -0400 Subject: [PATCH 07/34] fix --- packages/kit/test/apps/options/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/test/apps/options/tsconfig.json b/packages/kit/test/apps/options/tsconfig.json index 77500cd3c88f..a14103c5fc1b 100644 --- a/packages/kit/test/apps/options/tsconfig.json +++ b/packages/kit/test/apps/options/tsconfig.json @@ -4,5 +4,5 @@ "checkJs": true, "noEmit": true }, - "extends": "./.custom-out-dir/tsconfig.json" + "extends": "$app/tsconfig" } From 200a1b4262cd8f7e397280f89343586531a5e490 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 21 Jul 2026 15:54:56 -0400 Subject: [PATCH 08/34] fix --- packages/adapter-cloudflare/test/apps/pages/tsconfig.json | 2 +- packages/adapter-cloudflare/test/apps/workers/tsconfig.json | 2 +- packages/adapter-netlify/test/apps/basic/tsconfig.json | 2 +- packages/adapter-netlify/test/apps/edge/tsconfig.json | 2 +- .../adapter-netlify/test/apps/instrumentation/tsconfig.json | 2 +- packages/adapter-netlify/test/apps/split/tsconfig.json | 2 +- packages/adapter-node/test/apps/basic/tsconfig.json | 2 +- packages/adapter-static/test/apps/spa/jsconfig.json | 2 +- packages/adapter-vercel/test/apps/basic/tsconfig.json | 2 +- packages/enhanced-img/test/apps/basics/jsconfig.json | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/adapter-cloudflare/test/apps/pages/tsconfig.json b/packages/adapter-cloudflare/test/apps/pages/tsconfig.json index c2db702f9bb1..0c2092500732 100644 --- a/packages/adapter-cloudflare/test/apps/pages/tsconfig.json +++ b/packages/adapter-cloudflare/test/apps/pages/tsconfig.json @@ -9,5 +9,5 @@ "sourceMap": true, "moduleResolution": "bundler" }, - "extends": "./.svelte-kit/tsconfig.json" + "extends": "$app/tsconfig" } diff --git a/packages/adapter-cloudflare/test/apps/workers/tsconfig.json b/packages/adapter-cloudflare/test/apps/workers/tsconfig.json index c2db702f9bb1..0c2092500732 100644 --- a/packages/adapter-cloudflare/test/apps/workers/tsconfig.json +++ b/packages/adapter-cloudflare/test/apps/workers/tsconfig.json @@ -9,5 +9,5 @@ "sourceMap": true, "moduleResolution": "bundler" }, - "extends": "./.svelte-kit/tsconfig.json" + "extends": "$app/tsconfig" } diff --git a/packages/adapter-netlify/test/apps/basic/tsconfig.json b/packages/adapter-netlify/test/apps/basic/tsconfig.json index c2db702f9bb1..0c2092500732 100644 --- a/packages/adapter-netlify/test/apps/basic/tsconfig.json +++ b/packages/adapter-netlify/test/apps/basic/tsconfig.json @@ -9,5 +9,5 @@ "sourceMap": true, "moduleResolution": "bundler" }, - "extends": "./.svelte-kit/tsconfig.json" + "extends": "$app/tsconfig" } diff --git a/packages/adapter-netlify/test/apps/edge/tsconfig.json b/packages/adapter-netlify/test/apps/edge/tsconfig.json index c2db702f9bb1..0c2092500732 100644 --- a/packages/adapter-netlify/test/apps/edge/tsconfig.json +++ b/packages/adapter-netlify/test/apps/edge/tsconfig.json @@ -9,5 +9,5 @@ "sourceMap": true, "moduleResolution": "bundler" }, - "extends": "./.svelte-kit/tsconfig.json" + "extends": "$app/tsconfig" } diff --git a/packages/adapter-netlify/test/apps/instrumentation/tsconfig.json b/packages/adapter-netlify/test/apps/instrumentation/tsconfig.json index 34380ebc986e..12550521f550 100644 --- a/packages/adapter-netlify/test/apps/instrumentation/tsconfig.json +++ b/packages/adapter-netlify/test/apps/instrumentation/tsconfig.json @@ -10,5 +10,5 @@ "strict": true, "moduleResolution": "bundler" }, - "extends": "./.svelte-kit/tsconfig.json" + "extends": "$app/tsconfig" } diff --git a/packages/adapter-netlify/test/apps/split/tsconfig.json b/packages/adapter-netlify/test/apps/split/tsconfig.json index c2db702f9bb1..0c2092500732 100644 --- a/packages/adapter-netlify/test/apps/split/tsconfig.json +++ b/packages/adapter-netlify/test/apps/split/tsconfig.json @@ -9,5 +9,5 @@ "sourceMap": true, "moduleResolution": "bundler" }, - "extends": "./.svelte-kit/tsconfig.json" + "extends": "$app/tsconfig" } diff --git a/packages/adapter-node/test/apps/basic/tsconfig.json b/packages/adapter-node/test/apps/basic/tsconfig.json index c2db702f9bb1..0c2092500732 100644 --- a/packages/adapter-node/test/apps/basic/tsconfig.json +++ b/packages/adapter-node/test/apps/basic/tsconfig.json @@ -9,5 +9,5 @@ "sourceMap": true, "moduleResolution": "bundler" }, - "extends": "./.svelte-kit/tsconfig.json" + "extends": "$app/tsconfig" } diff --git a/packages/adapter-static/test/apps/spa/jsconfig.json b/packages/adapter-static/test/apps/spa/jsconfig.json index e0733f11157e..681df462f63f 100644 --- a/packages/adapter-static/test/apps/spa/jsconfig.json +++ b/packages/adapter-static/test/apps/spa/jsconfig.json @@ -1,4 +1,4 @@ { - "extends": "./.svelte-kit/tsconfig.json", + "extends": "$app/tsconfig", "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"] } diff --git a/packages/adapter-vercel/test/apps/basic/tsconfig.json b/packages/adapter-vercel/test/apps/basic/tsconfig.json index 1de19ffb0ade..ca10a5d09eba 100644 --- a/packages/adapter-vercel/test/apps/basic/tsconfig.json +++ b/packages/adapter-vercel/test/apps/basic/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "./.svelte-kit/tsconfig.json", + "extends": "$app/tsconfig", "compilerOptions": { "rewriteRelativeImportExtensions": true, "allowJs": true, diff --git a/packages/enhanced-img/test/apps/basics/jsconfig.json b/packages/enhanced-img/test/apps/basics/jsconfig.json index 0247cc671b3a..1a1cacd4c31e 100644 --- a/packages/enhanced-img/test/apps/basics/jsconfig.json +++ b/packages/enhanced-img/test/apps/basics/jsconfig.json @@ -1,4 +1,4 @@ { - "extends": "./.svelte-kit/tsconfig.json", + "extends": "$app/tsconfig", "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte", "*.js", "test/*.js"] } From 8125a01d58ccb5fdbcd7a330e30ac07c4d289d01 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 21 Jul 2026 19:01:33 -0400 Subject: [PATCH 09/34] add $app/service-worker and $app/tsconfig/service-worker --- documentation/docs/98-reference/54-types.md | 8 ++--- packages/kit/scripts/generate-dts.js | 1 + packages/kit/src/core/sync/write_tsconfig.js | 29 +++++++++++++++---- .../src/runtime/app/service-worker/index.js | 22 ++++++++++++++ packages/kit/types/index.d.ts | 6 ++++ 5 files changed, 56 insertions(+), 10 deletions(-) create mode 100644 packages/kit/src/runtime/app/service-worker/index.js diff --git a/documentation/docs/98-reference/54-types.md b/documentation/docs/98-reference/54-types.md index d1d311ff036b..077992d02b91 100644 --- a/documentation/docs/98-reference/54-types.md +++ b/documentation/docs/98-reference/54-types.md @@ -119,16 +119,16 @@ Starting with version 2.16.0, two additional helper types are provided: `PagePro > > ``` -> [!NOTE] For this to work, your own `tsconfig.json` or `jsconfig.json` should extend from the generated `.svelte-kit/tsconfig.json` (where `.svelte-kit` is your [`outDir`](configuration#outDir)): +> [!NOTE] For this to work, your own `tsconfig.json` or `jsconfig.json` should extend from the generated `$app/types`: > -> `{ "extends": "./.svelte-kit/tsconfig.json" }` +> `{ "extends": "$app/tsconfig" }` ### Default tsconfig.json -The generated `.svelte-kit/tsconfig.json` file contains a mixture of options. Some are generated programmatically based on your project configuration, and should generally not be overridden without good reason: +The generated `$app/types` file contains a mixture of options. Some are generated programmatically based on your project configuration, and should generally not be overridden without good reason: ```json -/// file: .svelte-kit/tsconfig.json +/// file: $app/types { "compilerOptions": { "paths": { diff --git a/packages/kit/scripts/generate-dts.js b/packages/kit/scripts/generate-dts.js index db78e8d968db..c4895e7e21ca 100644 --- a/packages/kit/scripts/generate-dts.js +++ b/packages/kit/scripts/generate-dts.js @@ -14,6 +14,7 @@ await createBundle({ '$app/navigation': 'src/runtime/app/navigation.js', '$app/paths': 'src/runtime/app/paths/public.d.ts', '$app/server': 'src/runtime/app/server/index.js', + '$app/service-worker': 'src/runtime/app/service-worker/index.js', '$app/state': 'src/runtime/app/state/index.js' }, include: ['src'], diff --git a/packages/kit/src/core/sync/write_tsconfig.js b/packages/kit/src/core/sync/write_tsconfig.js index 205875862aec..1168f786417c 100644 --- a/packages/kit/src/core/sync/write_tsconfig.js +++ b/packages/kit/src/core/sync/write_tsconfig.js @@ -42,23 +42,31 @@ function remove_trailing_slashstar(file) { * @param {string} root */ export function write_tsconfig(kit, root) { - const out = path.join(root, 'node_modules/$app/tsconfig/tsconfig.json'); - const user_config = load_user_tsconfig(root); if (user_config) validate_user_config(user_config); - write_if_changed(out, JSON.stringify(get_tsconfig(out, kit, root), null, '\t')); + const dir = path.join(root, 'node_modules/$app/tsconfig'); + + write_if_changed( + path.join(dir, 'tsconfig.json'), + JSON.stringify(get_tsconfig(dir, kit, root), null, '\t') + ); + + write_if_changed( + path.join(dir, 'service-worker/tsconfig.json'), + JSON.stringify(get_tsconfig_serviceworker(), null, '\t') + ); } /** * Generates the tsconfig that the user's tsconfig inherits from. - * @param {string} out The file we're writing to + * @param {string} dir The directory we're writing to * @param {import('types').ValidatedKitConfig} kit * @param {string} cwd */ -export function get_tsconfig(out, kit, cwd) { +export function get_tsconfig(dir, kit, cwd) { /** @param {string} file */ - const config_relative = (file) => posixify(path.relative(path.dirname(out), file)); + const config_relative = (file) => posixify(path.relative(dir, file)); const config = { compilerOptions: { @@ -101,6 +109,15 @@ export function get_tsconfig(out, kit, cwd) { return kit.typescript.config(config) ?? config; } +function get_tsconfig_serviceworker() { + return { + compilerOptions: { + lib: ['ESNext', 'WebWorker'], + types: ['$app/types'] + } + }; +} + /** @param {string} cwd */ function load_user_tsconfig(cwd) { const file = maybe_file(cwd, 'tsconfig.json') || maybe_file(cwd, 'jsconfig.json'); diff --git a/packages/kit/src/runtime/app/service-worker/index.js b/packages/kit/src/runtime/app/service-worker/index.js new file mode 100644 index 000000000000..6bd3c581d6db --- /dev/null +++ b/packages/kit/src/runtime/app/service-worker/index.js @@ -0,0 +1,22 @@ +/// +/// +/// + +import { DEV } from 'esm-env'; + +export const self = /** @type {ServiceWorkerGlobalScope} */ ( + /** @type {unknown} */ (globalThis.self) +); + +self.addEventListener('fetch', (e) => { + e.respondWith; +}); + +if (DEV) { + if ( + typeof ServiceWorkerGlobalScope === 'undefined' || + !(self instanceof ServiceWorkerGlobalScope) + ) { + throw new Error('The `$app/service-worker` module can only be imported into a service worker'); + } +} diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index b35569cdfc29..29c86c641cc5 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -3587,6 +3587,12 @@ declare module '$app/server' { export {}; } +declare module '$app/service-worker' { + export const self: ServiceWorkerGlobalScope; + + export {}; +} + declare module '$app/state' { /** * A read-only reactive object with information about the current page, serving several use cases: From 864eba362d1796e0e178ee5fde6dbb6df27ee4b3 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 21 Jul 2026 21:27:15 -0400 Subject: [PATCH 10/34] fix --- packages/kit/CHANGELOG.md | 2 +- packages/kit/scripts/generate-dts.js | 13 +++++++++++-- packages/kit/src/core/sync/sync.js | 5 ++--- packages/kit/src/core/sync/write_env.js | 3 +-- packages/kit/src/core/sync/write_tsconfig.js | 9 --------- .../kit/src/core/sync/write_types/index.spec.js | 2 +- packages/kit/tsconfig.json | 2 +- packages/kit/types/index.d.ts | 1 + 8 files changed, 18 insertions(+), 19 deletions(-) diff --git a/packages/kit/CHANGELOG.md b/packages/kit/CHANGELOG.md index 9b26b5076a8b..1adac89324f2 100644 --- a/packages/kit/CHANGELOG.md +++ b/packages/kit/CHANGELOG.md @@ -2278,7 +2278,7 @@ ▲ [WARNING] Cannot find base config file "./.svelte-kit/tsconfig.json" [tsconfig.json] tsconfig.json:2:12: - 2 │ "extends": "./.svelte-kit/tsconfig.json", + 2 │ "extends": "$app/tsconfig", ╵ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ``` diff --git a/packages/kit/scripts/generate-dts.js b/packages/kit/scripts/generate-dts.js index c4895e7e21ca..47ff7b3e2290 100644 --- a/packages/kit/scripts/generate-dts.js +++ b/packages/kit/scripts/generate-dts.js @@ -1,5 +1,5 @@ import { createBundle } from 'dts-buddy'; -import { readFileSync } from 'node:fs'; +import { readFileSync, writeFileSync } from 'node:fs'; await createBundle({ output: 'types/index.d.ts', @@ -25,10 +25,19 @@ await createBundle({ }); // dts-buddy doesn't inline imports of module declaration in ambient-private.d.ts but also doesn't include them, resulting in broken types - guard against that -const types = readFileSync('./types/index.d.ts', 'utf-8'); +let 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' + types ); } + +// this line causes type-checking to fail — simplest fix is to ignore it +types = types.replace( + 'export const self: ServiceWorkerGlobalScope;', + '// @ts-ignore\n\texport const self: ServiceWorkerGlobalScope;' +); + +writeFileSync('types/index.d.ts', types); diff --git a/packages/kit/src/core/sync/sync.js b/packages/kit/src/core/sync/sync.js index cbc6a525bb18..7510e313e37f 100644 --- a/packages/kit/src/core/sync/sync.js +++ b/packages/kit/src/core/sync/sync.js @@ -1,5 +1,4 @@ import path from 'node:path'; -import process from 'node:process'; import create_manifest_data from './create_manifest_data/index.js'; import { write_client_manifest } from './write_client_manifest.js'; import { write_tsconfig } from './write_tsconfig.js'; @@ -65,7 +64,7 @@ export function update(config, manifest_data, file, root) { } write_types(config, manifest_data, file, root); - write_non_ambient(config.kit, manifest_data); + write_non_ambient(config.kit, manifest_data, root); } /** @@ -100,7 +99,7 @@ export function all_types(config, root) { export async function env(kit, entry, root, mode) { const env_config = await load_explicit_env(kit, entry, root, mode); - write_env(kit, entry, env_config, root); + write_env(entry, env_config, root); return env_config; } diff --git a/packages/kit/src/core/sync/write_env.js b/packages/kit/src/core/sync/write_env.js index 160806cc5dc0..402e2ac6fb46 100644 --- a/packages/kit/src/core/sync/write_env.js +++ b/packages/kit/src/core/sync/write_env.js @@ -10,12 +10,11 @@ const DOCS = '// See https://svelte.dev/docs/kit/environment-variables for more * Writes ambient declarations including types reference to @sveltejs/kit, * and the existing environment variables in process.env to * $env/static/private and $env/static/public - * @param {import('types').ValidatedKitConfig} kit * @param {string | null} entry * @param {Record> | null} env_config * @param {string} root */ -export function write_env(kit, entry, env_config, root) { +export function write_env(entry, env_config, root) { const content = []; const dir = path.join(root, 'node_modules/$app/types'); diff --git a/packages/kit/src/core/sync/write_tsconfig.js b/packages/kit/src/core/sync/write_tsconfig.js index 1168f786417c..cc079b881d63 100644 --- a/packages/kit/src/core/sync/write_tsconfig.js +++ b/packages/kit/src/core/sync/write_tsconfig.js @@ -4,7 +4,6 @@ import { styleText } from 'node:util'; import { posixify } from '../../utils/os.js'; import { read_package_imports, normalize_import_value } from '../../utils/imports.js'; import { write_if_changed } from './utils.js'; -import { exclude } from 'rolldown/filter'; /** * @param {string} cwd @@ -17,14 +16,6 @@ function maybe_file(cwd, file) { } } -/** - * @param {string} cwd - * @param {string} file - */ -function project_relative(cwd, file) { - return posixify(path.relative(cwd, file)); -} - /** * @param {string} file */ diff --git a/packages/kit/src/core/sync/write_types/index.spec.js b/packages/kit/src/core/sync/write_types/index.spec.js index 8fb374562584..e805ea0f24d5 100644 --- a/packages/kit/src/core/sync/write_types/index.spec.js +++ b/packages/kit/src/core/sync/write_types/index.spec.js @@ -34,7 +34,7 @@ function run_test(dir) { write_all_types(initial, manifest, root); write_non_ambient(initial.kit, manifest, root); - write_env(initial.kit, '', {}, root); + write_env('', {}, root); } describe('Creates correct $types', () => { diff --git a/packages/kit/tsconfig.json b/packages/kit/tsconfig.json index 86b0ce7f2340..d859c2ba6ace 100644 --- a/packages/kit/tsconfig.json +++ b/packages/kit/tsconfig.json @@ -27,5 +27,5 @@ "types": ["node"] }, "include": ["*.js", "scripts/**/*", "src/**/*"], - "exclude": ["./**/write_types/test/**"] + "exclude": ["./**/write_types/test/**", "./src/runtime/app/service-worker"] } diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 29c86c641cc5..b713610e67f7 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -3588,6 +3588,7 @@ declare module '$app/server' { } declare module '$app/service-worker' { + // @ts-ignore export const self: ServiceWorkerGlobalScope; export {}; From 5448b91f5afe207b942af03a2ce8e592e303281c Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 21 Jul 2026 22:28:36 -0400 Subject: [PATCH 11/34] pacify eslint --- eslint.config.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/eslint.config.js b/eslint.config.js index 2adc2878bd36..66280d53e7c7 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -45,7 +45,9 @@ export default [ { languageOptions: { parserOptions: { - projectService: true + projectService: { + allowDefaultProject: ['packages/kit/src/runtime/app/service-worker/index.js'] + } } }, rules: { From d20ced24f3b75e9f406f7e978474bbae577d4113 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 22 Jul 2026 16:49:21 -0400 Subject: [PATCH 12/34] WIP --- packages/kit/src/core/sync/write_tsconfig.js | 93 ++++++++++++-------- 1 file changed, 58 insertions(+), 35 deletions(-) diff --git a/packages/kit/src/core/sync/write_tsconfig.js b/packages/kit/src/core/sync/write_tsconfig.js index cc079b881d63..808d5a187f16 100644 --- a/packages/kit/src/core/sync/write_tsconfig.js +++ b/packages/kit/src/core/sync/write_tsconfig.js @@ -36,57 +36,73 @@ export function write_tsconfig(kit, root) { const user_config = load_user_tsconfig(root); if (user_config) validate_user_config(user_config); - const dir = path.join(root, 'node_modules/$app/tsconfig'); + const main = path.join(root, 'node_modules/$app/tsconfig/tsconfig.json'); + const service_worker = path.join(root, 'node_modules/$app/tsconfig/service-worker/tsconfig.json'); - write_if_changed( - path.join(dir, 'tsconfig.json'), - JSON.stringify(get_tsconfig(dir, kit, root), null, '\t') - ); + write_if_changed(main, JSON.stringify(get_tsconfig(main, kit, root), null, '\t')); write_if_changed( - path.join(dir, 'service-worker/tsconfig.json'), - JSON.stringify(get_tsconfig_serviceworker(), null, '\t') + service_worker, + JSON.stringify(get_tsconfig_serviceworker(service_worker, kit, root), null, '\t') ); } +/** + * Without these, compilation will fail + */ +const ESSENTIAL_OPTIONS = { + // svelte-preprocess cannot figure out whether you have a value or a type, so tell TypeScript + // to enforce using \`import type\` instead of \`import\` for Types. + // Also, TypeScript doesn't know about import usages in the template because it only sees the + // script of a Svelte file. Therefore preserve all value imports. + verbatimModuleSyntax: true, + // Vite compiles modules one at a time + isolatedModules: true +}; + +/** + * Options that are strongly recommended, either because not having them is silly or + * because the align TypeScript's behaviour with Vite's, but which can be overwritten + */ +const RECOMMENDED_OPTIONS = { + allowJs: true, + checkJs: true, + forceConsistentCasingInFileNames: true, + resolveJsonModule: true, + moduleDetection: 'force', + moduleResolution: 'bundler', + allowImportingTsExtensions: true, + module: 'esnext', + target: 'esnext', + skipLibCheck: true, + esModuleInterop: true, + noEmit: true // prevent tsconfig error "overwriting input files" - Vite handles the build and ignores this +}; + /** * Generates the tsconfig that the user's tsconfig inherits from. - * @param {string} dir The directory we're writing to + * @param {string} out The file we're writing to * @param {import('types').ValidatedKitConfig} kit - * @param {string} cwd + * @param {string} root The project root */ -export function get_tsconfig(dir, kit, cwd) { +export function get_tsconfig(out, kit, root) { + const dir = path.dirname(out); + /** @param {string} file */ const config_relative = (file) => posixify(path.relative(dir, file)); const config = { compilerOptions: { - paths: get_tsconfig_paths(kit, cwd), + paths: get_tsconfig_paths(kit, dir, root), rootDirs: [config_relative('.'), config_relative(`${kit.outDir}/types`)], types: ['$app/types'], - // essential options - // svelte-preprocess cannot figure out whether you have a value or a type, so tell TypeScript - // to enforce using \`import type\` instead of \`import\` for Types. - // Also, TypeScript doesn't know about import usages in the template because it only sees the - // script of a Svelte file. Therefore preserve all value imports. - verbatimModuleSyntax: true, - // Vite compiles modules one at a time - isolatedModules: true, - - // recommended options - allowJs: true, - checkJs: true, - forceConsistentCasingInFileNames: true, - resolveJsonModule: true, - // This is required for svelte-package to work as expected // Can be overwritten lib: ['esnext', 'DOM', 'DOM.Iterable'], - moduleResolution: 'bundler', - module: 'esnext', - noEmit: true, // prevent tsconfig error "overwriting input files" - Vite handles the build and ignores this - target: 'esnext' + + ...ESSENTIAL_OPTIONS, + ...RECOMMENDED_OPTIONS }, exclude: [ config_relative( @@ -100,9 +116,15 @@ export function get_tsconfig(dir, kit, cwd) { return kit.typescript.config(config) ?? config; } -function get_tsconfig_serviceworker() { +/** + * @param {string} out + * @param {import('types').ValidatedKitConfig} kit + * @param {string} root + */ +function get_tsconfig_serviceworker(out, kit, root) { return { compilerOptions: { + paths: get_tsconfig_paths(kit, path.dirname(out), root), lib: ['ESNext', 'WebWorker'], types: ['$app/types'] } @@ -173,12 +195,13 @@ const value_regex = /^(.*?)((\/\*)|(\.\w+))?$/; * Related to vite alias creation. * * @param {import('types').ValidatedKitConfig} config - * @param {string} cwd + * @param {string} dir + * @param {string} root */ -function get_tsconfig_paths(config, cwd) { +function get_tsconfig_paths(config, dir, root) { /** @param {string} file */ const config_relative = (file) => { - let relative_path = path.relative(config.outDir, file); + let relative_path = path.relative(dir, file); if (!relative_path.startsWith('..')) { relative_path = './' + relative_path; } @@ -188,7 +211,7 @@ function get_tsconfig_paths(config, cwd) { const alias = { ...config.alias }; // Add all `#`-prefixed imports from package.json as path aliases - const imports = read_package_imports(cwd); + const imports = read_package_imports(root); if (imports) { for (const [key, raw_value] of Object.entries(imports)) { if (!key.startsWith('#')) continue; From 52a091b2ef28e553581069a2329a0486fe4c57d0 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 23 Jul 2026 09:35:10 -0400 Subject: [PATCH 13/34] WIP --- packages/kit/src/core/sync/write_tsconfig.js | 60 +++++++++++++------- 1 file changed, 40 insertions(+), 20 deletions(-) diff --git a/packages/kit/src/core/sync/write_tsconfig.js b/packages/kit/src/core/sync/write_tsconfig.js index 808d5a187f16..82e6f90d57ee 100644 --- a/packages/kit/src/core/sync/write_tsconfig.js +++ b/packages/kit/src/core/sync/write_tsconfig.js @@ -1,5 +1,7 @@ +import process from 'node:process'; import fs from 'node:fs'; import path from 'node:path'; +import * as ts from 'typescript'; import { styleText } from 'node:util'; import { posixify } from '../../utils/os.js'; import { read_package_imports, normalize_import_value } from '../../utils/imports.js'; @@ -99,7 +101,7 @@ export function get_tsconfig(out, kit, root) { // This is required for svelte-package to work as expected // Can be overwritten - lib: ['esnext', 'DOM', 'DOM.Iterable'], + lib: ['ESNext', 'DOM', 'DOM.Iterable'], ...ESSENTIAL_OPTIONS, ...RECOMMENDED_OPTIONS @@ -126,7 +128,10 @@ function get_tsconfig_serviceworker(out, kit, root) { compilerOptions: { paths: get_tsconfig_paths(kit, path.dirname(out), root), lib: ['ESNext', 'WebWorker'], - types: ['$app/types'] + types: ['$app/types'], + + ...ESSENTIAL_OPTIONS, + ...RECOMMENDED_OPTIONS } }; } @@ -134,42 +139,57 @@ function get_tsconfig_serviceworker(out, kit, root) { /** @param {string} cwd */ function load_user_tsconfig(cwd) { const file = maybe_file(cwd, 'tsconfig.json') || maybe_file(cwd, 'jsconfig.json'); - if (!file) return; - // we have to eval the file, since it's not parseable as JSON (contains comments) - const json = fs.readFileSync(file, 'utf-8'); + const options = ts.readConfigFile(file, ts.sys.readFile); + + if (options.error) { + let message = `Failed to parse TypeScript config`; + + if (typeof options.error.messageText === 'string') { + message += `: ${options.error.messageText}`; + } + + const error = new Error(message); + error.stack = ''; + + if (options.error.file && options.error.start !== undefined) { + const line_start = options.error.file.text.lastIndexOf('\n', options.error.start); + + const line = + line_start === -1 + ? 0 + : options.error.file.text.slice(0, options.error.start).split('\n').length; + const column = options.error.start - line_start; + + error.stack = `${error.message}\n at ${path.relative(process.cwd(), file)}:${line}:${column}`; + } + + throw error; + } return { kind: path.basename(file), - options: (0, eval)(`(${json})`) + options: options.config }; } /** * @param {{ kind: string, options: any }} config */ -function validate_user_config(config) { +function validate_user_config({ kind, options }) { // we need to check that the user's tsconfig extends the framework config - const extend = config.options.extends; - const extends_framework_config = - typeof extend === 'string' - ? extend === '$app/tsconfig' - : Array.isArray(extend) - ? extend.includes('$app/tsconfig') - : false; - - const options = config.options.compilerOptions || {}; + const extend = Array.isArray(options.extends) ? options.extends : [options.extends]; - if (extends_framework_config) { - const { paths, baseUrl } = options; + if (extend.includes('$app/tsconfig')) { + const { paths, baseUrl } = options.compilerOptions || {}; // TODO: baseUrl will be removed in TypeScript 7.0 if (baseUrl || paths) { console.warn( styleText( ['bold', 'yellow'], - `You have specified a baseUrl and/or paths in your ${config.kind} which interferes with SvelteKit's auto-generated tsconfig.json. ` + + `You have specified a baseUrl and/or paths in your ${kind} which interferes with SvelteKit's auto-generated tsconfig.json. ` + 'Remove it to avoid problems with intellisense. For path aliases, use `config.alias` instead: https://svelte.dev/docs/kit/configuration#alias' ) ); @@ -178,7 +198,7 @@ function validate_user_config(config) { console.warn( styleText( ['bold', 'yellow'], - `Your ${config.kind} should extend the configuration generated by SvelteKit:` + `Your ${kind} should extend the configuration generated by SvelteKit:` ) ); console.warn(`{\n "extends": "$app/tsconfig"\n}`); From 9bde5ccff95c94749fa76c65017510efab93b496 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 23 Jul 2026 13:34:33 -0400 Subject: [PATCH 14/34] validation --- packages/kit/src/core/sync/write_tsconfig.js | 315 ++++++++++++++----- 1 file changed, 244 insertions(+), 71 deletions(-) diff --git a/packages/kit/src/core/sync/write_tsconfig.js b/packages/kit/src/core/sync/write_tsconfig.js index 82e6f90d57ee..f993c458d153 100644 --- a/packages/kit/src/core/sync/write_tsconfig.js +++ b/packages/kit/src/core/sync/write_tsconfig.js @@ -7,6 +7,185 @@ import { posixify } from '../../utils/os.js'; import { read_package_imports, normalize_import_value } from '../../utils/imports.js'; import { write_if_changed } from './utils.js'; +/** + * Generates the tsconfig that the user's tsconfig inherits from. + * @param {import('types').ValidatedKitConfig} kit + * @param {string} root + */ +export function write_tsconfig(kit, root) { + const paths = get_paths(kit, root); + + write_parent_tsconfig( + root, + root, + '$app/tsconfig', + { + compilerOptions: { + paths, + rootDirs: ['.', `${kit.outDir}/types`], + types: ['$app/types'], + + // This is required for svelte-package to work as expected + // Can be overwritten + lib: ['ESNext', 'DOM', 'DOM.Iterable'], + + ...ESSENTIAL_OPTIONS, + ...RECOMMENDED_OPTIONS + }, + exclude: [ + path.extname(kit.files.serviceWorker) + ? kit.files.serviceWorker + : `${kit.files.serviceWorker}/**` + ] + }, + { + extends: '$app/tsconfig', + include: ['src'] + } + ); + + write_parent_tsconfig( + root, + kit.files.serviceWorker, + '$app/tsconfig/service-worker', + { + compilerOptions: { + paths, + types: ['$app/types'], + lib: ['ESNext', 'WebWorker'], + ...ESSENTIAL_OPTIONS + } + }, + { + extends: '$app/tsconfig/service-worker' + } + ); +} + +/** @type {Map} */ +const last_warned = new Map(); + +/** + * + * @param {string} root + * @param {string} dir + * @param {string} parent + * @param {any} config + * @param {any} example + */ +function write_parent_tsconfig(root, dir, parent, config, example) { + const out_dir = path.join(root, `node_modules/${parent}`); + const out_file = path.join(out_dir, 'tsconfig.json'); + + const relative = (/** @type {string} */ file) => path.relative(out_dir, file); + + const paths = + config.compilerOptions?.paths && + Object.fromEntries( + Object.entries(config.compilerOptions?.paths).map(([k, v]) => [k, v.map(relative)]) + ); + + const resolved = { + compilerOptions: { + ...config.compilerOptions, + paths, + rootDirs: config.compilerOptions?.rootDirs?.map(relative) + }, + include: config.include?.map(relative), + exclude: config.exclude?.map(relative) + }; + + write_if_changed(out_file, JSON.stringify(resolved, null, '\t')); + + // now that we've written the parent config, we can resolve the + // user config and validate that nothing important was overwritten + validate_resolved_config(dir, parent, config, example); +} + +/** + * @param {string} dir + * @param {string} parent + * @param {any} config + * @param {any} example + */ +function validate_resolved_config(dir, parent, config, example) { + const user_config = load_user_tsconfig(dir); + if (!user_config) return; + + const last_mtime = last_warned.get(user_config.file) ?? -1; + const mtime = fs.statSync(user_config.file).mtimeMs; + if (last_mtime >= mtime) return; + last_warned.set(user_config.file, mtime); + + const { options } = user_config; + const extend = Array.isArray(options.extends) ? options.extends : [options.extends]; + + if (!extend.includes(parent)) { + console.warn( + styleText( + ['bold', 'yellow'], + `${path.relative(process.cwd(), user_config.file)} should extend SvelteKit's built-in configuration:` + ) + ); + + console.warn(JSON.stringify(example, null, ' ')); + + return; + } + + const resolved = ts.parseJsonConfigFileContent(user_config.options, ts.sys, dir).options; + const warnings = []; + + if (!resolved.types?.includes('$app/types')) { + warnings.push('"types" was overwritten. It must include "$app/types"'); + } + + if (config.compilerOptions?.paths) { + /** @type {Set} */ + const mismatch = new Set(); + + for (const [k, expected] of Object.entries(config.compilerOptions.paths)) { + const actual = resolved.paths?.[k]?.map((x) => + path.resolve(/** @type {string} */ (resolved.pathsBasePath), x) + ); + + if (JSON.stringify(expected) !== JSON.stringify(actual)) { + mismatch.add(remove_trailing_slashstar(k)); + } + } + + if (mismatch.size > 0) { + const joined = Array.from(mismatch) + .map((v) => JSON.stringify(v)) + .join(', ') + .replace(/, ([^,]*)$/, ' and $1'); + + warnings.push(`"paths" was overwritten. Imports from ${joined} may not typecheck`); + } + } + + for (const key in ESSENTIAL_OPTIONS) { + if (resolved[key] !== ESSENTIAL_OPTIONS[key]) { + warnings.push( + `"${key}" was overwritten. It should be ${JSON.stringify(ESSENTIAL_OPTIONS[key])}` + ); + } + } + + if (warnings.length > 0) { + console.warn( + styleText( + ['bold', 'yellow'], + `Found issues while validating ${path.relative(process.cwd(), user_config.file)}` + ) + ); + + for (const warning of warnings) { + console.warn(` - ${warning}`); + } + } +} + /** * @param {string} cwd * @param {string} file @@ -29,28 +208,9 @@ function remove_trailing_slashstar(file) { } } -/** - * Generates the tsconfig that the user's tsconfig inherits from. - * @param {import('types').ValidatedKitConfig} kit - * @param {string} root - */ -export function write_tsconfig(kit, root) { - const user_config = load_user_tsconfig(root); - if (user_config) validate_user_config(user_config); - - const main = path.join(root, 'node_modules/$app/tsconfig/tsconfig.json'); - const service_worker = path.join(root, 'node_modules/$app/tsconfig/service-worker/tsconfig.json'); - - write_if_changed(main, JSON.stringify(get_tsconfig(main, kit, root), null, '\t')); - - write_if_changed( - service_worker, - JSON.stringify(get_tsconfig_serviceworker(service_worker, kit, root), null, '\t') - ); -} - /** * Without these, compilation will fail + * @type {Record} */ const ESSENTIAL_OPTIONS = { // svelte-preprocess cannot figure out whether you have a value or a type, so tell TypeScript @@ -65,6 +225,7 @@ const ESSENTIAL_OPTIONS = { /** * Options that are strongly recommended, either because not having them is silly or * because the align TypeScript's behaviour with Vite's, but which can be overwritten + * @type {Record} */ const RECOMMENDED_OPTIONS = { allowJs: true, @@ -118,29 +279,24 @@ export function get_tsconfig(out, kit, root) { return kit.typescript.config(config) ?? config; } -/** - * @param {string} out - * @param {import('types').ValidatedKitConfig} kit - * @param {string} root - */ -function get_tsconfig_serviceworker(out, kit, root) { - return { - compilerOptions: { - paths: get_tsconfig_paths(kit, path.dirname(out), root), - lib: ['ESNext', 'WebWorker'], - types: ['$app/types'], - - ...ESSENTIAL_OPTIONS, - ...RECOMMENDED_OPTIONS - } - }; -} - /** @param {string} cwd */ function load_user_tsconfig(cwd) { const file = maybe_file(cwd, 'tsconfig.json') || maybe_file(cwd, 'jsconfig.json'); if (!file) return; + const options = load_tsconfig(file); + + return { + file, + kind: path.basename(file), + options + }; +} + +/** + * @param {string} file + */ +function load_tsconfig(file) { const options = ts.readConfigFile(file, ts.sys.readFile); if (options.error) { @@ -168,47 +324,64 @@ function load_user_tsconfig(cwd) { throw error; } - return { - kind: path.basename(file), - options: options.config - }; + return options.config; } +// +const alias_regex = /^(.+?)(\/\*)?$/; +// +const value_regex = /^(.*?)((\/\*)|(\.\w+))?$/; + /** - * @param {{ kind: string, options: any }} config + * Generates tsconfig path aliases from kit's aliases and the package.json `imports` field. + * Related to vite alias creation. + * + * @param {import('types').ValidatedKitConfig} config + * @param {string} root + * @returns {Record} */ -function validate_user_config({ kind, options }) { - // we need to check that the user's tsconfig extends the framework config - const extend = Array.isArray(options.extends) ? options.extends : [options.extends]; +function get_paths(config, root) { + const alias = { ...config.alias }; - if (extend.includes('$app/tsconfig')) { - const { paths, baseUrl } = options.compilerOptions || {}; - - // TODO: baseUrl will be removed in TypeScript 7.0 - if (baseUrl || paths) { - console.warn( - styleText( - ['bold', 'yellow'], - `You have specified a baseUrl and/or paths in your ${kind} which interferes with SvelteKit's auto-generated tsconfig.json. ` + - 'Remove it to avoid problems with intellisense. For path aliases, use `config.alias` instead: https://svelte.dev/docs/kit/configuration#alias' - ) - ); + // Add all `#`-prefixed imports from package.json as path aliases + const imports = read_package_imports(root); + if (imports) { + for (const [key, raw_value] of Object.entries(imports)) { + if (!key.startsWith('#')) continue; + const value = normalize_import_value(raw_value); + if (value) { + alias[key] = value; + } } - } else { - console.warn( - styleText( - ['bold', 'yellow'], - `Your ${kind} should extend the configuration generated by SvelteKit:` - ) - ); - console.warn(`{\n "extends": "$app/tsconfig"\n}`); } -} -// -const alias_regex = /^(.+?)(\/\*)?$/; -// -const value_regex = /^(.*?)((\/\*)|(\.\w+))?$/; + /** @type {Record} */ + const paths = {}; + + for (const [key, value] of Object.entries(alias)) { + const key_match = alias_regex.exec(key); + if (!key_match) throw new Error(`Invalid alias key: ${key}`); + + const value_match = value_regex.exec(value); + if (!value_match) throw new Error(`Invalid alias value: ${value}`); + + const resolved = path.resolve(root, remove_trailing_slashstar(value)); + const slashstar = key_match[2]; + + if (slashstar) { + paths[key] = [resolved + '/*']; + } else { + paths[key] = [resolved]; + const fileending = value_match[4]; + + if (!fileending && !(key + '/*' in alias)) { + paths[key + '/*'] = [resolved + '/*']; + } + } + } + + return paths; +} /** * Generates tsconfig path aliases from kit's aliases and the package.json `imports` field. From 34785ce99e008954df580d1ec2d6b812e7062803 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 23 Jul 2026 13:46:06 -0400 Subject: [PATCH 15/34] WIP --- packages/kit/src/core/sync/write_tsconfig.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/kit/src/core/sync/write_tsconfig.js b/packages/kit/src/core/sync/write_tsconfig.js index f993c458d153..235ab170f9b1 100644 --- a/packages/kit/src/core/sync/write_tsconfig.js +++ b/packages/kit/src/core/sync/write_tsconfig.js @@ -1,7 +1,7 @@ import process from 'node:process'; import fs from 'node:fs'; import path from 'node:path'; -import * as ts from 'typescript'; +import ts from 'typescript'; import { styleText } from 'node:util'; import { posixify } from '../../utils/os.js'; import { read_package_imports, normalize_import_value } from '../../utils/imports.js'; @@ -32,16 +32,13 @@ export function write_tsconfig(kit, root) { ...ESSENTIAL_OPTIONS, ...RECOMMENDED_OPTIONS }, - exclude: [ - path.extname(kit.files.serviceWorker) - ? kit.files.serviceWorker - : `${kit.files.serviceWorker}/**` - ] + exclude: [kit.files.serviceWorker] }, { extends: '$app/tsconfig', include: ['src'] - } + }, + kit.typescript.config ); write_parent_tsconfig( @@ -72,8 +69,9 @@ const last_warned = new Map(); * @param {string} parent * @param {any} config * @param {any} example + * @param {(input: any) => any} [transform] TODO get rid of this */ -function write_parent_tsconfig(root, dir, parent, config, example) { +function write_parent_tsconfig(root, dir, parent, config, example, transform) { const out_dir = path.join(root, `node_modules/${parent}`); const out_file = path.join(out_dir, 'tsconfig.json'); @@ -85,7 +83,7 @@ function write_parent_tsconfig(root, dir, parent, config, example) { Object.entries(config.compilerOptions?.paths).map(([k, v]) => [k, v.map(relative)]) ); - const resolved = { + let resolved = { compilerOptions: { ...config.compilerOptions, paths, @@ -95,6 +93,8 @@ function write_parent_tsconfig(root, dir, parent, config, example) { exclude: config.exclude?.map(relative) }; + resolved = transform?.(resolved) ?? resolved; + write_if_changed(out_file, JSON.stringify(resolved, null, '\t')); // now that we've written the parent config, we can resolve the @@ -112,6 +112,7 @@ function validate_resolved_config(dir, parent, config, example) { const user_config = load_user_tsconfig(dir); if (!user_config) return; + // skip validation if the file wasn't written to since the last check const last_mtime = last_warned.get(user_config.file) ?? -1; const mtime = fs.statSync(user_config.file).mtimeMs; if (last_mtime >= mtime) return; From 8e697bd0dbcc3bfafa22a8f4d93bcdf150a17ee6 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 23 Jul 2026 13:46:36 -0400 Subject: [PATCH 16/34] move --- packages/kit/src/core/sync/sync.js | 2 +- .../sync/{write_tsconfig.js => write_tsconfig/index.js} | 6 +++--- .../index.spec.js} | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) rename packages/kit/src/core/sync/{write_tsconfig.js => write_tsconfig/index.js} (98%) rename packages/kit/src/core/sync/{write_tsconfig.spec.js => write_tsconfig/index.spec.js} (97%) diff --git a/packages/kit/src/core/sync/sync.js b/packages/kit/src/core/sync/sync.js index 7510e313e37f..a70bfe6e8527 100644 --- a/packages/kit/src/core/sync/sync.js +++ b/packages/kit/src/core/sync/sync.js @@ -1,7 +1,7 @@ import path from 'node:path'; import create_manifest_data from './create_manifest_data/index.js'; import { write_client_manifest } from './write_client_manifest.js'; -import { write_tsconfig } from './write_tsconfig.js'; +import { write_tsconfig } from './write_tsconfig/index.js'; import { write_types, write_all_types } from './write_types/index.js'; import { write_ambient } from './write_ambient.js'; import { write_non_ambient } from './write_non_ambient.js'; diff --git a/packages/kit/src/core/sync/write_tsconfig.js b/packages/kit/src/core/sync/write_tsconfig/index.js similarity index 98% rename from packages/kit/src/core/sync/write_tsconfig.js rename to packages/kit/src/core/sync/write_tsconfig/index.js index 235ab170f9b1..325755e8d5de 100644 --- a/packages/kit/src/core/sync/write_tsconfig.js +++ b/packages/kit/src/core/sync/write_tsconfig/index.js @@ -3,9 +3,9 @@ import fs from 'node:fs'; import path from 'node:path'; import ts from 'typescript'; import { styleText } from 'node:util'; -import { posixify } from '../../utils/os.js'; -import { read_package_imports, normalize_import_value } from '../../utils/imports.js'; -import { write_if_changed } from './utils.js'; +import { posixify } from '../../../utils/os.js'; +import { read_package_imports, normalize_import_value } from '../../../utils/imports.js'; +import { write_if_changed } from '../utils.js'; /** * Generates the tsconfig that the user's tsconfig inherits from. diff --git a/packages/kit/src/core/sync/write_tsconfig.spec.js b/packages/kit/src/core/sync/write_tsconfig/index.spec.js similarity index 97% rename from packages/kit/src/core/sync/write_tsconfig.spec.js rename to packages/kit/src/core/sync/write_tsconfig/index.spec.js index 0a661c4c6976..1cccdf58701d 100644 --- a/packages/kit/src/core/sync/write_tsconfig.spec.js +++ b/packages/kit/src/core/sync/write_tsconfig/index.spec.js @@ -1,6 +1,6 @@ import { assert, expect, test } from 'vitest'; import { validate_config } from '../config/index.js'; -import { get_tsconfig } from './write_tsconfig.js'; +import { get_tsconfig } from './write_tsconfig/index.js'; test('Creates tsconfig path aliases from kit.alias', () => { const { kit } = validate_config({ From e30e570510487be4bf4491fc23341f98ad7c2649 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 23 Jul 2026 13:50:59 -0400 Subject: [PATCH 17/34] update tests --- .../kit/src/core/sync/write_tsconfig/index.spec.js | 10 +++++----- .../test-app}/package.json | 0 2 files changed, 5 insertions(+), 5 deletions(-) rename packages/kit/src/core/sync/{write_tsconfig_test => write_tsconfig/test-app}/package.json (100%) diff --git a/packages/kit/src/core/sync/write_tsconfig/index.spec.js b/packages/kit/src/core/sync/write_tsconfig/index.spec.js index 1cccdf58701d..f0de90c47510 100644 --- a/packages/kit/src/core/sync/write_tsconfig/index.spec.js +++ b/packages/kit/src/core/sync/write_tsconfig/index.spec.js @@ -1,6 +1,6 @@ import { assert, expect, test } from 'vitest'; -import { validate_config } from '../config/index.js'; -import { get_tsconfig } from './write_tsconfig/index.js'; +import { validate_config } from '../../config/index.js'; +import { get_tsconfig } from './index.js'; test('Creates tsconfig path aliases from kit.alias', () => { const { kit } = validate_config({ @@ -26,8 +26,8 @@ test('Creates tsconfig path aliases from kit.alias', () => { key: ['../value'], 'key/*': ['../some/other/value/*'], keyToFile: ['../path/to/file.ts'], - $routes: ['./types/src/routes'], - '$routes/*': ['./types/src/routes/*'] + $routes: ['../.svelte-kit/types/src/routes'], + '$routes/*': ['../.svelte-kit/types/src/routes/*'] }); }); @@ -36,7 +36,7 @@ test('Creates tsconfig path aliases from package.json import map', () => { const { compilerOptions } = get_tsconfig( 'dir/tsconfig.json', kit, - import.meta.dirname + '/write_tsconfig_test' + import.meta.dirname + '/test-app' ); // No `#`-prefixed path aliases because the package.json at the test cwd diff --git a/packages/kit/src/core/sync/write_tsconfig_test/package.json b/packages/kit/src/core/sync/write_tsconfig/test-app/package.json similarity index 100% rename from packages/kit/src/core/sync/write_tsconfig_test/package.json rename to packages/kit/src/core/sync/write_tsconfig/test-app/package.json From 42b5552777fd336a5e328befb2c863841b9079a6 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 23 Jul 2026 15:07:56 -0400 Subject: [PATCH 18/34] WIP --- .../kit/src/core/sync/write_tsconfig/index.js | 309 +++--------------- .../core/sync/write_tsconfig/index.spec.js | 83 ----- .../kit/src/core/sync/write_tsconfig/utils.js | 160 +++++++++ .../core/sync/write_tsconfig/utils.spec.js | 99 ++++++ 4 files changed, 312 insertions(+), 339 deletions(-) delete mode 100644 packages/kit/src/core/sync/write_tsconfig/index.spec.js create mode 100644 packages/kit/src/core/sync/write_tsconfig/utils.js create mode 100644 packages/kit/src/core/sync/write_tsconfig/utils.spec.js diff --git a/packages/kit/src/core/sync/write_tsconfig/index.js b/packages/kit/src/core/sync/write_tsconfig/index.js index 325755e8d5de..c479e551e424 100644 --- a/packages/kit/src/core/sync/write_tsconfig/index.js +++ b/packages/kit/src/core/sync/write_tsconfig/index.js @@ -3,9 +3,16 @@ import fs from 'node:fs'; import path from 'node:path'; import ts from 'typescript'; import { styleText } from 'node:util'; -import { posixify } from '../../../utils/os.js'; -import { read_package_imports, normalize_import_value } from '../../../utils/imports.js'; import { write_if_changed } from '../utils.js'; +import { + ESSENTIAL_OPTIONS, + extends_parent, + get_subpath_imports, + normalize_config, + RECOMMENDED_OPTIONS, + remove_trailing_slashstar, + validate_resolved_config +} from './utils.js'; /** * Generates the tsconfig that the user's tsconfig inherits from. @@ -59,9 +66,6 @@ export function write_tsconfig(kit, root) { ); } -/** @type {Map} */ -const last_warned = new Map(); - /** * * @param {string} root @@ -72,119 +76,63 @@ const last_warned = new Map(); * @param {(input: any) => any} [transform] TODO get rid of this */ function write_parent_tsconfig(root, dir, parent, config, example, transform) { - const out_dir = path.join(root, `node_modules/${parent}`); - const out_file = path.join(out_dir, 'tsconfig.json'); - - const relative = (/** @type {string} */ file) => path.relative(out_dir, file); - - const paths = - config.compilerOptions?.paths && - Object.fromEntries( - Object.entries(config.compilerOptions?.paths).map(([k, v]) => [k, v.map(relative)]) - ); - - let resolved = { - compilerOptions: { - ...config.compilerOptions, - paths, - rootDirs: config.compilerOptions?.rootDirs?.map(relative) - }, - include: config.include?.map(relative), - exclude: config.exclude?.map(relative) - }; + const out_file = path.join(root, `node_modules/${parent}/tsconfig.json`); - resolved = transform?.(resolved) ?? resolved; + let normalized = normalize_config(out_file, config); + normalized = transform?.(normalized) ?? normalized; - write_if_changed(out_file, JSON.stringify(resolved, null, '\t')); + write_if_changed(out_file, JSON.stringify(normalized, null, '\t')); - // now that we've written the parent config, we can resolve the - // user config and validate that nothing important was overwritten - validate_resolved_config(dir, parent, config, example); -} - -/** - * @param {string} dir - * @param {string} parent - * @param {any} config - * @param {any} example - */ -function validate_resolved_config(dir, parent, config, example) { const user_config = load_user_tsconfig(dir); - if (!user_config) return; - - // skip validation if the file wasn't written to since the last check - const last_mtime = last_warned.get(user_config.file) ?? -1; - const mtime = fs.statSync(user_config.file).mtimeMs; - if (last_mtime >= mtime) return; - last_warned.set(user_config.file, mtime); - const { options } = user_config; - const extend = Array.isArray(options.extends) ? options.extends : [options.extends]; - - if (!extend.includes(parent)) { - console.warn( - styleText( - ['bold', 'yellow'], - `${path.relative(process.cwd(), user_config.file)} should extend SvelteKit's built-in configuration:` - ) - ); - - console.warn(JSON.stringify(example, null, ' ')); - - return; - } + if (user_config && modified_since_last_check(user_config.file)) { + // now that we've written the parent config, we can resolve the + // user config and validate that nothing important was overwritten + if (!extends_parent(user_config.options, parent)) { + console.warn( + styleText( + ['bold', 'yellow'], + `${path.relative(process.cwd(), user_config.file)} should extend SvelteKit's built-in configuration:` + ) + ); - const resolved = ts.parseJsonConfigFileContent(user_config.options, ts.sys, dir).options; - const warnings = []; + console.warn(JSON.stringify(example, null, ' ')); - if (!resolved.types?.includes('$app/types')) { - warnings.push('"types" was overwritten. It must include "$app/types"'); - } + return; + } - if (config.compilerOptions?.paths) { - /** @type {Set} */ - const mismatch = new Set(); + const resolved = ts.parseJsonConfigFileContent(user_config.options, ts.sys, dir).options; + const warnings = validate_resolved_config(resolved, config.compilerOptions); - for (const [k, expected] of Object.entries(config.compilerOptions.paths)) { - const actual = resolved.paths?.[k]?.map((x) => - path.resolve(/** @type {string} */ (resolved.pathsBasePath), x) + if (warnings.length > 0) { + console.warn( + styleText( + ['bold', 'yellow'], + `Found issues while validating ${path.relative(process.cwd(), user_config.file)}` + ) ); - if (JSON.stringify(expected) !== JSON.stringify(actual)) { - mismatch.add(remove_trailing_slashstar(k)); + for (const warning of warnings) { + console.warn(` - ${warning}`); } } - - if (mismatch.size > 0) { - const joined = Array.from(mismatch) - .map((v) => JSON.stringify(v)) - .join(', ') - .replace(/, ([^,]*)$/, ' and $1'); - - warnings.push(`"paths" was overwritten. Imports from ${joined} may not typecheck`); - } } +} - for (const key in ESSENTIAL_OPTIONS) { - if (resolved[key] !== ESSENTIAL_OPTIONS[key]) { - warnings.push( - `"${key}" was overwritten. It should be ${JSON.stringify(ESSENTIAL_OPTIONS[key])}` - ); - } - } +/** @type {Map} */ +const mtimes = new Map(); - if (warnings.length > 0) { - console.warn( - styleText( - ['bold', 'yellow'], - `Found issues while validating ${path.relative(process.cwd(), user_config.file)}` - ) - ); +/** + * Returns true if the file was modified since we last got here, + * otherwise we can skip warnings + * @param {string} file + */ +function modified_since_last_check(file) { + const a = mtimes.get(file) ?? -1; + const b = fs.statSync(file).mtimeMs; + mtimes.set(file, b); - for (const warning of warnings) { - console.warn(` - ${warning}`); - } - } + return b > a; } /** @@ -198,88 +146,6 @@ function maybe_file(cwd, file) { } } -/** - * @param {string} file - */ -function remove_trailing_slashstar(file) { - if (file.endsWith('/*')) { - return file.slice(0, -2); - } else { - return file; - } -} - -/** - * Without these, compilation will fail - * @type {Record} - */ -const ESSENTIAL_OPTIONS = { - // svelte-preprocess cannot figure out whether you have a value or a type, so tell TypeScript - // to enforce using \`import type\` instead of \`import\` for Types. - // Also, TypeScript doesn't know about import usages in the template because it only sees the - // script of a Svelte file. Therefore preserve all value imports. - verbatimModuleSyntax: true, - // Vite compiles modules one at a time - isolatedModules: true -}; - -/** - * Options that are strongly recommended, either because not having them is silly or - * because the align TypeScript's behaviour with Vite's, but which can be overwritten - * @type {Record} - */ -const RECOMMENDED_OPTIONS = { - allowJs: true, - checkJs: true, - forceConsistentCasingInFileNames: true, - resolveJsonModule: true, - moduleDetection: 'force', - moduleResolution: 'bundler', - allowImportingTsExtensions: true, - module: 'esnext', - target: 'esnext', - skipLibCheck: true, - esModuleInterop: true, - noEmit: true // prevent tsconfig error "overwriting input files" - Vite handles the build and ignores this -}; - -/** - * Generates the tsconfig that the user's tsconfig inherits from. - * @param {string} out The file we're writing to - * @param {import('types').ValidatedKitConfig} kit - * @param {string} root The project root - */ -export function get_tsconfig(out, kit, root) { - const dir = path.dirname(out); - - /** @param {string} file */ - const config_relative = (file) => posixify(path.relative(dir, file)); - - const config = { - compilerOptions: { - paths: get_tsconfig_paths(kit, dir, root), - rootDirs: [config_relative('.'), config_relative(`${kit.outDir}/types`)], - types: ['$app/types'], - - // This is required for svelte-package to work as expected - // Can be overwritten - lib: ['ESNext', 'DOM', 'DOM.Iterable'], - - ...ESSENTIAL_OPTIONS, - ...RECOMMENDED_OPTIONS - }, - exclude: [ - config_relative( - path.extname(kit.files.serviceWorker) - ? kit.files.serviceWorker - : `${kit.files.serviceWorker}/**` - ) - ] - }; - - return kit.typescript.config(config) ?? config; -} - /** @param {string} cwd */ function load_user_tsconfig(cwd) { const file = maybe_file(cwd, 'tsconfig.json') || maybe_file(cwd, 'jsconfig.json'); @@ -342,19 +208,10 @@ const value_regex = /^(.*?)((\/\*)|(\.\w+))?$/; * @returns {Record} */ function get_paths(config, root) { - const alias = { ...config.alias }; - - // Add all `#`-prefixed imports from package.json as path aliases - const imports = read_package_imports(root); - if (imports) { - for (const [key, raw_value] of Object.entries(imports)) { - if (!key.startsWith('#')) continue; - const value = normalize_import_value(raw_value); - if (value) { - alias[key] = value; - } - } - } + const alias = { + ...config.alias, + ...get_subpath_imports(root) + }; /** @type {Record} */ const paths = {}; @@ -383,63 +240,3 @@ function get_paths(config, root) { return paths; } - -/** - * Generates tsconfig path aliases from kit's aliases and the package.json `imports` field. - * Related to vite alias creation. - * - * @param {import('types').ValidatedKitConfig} config - * @param {string} dir - * @param {string} root - */ -function get_tsconfig_paths(config, dir, root) { - /** @param {string} file */ - const config_relative = (file) => { - let relative_path = path.relative(dir, file); - if (!relative_path.startsWith('..')) { - relative_path = './' + relative_path; - } - return posixify(relative_path); - }; - - const alias = { ...config.alias }; - - // Add all `#`-prefixed imports from package.json as path aliases - const imports = read_package_imports(root); - if (imports) { - for (const [key, raw_value] of Object.entries(imports)) { - if (!key.startsWith('#')) continue; - const value = normalize_import_value(raw_value); - if (value) { - alias[key] = value; - } - } - } - - /** @type {Record} */ - const paths = {}; - - for (const [key, value] of Object.entries(alias)) { - const key_match = alias_regex.exec(key); - if (!key_match) throw new Error(`Invalid alias key: ${key}`); - - const value_match = value_regex.exec(value); - if (!value_match) throw new Error(`Invalid alias value: ${value}`); - - const rel_path = config_relative(remove_trailing_slashstar(value)); - const slashstar = key_match[2]; - - if (slashstar) { - paths[key] = [rel_path + '/*']; - } else { - paths[key] = [rel_path]; - const fileending = value_match[4]; - - if (!fileending && !(key + '/*' in alias)) { - paths[key + '/*'] = [rel_path + '/*']; - } - } - } - - return paths; -} diff --git a/packages/kit/src/core/sync/write_tsconfig/index.spec.js b/packages/kit/src/core/sync/write_tsconfig/index.spec.js deleted file mode 100644 index f0de90c47510..000000000000 --- a/packages/kit/src/core/sync/write_tsconfig/index.spec.js +++ /dev/null @@ -1,83 +0,0 @@ -import { assert, expect, test } from 'vitest'; -import { validate_config } from '../../config/index.js'; -import { get_tsconfig } from './index.js'; - -test('Creates tsconfig path aliases from kit.alias', () => { - const { kit } = validate_config({ - kit: { - alias: { - simpleKey: 'simple/value', - key: 'value', - 'key/*': 'some/other/value/*', - keyToFile: 'path/to/file.ts', - $routes: '.svelte-kit/types/src/routes' - } - } - }); - - // Use a cwd without a package.json so no `#`-prefixed imports are picked up - const { compilerOptions } = get_tsconfig('dir/tsconfig.json', kit, import.meta.dirname); - - // No `#`-prefixed path aliases because the package.json at the test cwd - // doesn't have an `imports` field - expect(compilerOptions.paths).toEqual({ - simpleKey: ['../simple/value'], - 'simpleKey/*': ['../simple/value/*'], - key: ['../value'], - 'key/*': ['../some/other/value/*'], - keyToFile: ['../path/to/file.ts'], - $routes: ['../.svelte-kit/types/src/routes'], - '$routes/*': ['../.svelte-kit/types/src/routes/*'] - }); -}); - -test('Creates tsconfig path aliases from package.json import map', () => { - const { kit } = validate_config({}); - const { compilerOptions } = get_tsconfig( - 'dir/tsconfig.json', - kit, - import.meta.dirname + '/test-app' - ); - - // No `#`-prefixed path aliases because the package.json at the test cwd - // doesn't have an `imports` field - expect(compilerOptions.paths).toEqual({ - '#lib': ['../src/lib'], - '#lib/*': ['../src/lib/*'] - }); -}); - -test('Allows generated tsconfig to be mutated', () => { - const { kit } = validate_config({ - kit: { - typescript: { - config: (config) => { - config.extends = 'some/other/tsconfig.json'; - } - } - } - }); - - const config = get_tsconfig('dir/tsconfig.json', kit, '.'); - - // @ts-expect-error - assert.equal(config.extends, 'some/other/tsconfig.json'); -}); - -test('Allows generated tsconfig to be replaced', () => { - const { kit } = validate_config({ - kit: { - typescript: { - config: (config) => ({ - ...config, - extends: 'some/other/tsconfig.json' - }) - } - } - }); - - const config = get_tsconfig('dir/tsconfig.json', kit, '.'); - - // @ts-expect-error - assert.equal(config.extends, 'some/other/tsconfig.json'); -}); diff --git a/packages/kit/src/core/sync/write_tsconfig/utils.js b/packages/kit/src/core/sync/write_tsconfig/utils.js new file mode 100644 index 000000000000..91f90b1c0bd0 --- /dev/null +++ b/packages/kit/src/core/sync/write_tsconfig/utils.js @@ -0,0 +1,160 @@ +import path from 'node:path'; +import { normalize_import_value, read_package_imports } from '../../../utils/imports.js'; + +/** + * Without these, compilation will fail + * @type {Record} + */ +export const ESSENTIAL_OPTIONS = { + // svelte-preprocess cannot figure out whether you have a value or a type, so tell TypeScript + // to enforce using \`import type\` instead of \`import\` for Types. + // Also, TypeScript doesn't know about import usages in the template because it only sees the + // script of a Svelte file. Therefore preserve all value imports. + verbatimModuleSyntax: true, + // Vite compiles modules one at a time + isolatedModules: true +}; + +/** + * Options that are strongly recommended, either because not having them is silly or + * because the align TypeScript's behaviour with Vite's, but which can be overwritten + * @type {Record} + */ +export const RECOMMENDED_OPTIONS = { + allowJs: true, + checkJs: true, + forceConsistentCasingInFileNames: true, + resolveJsonModule: true, + moduleDetection: 'force', + moduleResolution: 'bundler', + allowImportingTsExtensions: true, + module: 'esnext', + target: 'esnext', + skipLibCheck: true, + esModuleInterop: true, + noEmit: true // prevent tsconfig error "overwriting input files" - Vite handles the build and ignores this +}; + +/** + * @param {any} options + * @param {string} parent + */ +export function extends_parent(options, parent) { + const o = options.extends; + return Array.isArray(o) ? o.includes(parent) : o === parent; +} + +/** + * Makes paths relative to the output file + * @param {string} out + * @param {any} config + * @param {(input: any) => any} [transform] TODO get rid of this option + */ +export function normalize_config(out, config, transform) { + const dir = path.dirname(out); + const relative = (/** @type {string} */ file) => path.relative(dir, file); + + const paths = + config.compilerOptions?.paths && + Object.fromEntries( + Object.entries(config.compilerOptions?.paths).map(([k, v]) => [k, v.map(relative)]) + ); + + const normalized = { + ...config, + compilerOptions: { + ...config.compilerOptions, + paths, + rootDirs: config.compilerOptions?.rootDirs?.map(relative) + }, + include: config.include?.map(relative), + exclude: config.exclude?.map(relative) + }; + + return transform?.(normalized) ?? normalized; +} + +/** + * @param {any} resolved + * @param {any} options + */ +export function validate_resolved_config(resolved, options) { + const warnings = []; + + const join = (/** @type {string[]} */ array) => + array + .map((v) => JSON.stringify(v)) + .join(', ') + .replace(/, ([^,]*)$/, ' and $1'); + + const missing_types = options.types?.filter( + (/** @type {string} */ type) => !resolved.types?.includes(type) + ); + + if (missing_types.length > 0) { + warnings.push(`"types" was overwritten. It must include ${join(missing_types)}`); + } + + if (options.paths) { + /** @type {Set} */ + const mismatch = new Set(); + + for (const [k, expected] of Object.entries(options.paths)) { + const actual = resolved.paths?.[k]?.map((x) => + path.resolve(/** @type {string} */ (resolved.pathsBasePath), x) + ); + + if (JSON.stringify(expected) !== JSON.stringify(actual)) { + mismatch.add(remove_trailing_slashstar(k)); + } + } + + if (mismatch.size > 0) { + const joined = join([...mismatch]); + + warnings.push(`"paths" was overwritten. Imports from ${joined} may not typecheck`); + } + } + + for (const key in ESSENTIAL_OPTIONS) { + if (resolved[key] !== options[key]) { + warnings.push(`"${key}" was overwritten. It should be ${JSON.stringify(options[key])}`); + } + } + + return warnings; +} + +/** + * @param {string} file + */ +export function remove_trailing_slashstar(file) { + if (file.endsWith('/*')) { + return file.slice(0, -2); + } else { + return file; + } +} + +/** + * @param {string} root + */ +export function get_subpath_imports(root) { + // Add all `#`-prefixed imports from package.json as path aliases + const imports = read_package_imports(root); + + /** @type {Record} */ + const alias = {}; + + if (imports) { + for (const [key, raw_value] of Object.entries(imports)) { + if (!key.startsWith('#')) continue; + const value = normalize_import_value(raw_value); + if (value) { + alias[key] = value; + } + } + } + + return alias; +} diff --git a/packages/kit/src/core/sync/write_tsconfig/utils.spec.js b/packages/kit/src/core/sync/write_tsconfig/utils.spec.js new file mode 100644 index 000000000000..15ece400a1ec --- /dev/null +++ b/packages/kit/src/core/sync/write_tsconfig/utils.spec.js @@ -0,0 +1,99 @@ +import { assert, describe, test } from 'vitest'; +import { + extends_parent, + get_subpath_imports, + normalize_config, + validate_resolved_config +} from './utils.js'; + +describe('normalize_config', () => { + test('normalizes rootDirs', () => { + assert.deepEqual( + normalize_config('/path/to/tsconfig.json', { + compilerOptions: { + rootDirs: ['/path/of/my/src', '/path/of/my/generated/stuff'] + } + }).compilerOptions.rootDirs, + ['../of/my/src', '../of/my/generated/stuff'] + ); + }); + + test('normalizes paths', () => { + assert.deepEqual( + normalize_config('/path/to/tsconfig.json', { + compilerOptions: { + paths: { + '#x': ['/path/of/my/alias'] + } + } + }).compilerOptions.paths, + { + '#x': ['../of/my/alias'] + } + ); + }); + + test('mutates a config', () => { + const { a, b } = normalize_config( + '/doesnt/matter', + { + a: 1, + b: 2 + }, + (config) => { + config.b += 1; + } + ); + + assert.equal(a, 1); + assert.equal(b, 3); + }); + + test('replaces a config', () => { + const { a, b, c } = normalize_config( + '/doesnt/matter', + { + a: 1, + b: 2 + }, + (config) => ({ ...config, c: 3 }) + ); + + assert.equal(a, 1); + assert.equal(b, 2); + assert.equal(c, 3); + }); +}); + +describe('get_subpath_imports', () => { + test('gets normalized subpath imports', () => { + assert.deepEqual(get_subpath_imports(import.meta.dirname + '/test-app'), { + '#lib': 'src/lib', + '#lib/*': 'src/lib/*' + }); + }); +}); + +describe('extends_parent', () => { + const parent = 'PARENT'; + + test('validates that a config extends a parent (string)', () => { + assert.equal(extends_parent({ extends: parent }, parent), true); + }); + + test('validates that a config extends a parent (array)', () => { + assert.equal(extends_parent({ extends: [parent] }, parent), true); + }); + + test('validates that a config does not extend a parent', () => { + assert.equal(extends_parent({}, parent), false); + }); +}); + +describe('validate_resolved_config', () => { + test('warns if types is overwritten', () => { + const warnings = validate_resolved_config({ types: [] }, { types: ['pinky', 'perky'] }); + + assert.deepEqual(warnings, ['"types" was overwritten. It must include "pinky" and "perky"']); + }); +}); From 456ef07af7136bc88dac2cbb8967f505ad31a767 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 23 Jul 2026 15:27:51 -0400 Subject: [PATCH 19/34] tweak --- packages/kit/src/core/sync/sync.js | 10 ++++------ packages/kit/src/core/sync/write_ambient.js | 18 ------------------ ...write_non_ambient.js => write_app_types.js} | 6 ++++-- .../src/core/sync/write_types/index.spec.js | 4 ++-- 4 files changed, 10 insertions(+), 28 deletions(-) delete mode 100644 packages/kit/src/core/sync/write_ambient.js rename packages/kit/src/core/sync/{write_non_ambient.js => write_app_types.js} (96%) diff --git a/packages/kit/src/core/sync/sync.js b/packages/kit/src/core/sync/sync.js index a70bfe6e8527..c4780f90ba22 100644 --- a/packages/kit/src/core/sync/sync.js +++ b/packages/kit/src/core/sync/sync.js @@ -3,8 +3,7 @@ import create_manifest_data from './create_manifest_data/index.js'; import { write_client_manifest } from './write_client_manifest.js'; import { write_tsconfig } from './write_tsconfig/index.js'; import { write_types, write_all_types } from './write_types/index.js'; -import { write_ambient } from './write_ambient.js'; -import { write_non_ambient } from './write_non_ambient.js'; +import { write_app_types } from './write_app_types.js'; import { write_server } from './write_server.js'; import { create_node_analyser, @@ -20,7 +19,6 @@ import { write_env } from './write_env.js'; */ export function init(config, root) { write_tsconfig(config.kit, root); - write_ambient(config.kit); } /** @@ -36,7 +34,7 @@ export function create(config, root) { write_client_manifest(config.kit, manifest_data, `${output}/client`); write_server(config, output, root); write_all_types(config, manifest_data, root); - write_non_ambient(config.kit, manifest_data, root); + write_app_types(config.kit, manifest_data, root); return { manifest_data }; } @@ -64,7 +62,7 @@ export function update(config, manifest_data, file, root) { } write_types(config, manifest_data, file, root); - write_non_ambient(config.kit, manifest_data, root); + write_app_types(config.kit, manifest_data, root); } /** @@ -86,7 +84,7 @@ export function all_types(config, root) { init(config, root); const manifest_data = create_manifest_data({ config, cwd: root }); write_all_types(config, manifest_data, root); - write_non_ambient(config.kit, manifest_data, root); + write_app_types(config.kit, manifest_data, root); } /** diff --git a/packages/kit/src/core/sync/write_ambient.js b/packages/kit/src/core/sync/write_ambient.js deleted file mode 100644 index fe459c296801..000000000000 --- a/packages/kit/src/core/sync/write_ambient.js +++ /dev/null @@ -1,18 +0,0 @@ -import path from 'node:path'; -import { GENERATED_COMMENT } from '../../constants.js'; -import { write_if_changed } from './utils.js'; - -// TODO get rid of this, it's useless - -/** - * Writes ambient declarations including types reference to @sveltejs/kit, - * and the existing environment variables in process.env to - * $env/static/private and $env/static/public - * @param {import('types').ValidatedKitConfig} config - */ -export function write_ambient(config) { - /** @type {string} */ - const content = `${GENERATED_COMMENT}\n/// `; - - write_if_changed(path.join(config.outDir, 'ambient.d.ts'), content); -} diff --git a/packages/kit/src/core/sync/write_non_ambient.js b/packages/kit/src/core/sync/write_app_types.js similarity index 96% rename from packages/kit/src/core/sync/write_non_ambient.js rename to packages/kit/src/core/sync/write_app_types.js index 59595c88e37e..d0a8c71614a9 100644 --- a/packages/kit/src/core/sync/write_non_ambient.js +++ b/packages/kit/src/core/sync/write_app_types.js @@ -250,12 +250,14 @@ function generate_app_types(manifest_data, config, dir) { } /** - * Writes non-ambient declarations to the output directory + * Writes `node_modules/$app/types/index.d.ts`. This file contains + * declarations for `$app/types` and `svelte/elements`, and + * imports `env.ts` which declares `$app/env/(public|private)` * @param {import('types').ValidatedKitConfig} config * @param {import('types').ManifestData} manifest_data * @param {string} root */ -export function write_non_ambient(config, manifest_data, root) { +export function write_app_types(config, manifest_data, root) { const dir = path.join(root, 'node_modules/$app/types'); const content = [ diff --git a/packages/kit/src/core/sync/write_types/index.spec.js b/packages/kit/src/core/sync/write_types/index.spec.js index e805ea0f24d5..cc6e369de4ba 100644 --- a/packages/kit/src/core/sync/write_types/index.spec.js +++ b/packages/kit/src/core/sync/write_types/index.spec.js @@ -6,7 +6,7 @@ import { assert, describe, expect, test } from 'vitest'; import { rimraf } from '../../../utils/filesystem.js'; import create_manifest_data from '../create_manifest_data/index.js'; import { tweak_types, write_all_types } from './index.js'; -import { write_non_ambient } from '../write_non_ambient.js'; +import { write_app_types } from '../write_app_types.js'; import { validate_config } from '../../config/index.js'; import { write_env } from '../write_env.js'; @@ -33,7 +33,7 @@ function run_test(dir) { }); write_all_types(initial, manifest, root); - write_non_ambient(initial.kit, manifest, root); + write_app_types(initial.kit, manifest, root); write_env('', {}, root); } From c6be788f6ab947cf67b06eda6e959b615fa6dbd2 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 23 Jul 2026 15:28:30 -0400 Subject: [PATCH 20/34] lint --- packages/kit/src/core/sync/write_tsconfig/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/src/core/sync/write_tsconfig/utils.js b/packages/kit/src/core/sync/write_tsconfig/utils.js index 91f90b1c0bd0..cea3d94638e5 100644 --- a/packages/kit/src/core/sync/write_tsconfig/utils.js +++ b/packages/kit/src/core/sync/write_tsconfig/utils.js @@ -100,7 +100,7 @@ export function validate_resolved_config(resolved, options) { const mismatch = new Set(); for (const [k, expected] of Object.entries(options.paths)) { - const actual = resolved.paths?.[k]?.map((x) => + const actual = resolved.paths?.[k]?.map((/** @type {string} */ x) => path.resolve(/** @type {string} */ (resolved.pathsBasePath), x) ); From e77b8d5af8d941034f1dc7ccf779d92c98c24941 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 23 Jul 2026 15:53:24 -0400 Subject: [PATCH 21/34] some docs --- .../30-project-structure.md | 6 +- .../20-$app-tsconfig-service-worker.md | 14 ++++ .../docs/98-reference/20-$app-tsconfig.md | 23 ++++++ documentation/docs/98-reference/54-types.md | 71 ------------------- 4 files changed, 41 insertions(+), 73 deletions(-) create mode 100644 documentation/docs/98-reference/20-$app-tsconfig-service-worker.md create mode 100644 documentation/docs/98-reference/20-$app-tsconfig.md diff --git a/documentation/docs/10-getting-started/30-project-structure.md b/documentation/docs/10-getting-started/30-project-structure.md index c356e6743415..cdc04118e615 100644 --- a/documentation/docs/10-getting-started/30-project-structure.md +++ b/documentation/docs/10-getting-started/30-project-structure.md @@ -13,11 +13,13 @@ my-project/ │ │ └ [your param matchers] │ ├ routes/ │ │ └ [your routes] +│ ├ service-worker/ +│ │ ├ index.js +│ │ └ tsconfig.json │ ├ app.html │ ├ error.html │ ├ hooks.client.js │ ├ hooks.server.js -│ ├ service-worker.js │ └ instrumentation.server.js ├ static/ │ └ [your static assets] @@ -52,7 +54,7 @@ The `src` directory contains the meat of your project. Everything except `src/ro - `%sveltekit.error.message%` — the error message - `hooks.client.js` contains your client [hooks](hooks) - `hooks.server.js` contains your server [hooks](hooks) -- `service-worker.js` contains your [service worker](service-workers) +- `service-worker` contains your [service worker](service-workers) - `instrumentation.server.js` contains your [observability](observability) setup and instrumentation code - Requires adapter support. If your adapter supports it, it is guaranteed to run prior to loading and running your application code. diff --git a/documentation/docs/98-reference/20-$app-tsconfig-service-worker.md b/documentation/docs/98-reference/20-$app-tsconfig-service-worker.md new file mode 100644 index 000000000000..97544f565f07 --- /dev/null +++ b/documentation/docs/98-reference/20-$app-tsconfig-service-worker.md @@ -0,0 +1,14 @@ +--- +title: $app/tsconfig/service-worker +--- + +This module contains TypeScript configuration tailored for your service worker: + +```json +/// file: src/service-worker/tsconfig.json +{ + "extends": "$app/tsconfig/service-worker" +} +``` + +You can extend this configuration with your own `compilerOptions`, adhering to the same restrictions as [`$app/tsconfig]($app-tsconfig). diff --git a/documentation/docs/98-reference/20-$app-tsconfig.md b/documentation/docs/98-reference/20-$app-tsconfig.md new file mode 100644 index 000000000000..f6534f4d29b3 --- /dev/null +++ b/documentation/docs/98-reference/20-$app-tsconfig.md @@ -0,0 +1,23 @@ +--- +title: $app/tsconfig +--- + +This module contains TypeScript configuration tailored for your app. Your own config should extend it — a typical `tsconfig.json` looks like this: + +```json +/// file: tsconfig.json +{ + "extends": "$app/tsconfig", + "includes": ["src", "test"], + "excludes": ["src/service-worker"] +} +``` + +You can extend this configuration with your own `compilerOptions`. Overriding the following properties may cause things to break — SvelteKit will warn you if this happens: + +- `paths` — this is derived from (deprecated) [`alias`](configuration#alias) config option, together with any [subpath imports](https://nodejs.org/api/packages.html#subpath-imports) specified in your `package.json`, to align behaviour between Vite and TypeScript. Ideally, configure subpath imports rather than using `paths` +- `types` — your needs to be able to 'see' generated module declarations for things like [environment variables](environment-variables), and as such this array must include `"$app/types"` +- `isolatedModules` — must be `true`, as Vite compiles modules one at a time +- `verbatimModuleSyntax` — must be `true`, so that you can safely use type imports in `.svelte` files + +Note that the example configuration above excludes `src/service-worker`, because service workers need to be in their own TypeScript project. If you are using a service worker, create a `src/service-worker/tsconfig.json` that extends [`$app/tsconfig/service-worker`]($app-tsconfig-service-worker). diff --git a/documentation/docs/98-reference/54-types.md b/documentation/docs/98-reference/54-types.md index 077992d02b91..303609691982 100644 --- a/documentation/docs/98-reference/54-types.md +++ b/documentation/docs/98-reference/54-types.md @@ -123,77 +123,6 @@ Starting with version 2.16.0, two additional helper types are provided: `PagePro > > `{ "extends": "$app/tsconfig" }` -### Default tsconfig.json - -The generated `$app/types` file contains a mixture of options. Some are generated programmatically based on your project configuration, and should generally not be overridden without good reason: - -```json -/// file: $app/types -{ - "compilerOptions": { - "paths": { - "#lib": ["../src/lib/index.js"], - "#lib/*": ["../src/lib/*"] - }, - "rootDirs": ["..", "./types"] - }, - "include": [ - "ambient.d.ts", - "non-ambient.d.ts", - "./types/**/$types.d.ts", - "../vite.config.js", - "../vite.config.ts", - "../src/**/*.js", - "../src/**/*.ts", - "../src/**/*.svelte", - "../tests/**/*.js", - "../tests/**/*.ts", - "../tests/**/*.svelte" - ], - "exclude": [ - "../node_modules/**", - "../src/service-worker.js", - "../src/service-worker/**/*.js", - "../src/service-worker.ts", - "../src/service-worker/**/*.ts", - "../src/service-worker.d.ts", - "../src/service-worker/**/*.d.ts" - ] -} -``` - -Others are required for SvelteKit to work properly, and should also be left untouched unless you know what you're doing: - -```json -/// file: .svelte-kit/tsconfig.json -{ - "compilerOptions": { - // this ensures that types are explicitly - // imported with `import type`, which is - // necessary as Svelte/Vite cannot - // otherwise compile components correctly - "verbatimModuleSyntax": true, - - // Vite compiles one TypeScript module - // at a time, rather than compiling - // the entire module graph - "isolatedModules": true, - - // Tell TS it's used only for type-checking - "noEmit": true, - - // This ensures both `vite build` - // and `svelte-package` work correctly - "lib": ["esnext", "DOM", "DOM.Iterable"], - "moduleResolution": "bundler", - "module": "esnext", - "target": "esnext" - } -} -``` - -Use the [`typescript.config` setting](configuration#typescript) of the SvelteKit plugin in `vite.config.js` to extend or modify the generated `tsconfig.json`. - ## app.d.ts The `app.d.ts` file is home to the ambient types of your apps, i.e. types that are available without explicitly importing them. From fc36e347d708ba5acdede90d977fc59fe91b6bb8 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 23 Jul 2026 16:00:44 -0400 Subject: [PATCH 22/34] docs --- documentation/docs/98-reference/20-$app-service-worker.md | 7 +++++++ packages/kit/src/runtime/app/service-worker/index.js | 6 ++++++ 2 files changed, 13 insertions(+) create mode 100644 documentation/docs/98-reference/20-$app-service-worker.md diff --git a/documentation/docs/98-reference/20-$app-service-worker.md b/documentation/docs/98-reference/20-$app-service-worker.md new file mode 100644 index 000000000000..796d4adbe735 --- /dev/null +++ b/documentation/docs/98-reference/20-$app-service-worker.md @@ -0,0 +1,7 @@ +--- +title: $app/service-worker +--- + +This module can only be imported in service workers. + +> MODULE: $app/service-worker diff --git a/packages/kit/src/runtime/app/service-worker/index.js b/packages/kit/src/runtime/app/service-worker/index.js index 6bd3c581d6db..ea91fa743df9 100644 --- a/packages/kit/src/runtime/app/service-worker/index.js +++ b/packages/kit/src/runtime/app/service-worker/index.js @@ -4,6 +4,12 @@ import { DEV } from 'esm-env'; +/** + * The execution context of a service worker. This export exists to make it easier to + * use service workers with the correct types, provided it is used in a module governed + * by a `tsconfig.json` that extends [`$app/tsconfig/service-worker`](https://svelte.dev/docs/kit/$app-tsconfig-service-worker), + * + */ export const self = /** @type {ServiceWorkerGlobalScope} */ ( /** @type {unknown} */ (globalThis.self) ); From b07be7c83b9dbf2376088070a7198b2b396ce860 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 23 Jul 2026 16:17:00 -0400 Subject: [PATCH 23/34] fixes --- .../docs/98-reference/20-$app-tsconfig-service-worker.md | 2 +- documentation/docs/98-reference/20-$app-tsconfig.md | 4 ++-- packages/kit/src/runtime/app/service-worker/index.js | 4 ++-- packages/kit/types/index.d.ts | 6 ++++++ 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/documentation/docs/98-reference/20-$app-tsconfig-service-worker.md b/documentation/docs/98-reference/20-$app-tsconfig-service-worker.md index 97544f565f07..1c8fa148dab4 100644 --- a/documentation/docs/98-reference/20-$app-tsconfig-service-worker.md +++ b/documentation/docs/98-reference/20-$app-tsconfig-service-worker.md @@ -11,4 +11,4 @@ This module contains TypeScript configuration tailored for your service worker: } ``` -You can extend this configuration with your own `compilerOptions`, adhering to the same restrictions as [`$app/tsconfig]($app-tsconfig). +You can extend this configuration with your own `compilerOptions`, adhering to the same restrictions as [`$app/tsconfig`]($app-tsconfig). diff --git a/documentation/docs/98-reference/20-$app-tsconfig.md b/documentation/docs/98-reference/20-$app-tsconfig.md index f6534f4d29b3..c46044a8f679 100644 --- a/documentation/docs/98-reference/20-$app-tsconfig.md +++ b/documentation/docs/98-reference/20-$app-tsconfig.md @@ -15,8 +15,8 @@ This module contains TypeScript configuration tailored for your app. Your own co You can extend this configuration with your own `compilerOptions`. Overriding the following properties may cause things to break — SvelteKit will warn you if this happens: -- `paths` — this is derived from (deprecated) [`alias`](configuration#alias) config option, together with any [subpath imports](https://nodejs.org/api/packages.html#subpath-imports) specified in your `package.json`, to align behaviour between Vite and TypeScript. Ideally, configure subpath imports rather than using `paths` -- `types` — your needs to be able to 'see' generated module declarations for things like [environment variables](environment-variables), and as such this array must include `"$app/types"` +- `paths` — this is derived from the (deprecated) [`alias`](configuration#alias) config option, together with any [subpath imports](https://nodejs.org/api/packages.html#subpath-imports) specified in your `package.json`, to align behaviour between Vite and TypeScript. Ideally, configure subpath imports rather than using `paths` directly +- `types` — your app needs to be able to 'see' generated module declarations for things like [environment variables](environment-variables), and as such this array must include `"$app/types"` - `isolatedModules` — must be `true`, as Vite compiles modules one at a time - `verbatimModuleSyntax` — must be `true`, so that you can safely use type imports in `.svelte` files diff --git a/packages/kit/src/runtime/app/service-worker/index.js b/packages/kit/src/runtime/app/service-worker/index.js index ea91fa743df9..044c7c769c66 100644 --- a/packages/kit/src/runtime/app/service-worker/index.js +++ b/packages/kit/src/runtime/app/service-worker/index.js @@ -6,8 +6,8 @@ import { DEV } from 'esm-env'; /** * The execution context of a service worker. This export exists to make it easier to - * use service workers with the correct types, provided it is used in a module governed - * by a `tsconfig.json` that extends [`$app/tsconfig/service-worker`](https://svelte.dev/docs/kit/$app-tsconfig-service-worker), + * use service workers with the correct types, provided the importing module is governed + * by a `tsconfig.json` that extends [`$app/tsconfig/service-worker`](https://svelte.dev/docs/kit/$app-tsconfig-service-worker). * */ export const self = /** @type {ServiceWorkerGlobalScope} */ ( diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 7609207d0d6e..a7480ed7315f 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -3569,6 +3569,12 @@ declare module '$app/server' { } declare module '$app/service-worker' { + /** + * The execution context of a service worker. This export exists to make it easier to + * use service workers with the correct types, provided the importing module is governed + * by a `tsconfig.json` that extends [`$app/tsconfig/service-worker`](https://svelte.dev/docs/kit/$app-tsconfig-service-worker). + * + */ // @ts-ignore export const self: ServiceWorkerGlobalScope; From a0da6b2534e5f5b84ac4ad7cb33fd130cc2d9347 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 23 Jul 2026 16:24:02 -0400 Subject: [PATCH 24/34] fucking windows --- packages/kit/src/core/sync/write_tsconfig/utils.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/kit/src/core/sync/write_tsconfig/utils.js b/packages/kit/src/core/sync/write_tsconfig/utils.js index cea3d94638e5..1663338ba3c7 100644 --- a/packages/kit/src/core/sync/write_tsconfig/utils.js +++ b/packages/kit/src/core/sync/write_tsconfig/utils.js @@ -1,5 +1,6 @@ import path from 'node:path'; import { normalize_import_value, read_package_imports } from '../../../utils/imports.js'; +import { posixify } from '../../../utils/os.js'; /** * Without these, compilation will fail @@ -52,7 +53,7 @@ export function extends_parent(options, parent) { */ export function normalize_config(out, config, transform) { const dir = path.dirname(out); - const relative = (/** @type {string} */ file) => path.relative(dir, file); + const relative = (/** @type {string} */ file) => posixify(path.relative(dir, file)); const paths = config.compilerOptions?.paths && From 1b80adbe14abd53ab41f8ffed3928c4224feecf1 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 23 Jul 2026 16:30:23 -0400 Subject: [PATCH 25/34] deprecate typescript.config option --- packages/kit/src/core/config/options.js | 11 ++++++++--- packages/kit/src/exports/public.d.ts | 3 +++ packages/kit/test/apps/async/tsconfig.json | 1 + packages/kit/test/apps/async/vite.config.js | 5 ----- packages/kit/types/index.d.ts | 3 +++ 5 files changed, 15 insertions(+), 8 deletions(-) diff --git a/packages/kit/src/core/config/options.js b/packages/kit/src/core/config/options.js index 9a40a0734f6b..40ec63d6392d 100644 --- a/packages/kit/src/core/config/options.js +++ b/packages/kit/src/core/config/options.js @@ -286,9 +286,14 @@ export const validate_kit_options = object({ server: boolean(false) }), - typescript: object({ - config: fun((config) => config) - }), + typescript: deprecate( + object({ + config: fun((config) => config) + }), + (keypath) => { + return `The \`${keypath}\` option is deprecated, and will be removed in a future version. Add configuration to tsconfig.json directly`; + } + ), version: object({ name: string(Date.now().toString()), diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index c1f28309fa6f..e92f0aa0ccad 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -861,6 +861,9 @@ export interface KitConfig { */ server?: boolean; }; + /** + * @deprecated Add configuration to `tsconfig.json` directly + */ typescript?: { /** * A function that allows you to edit the generated `tsconfig.json`. You can mutate the config (recommended) or return a new one. diff --git a/packages/kit/test/apps/async/tsconfig.json b/packages/kit/test/apps/async/tsconfig.json index 1b6ff59e897e..a453d154033b 100644 --- a/packages/kit/test/apps/async/tsconfig.json +++ b/packages/kit/test/apps/async/tsconfig.json @@ -6,5 +6,6 @@ "resolveJsonModule": true, "rewriteRelativeImportExtensions": true }, + "include": ["src", "unit-test", "test", "playwright.config.js"], "extends": "$app/tsconfig" } diff --git a/packages/kit/test/apps/async/vite.config.js b/packages/kit/test/apps/async/vite.config.js index 63d214b9b235..d01ad4345392 100644 --- a/packages/kit/test/apps/async/vite.config.js +++ b/packages/kit/test/apps/async/vite.config.js @@ -18,11 +18,6 @@ export default defineConfig({ experimental: { remoteFunctions: true, forkPreloads: true - }, - typescript: { - config(config) { - config.include = ['**', '../unit-test/*.js', '../test/*.js', '../playwright.config.js']; - } } }) ], diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index a7480ed7315f..35bc4f2f2d8b 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -833,6 +833,9 @@ declare module '@sveltejs/kit' { */ server?: boolean; }; + /** + * @deprecated Add configuration to `tsconfig.json` directly + */ typescript?: { /** * A function that allows you to edit the generated `tsconfig.json`. You can mutate the config (recommended) or return a new one. From 566134309a90ccc0f5517f9b00815856e466d810 Mon Sep 17 00:00:00 2001 From: "vercel[bot]" <35613825+vercel[bot]@users.noreply.github.com> Date: Thu, 23 Jul 2026 20:33:35 +0000 Subject: [PATCH 26/34] Fix: Leftover WIP code in `$app/service-worker` registers an empty no-op `fetch` event listener as an import-time side effect for every consumer. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit fixes the issue reported at packages/kit/src/runtime/app/service-worker/index.js:15 ## Bug The new `$app/service-worker` runtime module contained leftover WIP/debug code at module top level: ```js self.addEventListener('fetch', (e) => { e.respondWith; }); ``` The module's stated purpose is *solely* to expose a correctly-typed `self` (`export const self: ServiceWorkerGlobalScope`). The generated public types (`packages/kit/types/index.d.ts`) only declare `self` — there is no fetch-handling surface. ### Why it's a problem * **Import-time side effect:** Any module doing `import { self } from '$app/service-worker'` would register a real `fetch` event listener on the service worker global scope. Registering a fetch handler is semantically meaningful (it influences PWA install/behavior criteria and can shadow the network), so silently attaching one for every consumer is surprising and unintended. * **Dead expression:** `e.respondWith;` merely references the property and does nothing — it never calls `respondWith(...)`. This is the classic unused-expression that likely triggered the "pacify eslint" workaround rather than being intentional behavior. ### Trigger Any consumer importing the module registers the empty listener. The `e.respondWith;` statement is unconditionally dead. ## Fix Removed the entire `self.addEventListener('fetch', ...)` block, leaving only the typed `self` export and the DEV-time guard, which matches the module's documented single responsibility and the generated public types. Co-authored-by: Vercel Co-authored-by: Rich-Harris --- packages/kit/src/runtime/app/service-worker/index.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/kit/src/runtime/app/service-worker/index.js b/packages/kit/src/runtime/app/service-worker/index.js index 044c7c769c66..88c1981a8a4d 100644 --- a/packages/kit/src/runtime/app/service-worker/index.js +++ b/packages/kit/src/runtime/app/service-worker/index.js @@ -14,10 +14,6 @@ export const self = /** @type {ServiceWorkerGlobalScope} */ ( /** @type {unknown} */ (globalThis.self) ); -self.addEventListener('fetch', (e) => { - e.respondWith; -}); - if (DEV) { if ( typeof ServiceWorkerGlobalScope === 'undefined' || From 822e3e3eca1a945bf229d7adb5d47559e03b711d Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 23 Jul 2026 16:35:35 -0400 Subject: [PATCH 27/34] changesets --- .changeset/fresh-pots-smoke.md | 5 +++++ .changeset/moody-bags-heal.md | 5 +++++ .changeset/plain-jobs-flash.md | 5 +++++ 3 files changed, 15 insertions(+) create mode 100644 .changeset/fresh-pots-smoke.md create mode 100644 .changeset/moody-bags-heal.md create mode 100644 .changeset/plain-jobs-flash.md diff --git a/.changeset/fresh-pots-smoke.md b/.changeset/fresh-pots-smoke.md new file mode 100644 index 000000000000..7ebeba8835bf --- /dev/null +++ b/.changeset/fresh-pots-smoke.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': major +--- + +breaking: write tsconfig to `node_modules/$app/tsconfig` diff --git a/.changeset/moody-bags-heal.md b/.changeset/moody-bags-heal.md new file mode 100644 index 000000000000..1b12a3b4509d --- /dev/null +++ b/.changeset/moody-bags-heal.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': minor +--- + +feat: `$app/service-worker` module diff --git a/.changeset/plain-jobs-flash.md b/.changeset/plain-jobs-flash.md new file mode 100644 index 000000000000..600633abee7a --- /dev/null +++ b/.changeset/plain-jobs-flash.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': minor +--- + +feat: better tsconfig validation From 8a4b4c83bef6043664139b3d6c994cc1556cfef6 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 23 Jul 2026 16:41:25 -0400 Subject: [PATCH 28/34] Update packages/kit/CHANGELOG.md --- packages/kit/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/CHANGELOG.md b/packages/kit/CHANGELOG.md index 1adac89324f2..9b26b5076a8b 100644 --- a/packages/kit/CHANGELOG.md +++ b/packages/kit/CHANGELOG.md @@ -2278,7 +2278,7 @@ ▲ [WARNING] Cannot find base config file "./.svelte-kit/tsconfig.json" [tsconfig.json] tsconfig.json:2:12: - 2 │ "extends": "$app/tsconfig", + 2 │ "extends": "./.svelte-kit/tsconfig.json", ╵ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ``` From 91e07856377643c9cae8123ae3c4c918239b5739 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 23 Jul 2026 17:03:46 -0400 Subject: [PATCH 29/34] fix --- packages/kit/test/apps/basics/tsconfig.json | 3 ++- packages/kit/test/apps/basics/vite.config.js | 6 ------ packages/kit/test/apps/options/tsconfig.json | 3 ++- packages/kit/test/apps/options/vite.custom.config.js | 5 ----- 4 files changed, 4 insertions(+), 13 deletions(-) diff --git a/packages/kit/test/apps/basics/tsconfig.json b/packages/kit/test/apps/basics/tsconfig.json index 947c9ce5579f..9c01fc02941d 100644 --- a/packages/kit/test/apps/basics/tsconfig.json +++ b/packages/kit/test/apps/basics/tsconfig.json @@ -5,5 +5,6 @@ "esModuleInterop": true, "resolveJsonModule": true }, - "extends": "$app/tsconfig" + "extends": "$app/tsconfig", + "include": ["src", "unit-test", "test"] } diff --git a/packages/kit/test/apps/basics/vite.config.js b/packages/kit/test/apps/basics/vite.config.js index e97c0be11345..2de19ee19ecf 100644 --- a/packages/kit/test/apps/basics/vite.config.js +++ b/packages/kit/test/apps/basics/vite.config.js @@ -84,12 +84,6 @@ export default defineConfig({ router: { resolution: /** @type {'client' | 'server'} */ (process.env.ROUTER_RESOLUTION) || 'client' - }, - - typescript: { - config: (config) => { - config.include = ['**', '../unit-test']; - } } }) ], diff --git a/packages/kit/test/apps/options/tsconfig.json b/packages/kit/test/apps/options/tsconfig.json index a14103c5fc1b..6e016144f94c 100644 --- a/packages/kit/test/apps/options/tsconfig.json +++ b/packages/kit/test/apps/options/tsconfig.json @@ -4,5 +4,6 @@ "checkJs": true, "noEmit": true }, - "extends": "$app/tsconfig" + "extends": "$app/tsconfig", + "include": ["source", "test", "vite.custom.config.js", "playwright.config.js"] } diff --git a/packages/kit/test/apps/options/vite.custom.config.js b/packages/kit/test/apps/options/vite.custom.config.js index a2fe99ae4608..8b0615458441 100644 --- a/packages/kit/test/apps/options/vite.custom.config.js +++ b/packages/kit/test/apps/options/vite.custom.config.js @@ -51,11 +51,6 @@ const config = { }, router: { resolution: /** @type {'client' | 'server'} */ (process.env.ROUTER_RESOLUTION) || 'client' - }, - typescript: { - config(config) { - config.include = ['**', '../vite.custom.config.js', '../playwright.config.js']; - } } }) ], From 9a0fb38f8b0f27e0b707473054708ceefda7c17f Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 23 Jul 2026 17:47:05 -0400 Subject: [PATCH 30/34] fix playground --- playgrounds/basic/package.json | 5 ++++- playgrounds/basic/src/service-worker/index.ts | 0 .../basic/src/service-worker/tsconfig.json | 3 +++ playgrounds/basic/tsconfig.json | 17 +++-------------- 4 files changed, 10 insertions(+), 15 deletions(-) create mode 100644 playgrounds/basic/src/service-worker/index.ts create mode 100644 playgrounds/basic/src/service-worker/tsconfig.json diff --git a/playgrounds/basic/package.json b/playgrounds/basic/package.json index 44d2dc2580d6..e39489596765 100644 --- a/playgrounds/basic/package.json +++ b/playgrounds/basic/package.json @@ -48,5 +48,8 @@ "!dist/**/*.spec.*" ], "svelte": "./dist/index.js", - "types": "./dist/index.d.ts" + "types": "./dist/index.d.ts", + "imports": { + "#lib/*": "./src/lib/*" + } } diff --git a/playgrounds/basic/src/service-worker/index.ts b/playgrounds/basic/src/service-worker/index.ts new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/playgrounds/basic/src/service-worker/tsconfig.json b/playgrounds/basic/src/service-worker/tsconfig.json new file mode 100644 index 000000000000..a9db6421edca --- /dev/null +++ b/playgrounds/basic/src/service-worker/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "$app/tsconfig/service-worker" +} diff --git a/playgrounds/basic/tsconfig.json b/playgrounds/basic/tsconfig.json index 9abacf4748b1..a17ce35075a3 100644 --- a/playgrounds/basic/tsconfig.json +++ b/playgrounds/basic/tsconfig.json @@ -1,16 +1,5 @@ { - "extends": "./.svelte-kit/tsconfig.json", - "compilerOptions": { - "allowJs": true, - "checkJs": true, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, - "skipLibCheck": true, - "sourceMap": true - } - // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias and https://svelte.dev/docs/kit/configuration#files - // - // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes - // from the referenced tsconfig.json - TypeScript does not merge them in + "extends": "$app/tsconfig", + "include": ["src"], + "exclude": ["src/service-worker"] } From 831b5344b564aec399f88bc1671dd4878f0d9b2f Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 23 Jul 2026 21:33:59 -0400 Subject: [PATCH 31/34] add comments/descriptions --- .../kit/src/core/sync/write_tsconfig/index.js | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/packages/kit/src/core/sync/write_tsconfig/index.js b/packages/kit/src/core/sync/write_tsconfig/index.js index c479e551e424..d88f1edbe747 100644 --- a/packages/kit/src/core/sync/write_tsconfig/index.js +++ b/packages/kit/src/core/sync/write_tsconfig/index.js @@ -1,3 +1,4 @@ +/** @import { ValidatedKitConfig } from 'types' */ import process from 'node:process'; import fs from 'node:fs'; import path from 'node:path'; @@ -67,16 +68,17 @@ export function write_tsconfig(kit, root) { } /** - * - * @param {string} root - * @param {string} dir - * @param {string} parent - * @param {any} config - * @param {any} example - * @param {(input: any) => any} [transform] TODO get rid of this + * Write a generated `tsconfig.json` inside `node_modules`, for the + * user config to extend + * @param {string} root The project root + * @param {string} dir The directory to resolve a user config from + * @param {string} id The id of the generated config + * @param {any} config The contents of the generated tsconfig, with paths relative to `root` + * @param {any} example What to print if the user config does _not_ extend the generated config + * @param {ValidatedKitConfig['typescript']['config']} [transform] TODO get rid of this */ -function write_parent_tsconfig(root, dir, parent, config, example, transform) { - const out_file = path.join(root, `node_modules/${parent}/tsconfig.json`); +function write_parent_tsconfig(root, dir, id, config, example, transform) { + const out_file = path.join(root, `node_modules/${id}/tsconfig.json`); let normalized = normalize_config(out_file, config); normalized = transform?.(normalized) ?? normalized; @@ -88,7 +90,7 @@ function write_parent_tsconfig(root, dir, parent, config, example, transform) { if (user_config && modified_since_last_check(user_config.file)) { // now that we've written the parent config, we can resolve the // user config and validate that nothing important was overwritten - if (!extends_parent(user_config.options, parent)) { + if (!extends_parent(user_config.options, id)) { console.warn( styleText( ['bold', 'yellow'], From b9a91ee35cf16e5a944d26d6da9c329cf4ea5d55 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 24 Jul 2026 09:14:08 -0400 Subject: [PATCH 32/34] attempt to explain regexes --- packages/kit/src/core/sync/write_tsconfig/index.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/kit/src/core/sync/write_tsconfig/index.js b/packages/kit/src/core/sync/write_tsconfig/index.js index d88f1edbe747..4d05d61bdee0 100644 --- a/packages/kit/src/core/sync/write_tsconfig/index.js +++ b/packages/kit/src/core/sync/write_tsconfig/index.js @@ -196,10 +196,11 @@ function load_tsconfig(file) { return options.config; } -// -const alias_regex = /^(.+?)(\/\*)?$/; -// -const value_regex = /^(.*?)((\/\*)|(\.\w+))?$/; +/** Matches anything (except the empty string/string with newline), and separates any trailing `/*` */ +const alias_key = /^(.+?)(\/\*)?$/; + +/** Matches anything (except the empty string/string with newline), and separates any trailing `/*` or file extension */ +const alias_value = /^(.+?)((\/\*)|(\.\w+))?$/; /** * Generates tsconfig path aliases from kit's aliases and the package.json `imports` field. @@ -219,10 +220,10 @@ function get_paths(config, root) { const paths = {}; for (const [key, value] of Object.entries(alias)) { - const key_match = alias_regex.exec(key); + const key_match = alias_key.exec(key); if (!key_match) throw new Error(`Invalid alias key: ${key}`); - const value_match = value_regex.exec(value); + const value_match = alias_value.exec(value); if (!value_match) throw new Error(`Invalid alias value: ${value}`); const resolved = path.resolve(root, remove_trailing_slashstar(value)); From 99fdc90823b88ac711d257793afa8ecdde635612 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 24 Jul 2026 09:19:17 -0400 Subject: [PATCH 33/34] parent -> id --- .../kit/src/core/sync/write_tsconfig/index.js | 4 ++-- .../kit/src/core/sync/write_tsconfig/utils.js | 6 +++--- .../src/core/sync/write_tsconfig/utils.spec.js | 18 +++++++++--------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/kit/src/core/sync/write_tsconfig/index.js b/packages/kit/src/core/sync/write_tsconfig/index.js index 4d05d61bdee0..28b6a19dbb91 100644 --- a/packages/kit/src/core/sync/write_tsconfig/index.js +++ b/packages/kit/src/core/sync/write_tsconfig/index.js @@ -7,7 +7,7 @@ import { styleText } from 'node:util'; import { write_if_changed } from '../utils.js'; import { ESSENTIAL_OPTIONS, - extends_parent, + extends_id, get_subpath_imports, normalize_config, RECOMMENDED_OPTIONS, @@ -90,7 +90,7 @@ function write_parent_tsconfig(root, dir, id, config, example, transform) { if (user_config && modified_since_last_check(user_config.file)) { // now that we've written the parent config, we can resolve the // user config and validate that nothing important was overwritten - if (!extends_parent(user_config.options, id)) { + if (!extends_id(user_config.options, id)) { console.warn( styleText( ['bold', 'yellow'], diff --git a/packages/kit/src/core/sync/write_tsconfig/utils.js b/packages/kit/src/core/sync/write_tsconfig/utils.js index 1663338ba3c7..7298737e9ef9 100644 --- a/packages/kit/src/core/sync/write_tsconfig/utils.js +++ b/packages/kit/src/core/sync/write_tsconfig/utils.js @@ -38,11 +38,11 @@ export const RECOMMENDED_OPTIONS = { /** * @param {any} options - * @param {string} parent + * @param {string} id */ -export function extends_parent(options, parent) { +export function extends_id(options, id) { const o = options.extends; - return Array.isArray(o) ? o.includes(parent) : o === parent; + return Array.isArray(o) ? o.includes(id) : o === id; } /** diff --git a/packages/kit/src/core/sync/write_tsconfig/utils.spec.js b/packages/kit/src/core/sync/write_tsconfig/utils.spec.js index 15ece400a1ec..5a3cf7af73b8 100644 --- a/packages/kit/src/core/sync/write_tsconfig/utils.spec.js +++ b/packages/kit/src/core/sync/write_tsconfig/utils.spec.js @@ -1,6 +1,6 @@ import { assert, describe, test } from 'vitest'; import { - extends_parent, + extends_id, get_subpath_imports, normalize_config, validate_resolved_config @@ -74,19 +74,19 @@ describe('get_subpath_imports', () => { }); }); -describe('extends_parent', () => { - const parent = 'PARENT'; +describe('extends_id', () => { + const id = 'POTATO'; - test('validates that a config extends a parent (string)', () => { - assert.equal(extends_parent({ extends: parent }, parent), true); + test('validates that a config extends an id (string)', () => { + assert.equal(extends_id({ extends: id }, id), true); }); - test('validates that a config extends a parent (array)', () => { - assert.equal(extends_parent({ extends: [parent] }, parent), true); + test('validates that a config extends an id (array)', () => { + assert.equal(extends_id({ extends: [id] }, id), true); }); - test('validates that a config does not extend a parent', () => { - assert.equal(extends_parent({}, parent), false); + test('validates that a config does not extend an id', () => { + assert.equal(extends_id({}, id), false); }); }); From e74bfb37eefbd5ae9dce4738a065ac1ba1836b07 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 24 Jul 2026 09:19:52 -0400 Subject: [PATCH 34/34] inline docs --- packages/kit/src/core/sync/write_tsconfig/utils.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/kit/src/core/sync/write_tsconfig/utils.js b/packages/kit/src/core/sync/write_tsconfig/utils.js index 7298737e9ef9..472b402b499f 100644 --- a/packages/kit/src/core/sync/write_tsconfig/utils.js +++ b/packages/kit/src/core/sync/write_tsconfig/utils.js @@ -37,6 +37,7 @@ export const RECOMMENDED_OPTIONS = { }; /** + * Validates that a tsconfig contains `"extends": ""` * @param {any} options * @param {string} id */