diff --git a/.changeset/good-beds-say.md b/.changeset/good-beds-say.md new file mode 100644 index 000000000000..83d703f876ae --- /dev/null +++ b/.changeset/good-beds-say.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +chore: generate `$app/types` in a more Typescript-friendly way diff --git a/packages/kit/src/core/sync/sync.js b/packages/kit/src/core/sync/sync.js index a96e40bc0928..c77bbca30f27 100644 --- a/packages/kit/src/core/sync/sync.js +++ b/packages/kit/src/core/sync/sync.js @@ -9,14 +9,13 @@ import { write_non_ambient } from './write_non_ambient.js'; import { write_server } from './write_server.js'; /** - * Initialize SvelteKit's generated files. + * Initialize SvelteKit's generated files that only depend on the config and mode. * @param {import('types').ValidatedConfig} config * @param {string} mode */ export function init(config, mode) { write_tsconfig(config.kit); write_ambient(config.kit, mode); - write_non_ambient(config.kit); } /** @@ -32,6 +31,7 @@ export function create(config) { write_server(config, output); write_root(manifest_data, output); write_all_types(config, manifest_data); + write_non_ambient(config.kit, manifest_data); return { manifest_data }; } @@ -67,6 +67,7 @@ export function all_types(config, mode) { init(config, mode); const manifest_data = create_manifest_data({ config }); write_all_types(config, manifest_data); + write_non_ambient(config.kit, manifest_data); } /** diff --git a/packages/kit/src/core/sync/write_non_ambient.js b/packages/kit/src/core/sync/write_non_ambient.js index a191495ac6f2..c2b4a57025c8 100644 --- a/packages/kit/src/core/sync/write_non_ambient.js +++ b/packages/kit/src/core/sync/write_non_ambient.js @@ -1,6 +1,17 @@ import path from 'node:path'; import { GENERATED_COMMENT } from '../../constants.js'; import { write_if_changed } from './utils.js'; +import { s } from '../../utils/misc.js'; +import { get_route_segments } from '../../utils/routing.js'; + +const replace_optional_params = (/** @type {string} */ id) => + id.replace(/\/\[\[[^\]]+\]\]/g, '${string}'); +const replace_required_params = (/** @type {string} */ id) => + id.replace(/\/\[[^\]]+\]/g, '/${string}'); +/** Convert route ID to pathname by removing layout groups */ +const remove_group_segments = (/** @type {string} */ id) => { + return '/' + get_route_segments(id).join('/'); +}; // `declare module "svelte/elements"` needs to happen in a non-ambient module, and dts-buddy generates one big ambient module, // so we can't add it there - therefore generate the typings ourselves here. @@ -33,10 +44,75 @@ declare module "svelte/elements" { export {}; `; +/** + * Generate app types interface extension + * @param {import('types').ManifestData} manifest_data + */ +function generate_app_types(manifest_data) { + /** @type {Set} */ + const pathnames = new Set(); + + /** @type {string[]} */ + const dynamic_routes = []; + + /** @type {string[]} */ + const layouts = []; + + for (const route of manifest_data.routes) { + if (route.params.length > 0) { + const params = route.params.map((p) => `${p.name}${p.optional ? '?:' : ':'} string`); + const route_type = `${s(route.id)}: { ${params.join('; ')} }`; + + dynamic_routes.push(route_type); + + const pathname = remove_group_segments(route.id); + pathnames.add(`\`${replace_required_params(replace_optional_params(pathname))}\` & {}`); + } else { + const pathname = remove_group_segments(route.id); + pathnames.add(s(pathname)); + } + + /** @type {Map} */ + const child_params = new Map(route.params.map((p) => [p.name, p.optional])); + + for (const child of manifest_data.routes.filter((r) => r.id.startsWith(route.id))) { + for (const p of child.params) { + if (!child_params.has(p.name)) { + child_params.set(p.name, true); // always optional + } + } + } + + const layout_params = Array.from(child_params) + .map(([name, optional]) => `${name}${optional ? '?:' : ':'} string`) + .join('; '); + + const layout_type = `${s(route.id)}: ${layout_params.length > 0 ? `{ ${layout_params} }` : 'Record'}`; + layouts.push(layout_type); + } + + return [ + 'declare module "$app/types" {', + '\texport interface AppTypes {', + `\t\tRouteId(): ${manifest_data.routes.map((r) => s(r.id)).join(' | ')};`, + `\t\tRouteParams(): {\n\t\t\t${dynamic_routes.join(';\n\t\t\t')}\n\t\t};`, + `\t\tLayoutParams(): {\n\t\t\t${layouts.join(';\n\t\t\t')}\n\t\t};`, + `\t\tPathname(): ${Array.from(pathnames).join(' | ')};`, + '\t\tResolvedPathname(): `${"" | `/${string}`}${ReturnType}`;', + `\t\tAsset(): ${manifest_data.assets.map((asset) => s('/' + asset.file)).join(' | ') || 'never'};`, + '\t}', + '}' + ].join('\n'); +} + /** * Writes non-ambient declarations to the output directory * @param {import('types').ValidatedKitConfig} config + * @param {import('types').ManifestData} manifest_data */ -export function write_non_ambient(config) { - write_if_changed(path.join(config.outDir, 'non-ambient.d.ts'), template); +export function write_non_ambient(config, manifest_data) { + const app_types = generate_app_types(manifest_data); + const content = [template, app_types].join('\n\n'); + + write_if_changed(path.join(config.outDir, 'non-ambient.d.ts'), content); } diff --git a/packages/kit/src/core/sync/write_types/index.js b/packages/kit/src/core/sync/write_types/index.js index d51c60a94442..a7f48109548d 100644 --- a/packages/kit/src/core/sync/write_types/index.js +++ b/packages/kit/src/core/sync/write_types/index.js @@ -5,19 +5,8 @@ import MagicString from 'magic-string'; import { posixify, rimraf, walk } from '../../../utils/filesystem.js'; import { compact } from '../../../utils/array.js'; import { ts } from '../ts.js'; -import { s } from '../../../utils/misc.js'; -import { get_route_segments } from '../../../utils/routing.js'; - const remove_relative_parent_traversals = (/** @type {string} */ path) => path.replace(/\.\.\//g, ''); -const replace_optional_params = (/** @type {string} */ id) => - id.replace(/\/\[\[[^\]]+\]\]/g, '${string}'); -const replace_required_params = (/** @type {string} */ id) => - id.replace(/\/\[[^\]]+\]/g, '/${string}'); -/** Convert route ID to pathname by removing layout groups */ -const remove_group_segments = (/** @type {string} */ id) => { - return '/' + get_route_segments(id).join('/'); -}; const is_whitespace = (/** @type {string} */ char) => /\s/.test(char); /** @@ -65,67 +54,6 @@ export function write_all_types(config, manifest_data) { } } - /** @type {Set} */ - const pathnames = new Set(); - - /** @type {string[]} */ - const dynamic_routes = []; - - /** @type {string[]} */ - const layouts = []; - - for (const route of manifest_data.routes) { - if (route.params.length > 0) { - const params = route.params.map((p) => `${p.name}${p.optional ? '?:' : ':'} string`); - const route_type = `${s(route.id)}: { ${params.join('; ')} }`; - - dynamic_routes.push(route_type); - - const pathname = remove_group_segments(route.id); - pathnames.add(`\`${replace_required_params(replace_optional_params(pathname))}\` & {}`); - } else { - const pathname = remove_group_segments(route.id); - pathnames.add(s(pathname)); - } - - /** @type {Map} */ - const child_params = new Map(route.params.map((p) => [p.name, p.optional])); - - for (const child of manifest_data.routes.filter((r) => r.id.startsWith(route.id))) { - for (const p of child.params) { - if (!child_params.has(p.name)) { - child_params.set(p.name, true); // always optional - } - } - } - - const layout_params = Array.from(child_params) - .map(([name, optional]) => `${name}${optional ? '?:' : ':'} string`) - .join('; '); - - const layout_type = `${s(route.id)}: ${layout_params.length > 0 ? `{ ${layout_params} }` : 'undefined'}`; - layouts.push(layout_type); - } - - try { - fs.mkdirSync(types_dir, { recursive: true }); - } catch {} - - fs.writeFileSync( - `${types_dir}/index.d.ts`, - [ - `type DynamicRoutes = {\n\t${dynamic_routes.join(';\n\t')}\n};`, - `type Layouts = {\n\t${layouts.join(';\n\t')}\n};`, - // we enumerate these rather than doing `keyof Routes` so that the list is visible on hover - `export type RouteId = ${manifest_data.routes.map((r) => s(r.id)).join(' | ')};`, - 'export type RouteParams = T extends keyof DynamicRoutes ? DynamicRoutes[T] : Record;', - 'export type LayoutParams = Layouts[T] | Record;', - `export type Pathname = ${Array.from(pathnames).join(' | ')};`, - 'export type ResolvedPathname = `${"" | `/${string}`}${Pathname}`;', - `export type Asset = ${manifest_data.assets.map((asset) => s('/' + asset.file)).join(' | ') || 'never'};` - ].join('\n\n') - ); - // Read/write meta data on each invocation, not once per node process, // it could be invoked by another process in the meantime. const meta_data_file = `${types_dir}/route_meta_data.json`; 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 d6bfc36f7578..6ea35f7534d9 100644 --- a/packages/kit/src/core/sync/write_types/index.spec.js +++ b/packages/kit/src/core/sync/write_types/index.spec.js @@ -6,6 +6,7 @@ import { assert, 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'; const cwd = fileURLToPath(new URL('./test', import.meta.url)); @@ -28,9 +29,10 @@ function run_test(dir) { }); write_all_types(initial, manifest); + write_non_ambient(initial.kit, manifest); } -test('Creates correct $types', { timeout: 10000 }, () => { +test('Creates correct $types', { timeout: 60000 }, () => { // 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. @@ -40,13 +42,12 @@ test('Creates correct $types', { timeout: 10000 }, () => { for (const dir of directories) { run_test(dir); - } - - try { - execSync('pnpm testtypes', { cwd }); - } catch (e) { - console.error(/** @type {any} */ (e).stdout.toString()); - throw new Error('Type tests failed'); + try { + execSync('pnpm testtypes', { cwd: path.join(cwd, dir) }); + } catch (e) { + console.error(/** @type {any} */ (e).stdout.toString()); + throw new Error('Type tests failed'); + } } }); diff --git a/packages/kit/src/core/sync/write_types/test/package.json b/packages/kit/src/core/sync/write_types/test/actions/package.json similarity index 100% rename from packages/kit/src/core/sync/write_types/test/package.json rename to packages/kit/src/core/sync/write_types/test/actions/package.json diff --git a/packages/kit/src/core/sync/write_types/test/actions/tsconfig.json b/packages/kit/src/core/sync/write_types/test/actions/tsconfig.json new file mode 100644 index 000000000000..8ac11dd62008 --- /dev/null +++ b/packages/kit/src/core/sync/write_types/test/actions/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "noEmit": true, + "strict": true, + "target": "es2022", + "module": "es2022", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "baseUrl": ".", + "paths": { + "@sveltejs/kit": ["../../../../../exports/public"], + "types": ["../../../../../types/internal"], + "$app/types": ["../../../../../types/ambient.d.ts"] + } + }, + "include": ["./**/*.js", "./**/*.ts", ".svelte-kit/non-ambient.d.ts"], + "exclude": ["..svelte-kit/**"] +} diff --git a/packages/kit/src/core/sync/write_types/test/app-types/+page.ts b/packages/kit/src/core/sync/write_types/test/app-types/+page.ts index 09dea2ca9d96..9d1f7ea39011 100644 --- a/packages/kit/src/core/sync/write_types/test/app-types/+page.ts +++ b/packages/kit/src/core/sync/write_types/test/app-types/+page.ts @@ -1,4 +1,4 @@ -import type { RouteId, RouteParams, Pathname } from './.svelte-kit/types/index.d.ts'; +import type { RouteId, RouteParams, Pathname } from '$app/types'; declare let id: RouteId; diff --git a/packages/kit/src/core/sync/write_types/test/app-types/package.json b/packages/kit/src/core/sync/write_types/test/app-types/package.json new file mode 100644 index 000000000000..9ccb30aba068 --- /dev/null +++ b/packages/kit/src/core/sync/write_types/test/app-types/package.json @@ -0,0 +1,7 @@ +{ + "private": true, + "type": "module", + "scripts": { + "testtypes": "tsc" + } +} 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 new file mode 100644 index 000000000000..8ac11dd62008 --- /dev/null +++ b/packages/kit/src/core/sync/write_types/test/app-types/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "noEmit": true, + "strict": true, + "target": "es2022", + "module": "es2022", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "baseUrl": ".", + "paths": { + "@sveltejs/kit": ["../../../../../exports/public"], + "types": ["../../../../../types/internal"], + "$app/types": ["../../../../../types/ambient.d.ts"] + } + }, + "include": ["./**/*.js", "./**/*.ts", ".svelte-kit/non-ambient.d.ts"], + "exclude": ["..svelte-kit/**"] +} diff --git a/packages/kit/src/core/sync/write_types/test/layout-advanced/package.json b/packages/kit/src/core/sync/write_types/test/layout-advanced/package.json new file mode 100644 index 000000000000..9ccb30aba068 --- /dev/null +++ b/packages/kit/src/core/sync/write_types/test/layout-advanced/package.json @@ -0,0 +1,7 @@ +{ + "private": true, + "type": "module", + "scripts": { + "testtypes": "tsc" + } +} diff --git a/packages/kit/src/core/sync/write_types/test/layout-advanced/tsconfig.json b/packages/kit/src/core/sync/write_types/test/layout-advanced/tsconfig.json new file mode 100644 index 000000000000..8ac11dd62008 --- /dev/null +++ b/packages/kit/src/core/sync/write_types/test/layout-advanced/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "noEmit": true, + "strict": true, + "target": "es2022", + "module": "es2022", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "baseUrl": ".", + "paths": { + "@sveltejs/kit": ["../../../../../exports/public"], + "types": ["../../../../../types/internal"], + "$app/types": ["../../../../../types/ambient.d.ts"] + } + }, + "include": ["./**/*.js", "./**/*.ts", ".svelte-kit/non-ambient.d.ts"], + "exclude": ["..svelte-kit/**"] +} diff --git a/packages/kit/src/core/sync/write_types/test/layout/package.json b/packages/kit/src/core/sync/write_types/test/layout/package.json new file mode 100644 index 000000000000..9ccb30aba068 --- /dev/null +++ b/packages/kit/src/core/sync/write_types/test/layout/package.json @@ -0,0 +1,7 @@ +{ + "private": true, + "type": "module", + "scripts": { + "testtypes": "tsc" + } +} diff --git a/packages/kit/src/core/sync/write_types/test/layout/tsconfig.json b/packages/kit/src/core/sync/write_types/test/layout/tsconfig.json new file mode 100644 index 000000000000..8ac11dd62008 --- /dev/null +++ b/packages/kit/src/core/sync/write_types/test/layout/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "noEmit": true, + "strict": true, + "target": "es2022", + "module": "es2022", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "baseUrl": ".", + "paths": { + "@sveltejs/kit": ["../../../../../exports/public"], + "types": ["../../../../../types/internal"], + "$app/types": ["../../../../../types/ambient.d.ts"] + } + }, + "include": ["./**/*.js", "./**/*.ts", ".svelte-kit/non-ambient.d.ts"], + "exclude": ["..svelte-kit/**"] +} diff --git a/packages/kit/src/core/sync/write_types/test/param-type-inference/package.json b/packages/kit/src/core/sync/write_types/test/param-type-inference/package.json new file mode 100644 index 000000000000..9ccb30aba068 --- /dev/null +++ b/packages/kit/src/core/sync/write_types/test/param-type-inference/package.json @@ -0,0 +1,7 @@ +{ + "private": true, + "type": "module", + "scripts": { + "testtypes": "tsc" + } +} 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 new file mode 100644 index 000000000000..8ac11dd62008 --- /dev/null +++ b/packages/kit/src/core/sync/write_types/test/param-type-inference/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "noEmit": true, + "strict": true, + "target": "es2022", + "module": "es2022", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "baseUrl": ".", + "paths": { + "@sveltejs/kit": ["../../../../../exports/public"], + "types": ["../../../../../types/internal"], + "$app/types": ["../../../../../types/ambient.d.ts"] + } + }, + "include": ["./**/*.js", "./**/*.ts", ".svelte-kit/non-ambient.d.ts"], + "exclude": ["..svelte-kit/**"] +} diff --git a/packages/kit/src/core/sync/write_types/test/simple-page-server-and-shared/package.json b/packages/kit/src/core/sync/write_types/test/simple-page-server-and-shared/package.json new file mode 100644 index 000000000000..9ccb30aba068 --- /dev/null +++ b/packages/kit/src/core/sync/write_types/test/simple-page-server-and-shared/package.json @@ -0,0 +1,7 @@ +{ + "private": true, + "type": "module", + "scripts": { + "testtypes": "tsc" + } +} diff --git a/packages/kit/src/core/sync/write_types/test/simple-page-server-and-shared/tsconfig.json b/packages/kit/src/core/sync/write_types/test/simple-page-server-and-shared/tsconfig.json new file mode 100644 index 000000000000..8ac11dd62008 --- /dev/null +++ b/packages/kit/src/core/sync/write_types/test/simple-page-server-and-shared/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "noEmit": true, + "strict": true, + "target": "es2022", + "module": "es2022", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "baseUrl": ".", + "paths": { + "@sveltejs/kit": ["../../../../../exports/public"], + "types": ["../../../../../types/internal"], + "$app/types": ["../../../../../types/ambient.d.ts"] + } + }, + "include": ["./**/*.js", "./**/*.ts", ".svelte-kit/non-ambient.d.ts"], + "exclude": ["..svelte-kit/**"] +} diff --git a/packages/kit/src/core/sync/write_types/test/simple-page-server-only/package.json b/packages/kit/src/core/sync/write_types/test/simple-page-server-only/package.json new file mode 100644 index 000000000000..9ccb30aba068 --- /dev/null +++ b/packages/kit/src/core/sync/write_types/test/simple-page-server-only/package.json @@ -0,0 +1,7 @@ +{ + "private": true, + "type": "module", + "scripts": { + "testtypes": "tsc" + } +} diff --git a/packages/kit/src/core/sync/write_types/test/simple-page-server-only/tsconfig.json b/packages/kit/src/core/sync/write_types/test/simple-page-server-only/tsconfig.json new file mode 100644 index 000000000000..8ac11dd62008 --- /dev/null +++ b/packages/kit/src/core/sync/write_types/test/simple-page-server-only/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "noEmit": true, + "strict": true, + "target": "es2022", + "module": "es2022", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "baseUrl": ".", + "paths": { + "@sveltejs/kit": ["../../../../../exports/public"], + "types": ["../../../../../types/internal"], + "$app/types": ["../../../../../types/ambient.d.ts"] + } + }, + "include": ["./**/*.js", "./**/*.ts", ".svelte-kit/non-ambient.d.ts"], + "exclude": ["..svelte-kit/**"] +} diff --git a/packages/kit/src/core/sync/write_types/test/simple-page-shared-only/package.json b/packages/kit/src/core/sync/write_types/test/simple-page-shared-only/package.json new file mode 100644 index 000000000000..9ccb30aba068 --- /dev/null +++ b/packages/kit/src/core/sync/write_types/test/simple-page-shared-only/package.json @@ -0,0 +1,7 @@ +{ + "private": true, + "type": "module", + "scripts": { + "testtypes": "tsc" + } +} diff --git a/packages/kit/src/core/sync/write_types/test/simple-page-shared-only/tsconfig.json b/packages/kit/src/core/sync/write_types/test/simple-page-shared-only/tsconfig.json new file mode 100644 index 000000000000..8ac11dd62008 --- /dev/null +++ b/packages/kit/src/core/sync/write_types/test/simple-page-shared-only/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "noEmit": true, + "strict": true, + "target": "es2022", + "module": "es2022", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "baseUrl": ".", + "paths": { + "@sveltejs/kit": ["../../../../../exports/public"], + "types": ["../../../../../types/internal"], + "$app/types": ["../../../../../types/ambient.d.ts"] + } + }, + "include": ["./**/*.js", "./**/*.ts", ".svelte-kit/non-ambient.d.ts"], + "exclude": ["..svelte-kit/**"] +} diff --git a/packages/kit/src/core/sync/write_types/test/slugs-layout-not-all-pages-have-load/package.json b/packages/kit/src/core/sync/write_types/test/slugs-layout-not-all-pages-have-load/package.json new file mode 100644 index 000000000000..9ccb30aba068 --- /dev/null +++ b/packages/kit/src/core/sync/write_types/test/slugs-layout-not-all-pages-have-load/package.json @@ -0,0 +1,7 @@ +{ + "private": true, + "type": "module", + "scripts": { + "testtypes": "tsc" + } +} diff --git a/packages/kit/src/core/sync/write_types/test/slugs-layout-not-all-pages-have-load/tsconfig.json b/packages/kit/src/core/sync/write_types/test/slugs-layout-not-all-pages-have-load/tsconfig.json new file mode 100644 index 000000000000..8ac11dd62008 --- /dev/null +++ b/packages/kit/src/core/sync/write_types/test/slugs-layout-not-all-pages-have-load/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "noEmit": true, + "strict": true, + "target": "es2022", + "module": "es2022", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "baseUrl": ".", + "paths": { + "@sveltejs/kit": ["../../../../../exports/public"], + "types": ["../../../../../types/internal"], + "$app/types": ["../../../../../types/ambient.d.ts"] + } + }, + "include": ["./**/*.js", "./**/*.ts", ".svelte-kit/non-ambient.d.ts"], + "exclude": ["..svelte-kit/**"] +} diff --git a/packages/kit/src/core/sync/write_types/test/slugs/package.json b/packages/kit/src/core/sync/write_types/test/slugs/package.json new file mode 100644 index 000000000000..9ccb30aba068 --- /dev/null +++ b/packages/kit/src/core/sync/write_types/test/slugs/package.json @@ -0,0 +1,7 @@ +{ + "private": true, + "type": "module", + "scripts": { + "testtypes": "tsc" + } +} diff --git a/packages/kit/src/core/sync/write_types/test/slugs/tsconfig.json b/packages/kit/src/core/sync/write_types/test/slugs/tsconfig.json new file mode 100644 index 000000000000..8ac11dd62008 --- /dev/null +++ b/packages/kit/src/core/sync/write_types/test/slugs/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "noEmit": true, + "strict": true, + "target": "es2022", + "module": "es2022", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "baseUrl": ".", + "paths": { + "@sveltejs/kit": ["../../../../../exports/public"], + "types": ["../../../../../types/internal"], + "$app/types": ["../../../../../types/ambient.d.ts"] + } + }, + "include": ["./**/*.js", "./**/*.ts", ".svelte-kit/non-ambient.d.ts"], + "exclude": ["..svelte-kit/**"] +} diff --git a/packages/kit/src/core/sync/write_types/test/tsconfig.json b/packages/kit/src/core/sync/write_types/test/tsconfig.json deleted file mode 100644 index 9c43f3c10e41..000000000000 --- a/packages/kit/src/core/sync/write_types/test/tsconfig.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "compilerOptions": { - "allowJs": true, - "checkJs": true, - "noEmit": true, - "strict": true, - "target": "es2022", - "module": "es2022", - "moduleResolution": "bundler", - "allowSyntheticDefaultImports": true, - "baseUrl": ".", - "paths": { - "@sveltejs/kit": ["../../../../exports/public"], - "types": ["../../../../types/internal"] - } - }, - "include": ["./**/*.js", "./**/*.ts"], - "exclude": ["./**/.svelte-kit/**"] -} diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index a096eeae5ccb..980ac090996b 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -23,7 +23,6 @@ import { RouteId as AppRouteId, LayoutParams as AppLayoutParams, ResolvedPathname - // @ts-ignore } from '$app/types'; export { PrerenderOption } from '../types/private.js'; diff --git a/packages/kit/src/runtime/app/paths/index.js b/packages/kit/src/runtime/app/paths/index.js index d9f29bbdc395..4ca8b262bc67 100644 --- a/packages/kit/src/runtime/app/paths/index.js +++ b/packages/kit/src/runtime/app/paths/index.js @@ -8,7 +8,9 @@ export function asset(file) { /** @type {import('./types.d.ts').resolve} */ export function resolve(id, params) { - return base + resolve_route(id, params); + // The type error is correct here, and if someone doesn't pass params when they should there's a runtime error, + // but we don't want to adjust the internal resolve_route function to accept `undefined`, hence the type cast. + return base + resolve_route(id, /** @type {Record} */ (params)); } export { base, assets, resolve as resolveRoute }; diff --git a/packages/kit/src/runtime/app/paths/types.d.ts b/packages/kit/src/runtime/app/paths/types.d.ts index 6f8a4a481fe0..8f0e2f4ebaed 100644 --- a/packages/kit/src/runtime/app/paths/types.d.ts +++ b/packages/kit/src/runtime/app/paths/types.d.ts @@ -1,4 +1,3 @@ -// @ts-ignore import { Asset, RouteId, RouteParams, Pathname, ResolvedPathname } from '$app/types'; /** diff --git a/packages/kit/src/types/ambient.d.ts b/packages/kit/src/types/ambient.d.ts index 5ab0c5b80c20..3b3c43c9d61f 100644 --- a/packages/kit/src/types/ambient.d.ts +++ b/packages/kit/src/types/ambient.d.ts @@ -79,3 +79,57 @@ declare module '$service-worker' { */ export const version: string; } + +/** + * This module contains generated types for the routes in your app. + */ +declare module '$app/types' { + /** + * Interface for all generated app types. This gets extended via declaration merging. DO NOT USE THIS INTERFACE DIRECTLY. + */ + export interface AppTypes { + // These are all functions so that we can leverage function overloads to get the correct type. + // Using the return types directly would error with a "not the same type" error. + // https://www.typescriptlang.org/docs/handbook/declaration-merging.html#merging-interfaces + RouteId(): string; + RouteParams(): Record>; + LayoutParams(): Record>; + Pathname(): string; + ResolvedPathname(): string; + Asset(): string; + } + + /** + * A union of all the route IDs in your app. Used for `page.route.id` and `event.route.id`. + */ + export type RouteId = ReturnType; + + /** + * A utility for getting the parameters associated with a given route. + */ + export type RouteParams = T extends keyof ReturnType + ? ReturnType[T] + : Record; + + /** + * A utility for getting the parameters associated with a given layout, which is similar to `RouteParams` but also includes optional parameters for any child route. + */ + export type LayoutParams = T extends keyof ReturnType + ? ReturnType[T] + : Record; + + /** + * A union of all valid pathnames in your app. + */ + export type Pathname = ReturnType; + + /** + * `Pathname`, but possibly prefixed with a base path. Used for `page.url.pathname`. + */ + export type ResolvedPathname = ReturnType; + + /** + * A union of all the filenames of assets contained in your `static` directory. + */ + export type Asset = ReturnType; +} diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 865044887673..9b39cd10c310 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -2950,4 +2950,58 @@ declare module '$service-worker' { export const version: string; } +/** + * This module contains generated types for the routes in your app. + */ +declare module '$app/types' { + /** + * Interface for all generated app types. This gets extended via declaration merging. DO NOT USE THIS INTERFACE DIRECTLY. + */ + export interface AppTypes { + // These are all functions so that we can leverage function overloads to get the correct type. + // Using the return types directly would error with a "not the same type" error. + // https://www.typescriptlang.org/docs/handbook/declaration-merging.html#merging-interfaces + RouteId(): string; + RouteParams(): Record>; + LayoutParams(): Record>; + Pathname(): string; + ResolvedPathname(): string; + Asset(): string; + } + + /** + * A union of all the route IDs in your app. Used for `page.route.id` and `event.route.id`. + */ + export type RouteId = ReturnType; + + /** + * A utility for getting the parameters associated with a given route. + */ + export type RouteParams = T extends keyof ReturnType + ? ReturnType[T] + : Record; + + /** + * A utility for getting the parameters associated with a given layout, which is similar to `RouteParams` but also includes optional parameters for any child route. + */ + export type LayoutParams = T extends keyof ReturnType + ? ReturnType[T] + : Record; + + /** + * A union of all valid pathnames in your app. + */ + export type Pathname = ReturnType; + + /** + * `Pathname`, but possibly prefixed with a base path. Used for `page.url.pathname`. + */ + export type ResolvedPathname = ReturnType; + + /** + * A union of all the filenames of assets contained in your `static` directory. + */ + export type Asset = ReturnType; +} + //# sourceMappingURL=index.d.ts.map \ No newline at end of file