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 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-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/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..1c8fa148dab4 --- /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..c46044a8f679 --- /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 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 + +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 d1d311ff036b..303609691982 100644 --- a/documentation/docs/98-reference/54-types.md +++ b/documentation/docs/98-reference/54-types.md @@ -119,80 +119,9 @@ 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" }` - -### 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: - -```json -/// file: .svelte-kit/tsconfig.json -{ - "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`. +> `{ "extends": "$app/tsconfig" }` ## app.d.ts 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: { 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"] } diff --git a/packages/kit/scripts/generate-dts.js b/packages/kit/scripts/generate-dts.js index db78e8d968db..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', @@ -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'], @@ -24,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/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/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/core/sync/sync.js b/packages/kit/src/core/sync/sync.js index 0adf76e3c5ac..c4780f90ba22 100644 --- a/packages/kit/src/core/sync/sync.js +++ b/packages/kit/src/core/sync/sync.js @@ -1,11 +1,9 @@ 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'; +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, @@ -21,7 +19,6 @@ import { write_env } from './write_env.js'; */ export function init(config, root) { write_tsconfig(config.kit, root); - write_ambient(config.kit); } /** @@ -37,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); + write_app_types(config.kit, manifest_data, root); return { manifest_data }; } @@ -65,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); + write_app_types(config.kit, manifest_data, root); } /** @@ -81,13 +78,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_app_types(config.kit, manifest_data, root); } /** @@ -100,7 +97,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(entry, env_config, root); return env_config; } 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 91% rename from packages/kit/src/core/sync/write_non_ambient.js rename to packages/kit/src/core/sync/write_app_types.js index 3010cc281f7f..d0a8c71614a9 100644 --- a/packages/kit/src/core/sync/write_non_ambient.js +++ b/packages/kit/src/core/sync/write_app_types.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,16 +70,15 @@ declare module "svelte/elements" { 'data-sveltekit-replacestate'?: true | false | '' | undefined | null; } } - -export {}; `; /** * 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(); @@ -96,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)}]>`; @@ -253,13 +250,22 @@ function generate_app_types(manifest_data, config) { } /** - * 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) { - const app_types = generate_app_types(manifest_data, config); - const content = [template, app_types].join('\n\n'); +export function write_app_types(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, dir), + template.trim() + ].join('\n\n'); - write_if_changed(path.join(config.outDir, 'non-ambient.d.ts'), content); + write_if_changed(path.join(dir, 'index.d.ts'), content); } diff --git a/packages/kit/src/core/sync/write_env.js b/packages/kit/src/core/sync/write_env.js index 1a6946a81982..402e2ac6fb46 100644 --- a/packages/kit/src/core/sync/write_env.js +++ b/packages/kit/src/core/sync/write_env.js @@ -10,16 +10,18 @@ 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) { +export function write_env(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_tsconfig.js b/packages/kit/src/core/sync/write_tsconfig.js deleted file mode 100644 index 2f6b1eacc506..000000000000 --- a/packages/kit/src/core/sync/write_tsconfig.js +++ /dev/null @@ -1,258 +0,0 @@ -import fs from 'node:fs'; -import path from 'node:path'; -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'; - -/** - * @param {string} cwd - * @param {string} file - */ -function maybe_file(cwd, file) { - const resolved = path.resolve(cwd, file); - if (fs.existsSync(resolved)) { - return resolved; - } -} - -/** - * @param {string} cwd - * @param {string} file - */ -function project_relative(cwd, file) { - return posixify(path.relative(cwd, file)); -} - -/** - * @param {string} file - */ -function remove_trailing_slashstar(file) { - if (file.endsWith('/*')) { - return file.slice(0, -2); - } else { - return file; - } -} - -/** - * Generates the tsconfig that the user's tsconfig inherits from. - * @param {import('types').ValidatedKitConfig} kit - * @param {string} cwd - */ -export function write_tsconfig(kit, cwd) { - const out = path.join(kit.outDir, 'tsconfig.json'); - - const user_config = load_user_tsconfig(cwd); - if (user_config) validate_user_config(cwd, out, user_config); - - write_if_changed(out, JSON.stringify(get_tsconfig(kit, cwd), null, '\t')); -} - -/** - * Generates the tsconfig that the user's tsconfig inherits from. - * @param {import('types').ValidatedKitConfig} kit - * @param {string} cwd - */ -export function get_tsconfig(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 = { - compilerOptions: { - // generated options - paths: { - ...get_tsconfig_paths(kit, cwd), - '$app/types': ['./types/index.d.ts'] - }, - rootDirs: [config_relative('.'), './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, - - // 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' - }, - include: [...include], - exclude - }; - - 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'); - - 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'); - - return { - kind: path.basename(file), - options: (0, eval)(`(${json})`) - }; -} - -/** - * @param {string} cwd - * @param {string} out - * @param {{ kind: string, options: any }} config - */ -function validate_user_config(cwd, out, 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 - : Array.isArray(extend) - ? extend.some((e) => path.resolve(cwd, e) === out) - : false; - - const options = config.options.compilerOptions || {}; - - if (extends_framework_config) { - const { paths, baseUrl } = options; - - // 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. ` + - 'Remove it to avoid problems with intellisense. For path aliases, use subpath imports instead: https://svelte.dev/docs/kit/$lib' - ) - ); - } - } 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}`); - } -} - -// -const alias_regex = /^(.+?)(\/\*)?$/; -// -const value_regex = /^(.*?)((\/\*)|(\.\w+))?$/; - -/** - * 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} cwd - */ -function get_tsconfig_paths(config, cwd) { - /** @param {string} file */ - const config_relative = (file) => { - let relative_path = path.relative(config.outDir, 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(cwd); - 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.spec.js b/packages/kit/src/core/sync/write_tsconfig.spec.js deleted file mode 100644 index 3234717dfecc..000000000000 --- a/packages/kit/src/core/sync/write_tsconfig.spec.js +++ /dev/null @@ -1,114 +0,0 @@ -import { assert, expect, test } from 'vitest'; -import { validate_config } from '../config/index.js'; -import { get_tsconfig } from './write_tsconfig.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(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'], - 'key/*': ['../some/other/value/*'], - keyToFile: ['../path/to/file.ts'], - $routes: ['./types/src/routes'], - '$routes/*': ['./types/src/routes/*'] - }); -}); - -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'); - - // 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/*'] - }); -}); - -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(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(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_tsconfig/index.js b/packages/kit/src/core/sync/write_tsconfig/index.js new file mode 100644 index 000000000000..28b6a19dbb91 --- /dev/null +++ b/packages/kit/src/core/sync/write_tsconfig/index.js @@ -0,0 +1,245 @@ +/** @import { ValidatedKitConfig } from 'types' */ +import process from 'node:process'; +import fs from 'node:fs'; +import path from 'node:path'; +import ts from 'typescript'; +import { styleText } from 'node:util'; +import { write_if_changed } from '../utils.js'; +import { + ESSENTIAL_OPTIONS, + extends_id, + 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. + * @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: [kit.files.serviceWorker] + }, + { + extends: '$app/tsconfig', + include: ['src'] + }, + kit.typescript.config + ); + + 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' + } + ); +} + +/** + * 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, 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; + + write_if_changed(out_file, JSON.stringify(normalized, null, '\t')); + + const user_config = load_user_tsconfig(dir); + + 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_id(user_config.options, id)) { + 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 = validate_resolved_config(resolved, config.compilerOptions); + + 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}`); + } + } + } +} + +/** @type {Map} */ +const mtimes = new Map(); + +/** + * 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); + + return b > a; +} + +/** + * @param {string} cwd + * @param {string} file + */ +function maybe_file(cwd, file) { + const resolved = path.resolve(cwd, file); + if (fs.existsSync(resolved)) { + return resolved; + } +} + +/** @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) { + 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 options.config; +} + +/** 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. + * Related to vite alias creation. + * + * @param {import('types').ValidatedKitConfig} config + * @param {string} root + * @returns {Record} + */ +function get_paths(config, root) { + const alias = { + ...config.alias, + ...get_subpath_imports(root) + }; + + /** @type {Record} */ + const paths = {}; + + for (const [key, value] of Object.entries(alias)) { + const key_match = alias_key.exec(key); + if (!key_match) throw new Error(`Invalid alias key: ${key}`); + + 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)); + 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; +} 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 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..472b402b499f --- /dev/null +++ b/packages/kit/src/core/sync/write_tsconfig/utils.js @@ -0,0 +1,162 @@ +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 + * @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 +}; + +/** + * Validates that a tsconfig contains `"extends": ""` + * @param {any} options + * @param {string} id + */ +export function extends_id(options, id) { + const o = options.extends; + return Array.isArray(o) ? o.includes(id) : o === id; +} + +/** + * 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) => posixify(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((/** @type {string} */ 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..5a3cf7af73b8 --- /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_id, + 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_id', () => { + const id = 'POTATO'; + + test('validates that a config extends an id (string)', () => { + assert.equal(extends_id({ extends: id }, id), 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 an id', () => { + assert.equal(extends_id({}, id), 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"']); + }); +}); 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..cc6e369de4ba 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 { write_app_types } from '../write_app_types.js'; import { validate_config } from '../../config/index.js'; +import { write_env } from '../write_env.js'; const cwd = path.join(import.meta.dirname, 'test'); @@ -32,10 +33,11 @@ function run_test(dir) { }); write_all_types(initial, manifest, root); - write_non_ambient(initial.kit, manifest); + write_app_types(initial.kit, manifest, root); + write_env('', {}, 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/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/**"] } 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/**"] } 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/src/runtime/app/service-worker/index.js b/packages/kit/src/runtime/app/service-worker/index.js new file mode 100644 index 000000000000..88c1981a8a4d --- /dev/null +++ b/packages/kit/src/runtime/app/service-worker/index.js @@ -0,0 +1,24 @@ +/// +/// +/// + +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 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} */ ( + /** @type {unknown} */ (globalThis.self) +); + +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/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..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 }, - "extends": "./.svelte-kit/tsconfig.json" + "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 be69d36e3c2c..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.push('../unit-test/*.js', '../test/*.js', '../playwright.config.js'); - } } }) ], diff --git a/packages/kit/test/apps/basics/tsconfig.json b/packages/kit/test/apps/basics/tsconfig.json index 4cf3b2a60fe9..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": "./.svelte-kit/tsconfig.json" + "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 3d5dd55594df..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.push('../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/options/tsconfig.json b/packages/kit/test/apps/options/tsconfig.json index 77500cd3c88f..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": "./.custom-out-dir/tsconfig.json" + "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 9ac7b8a758ef..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.push('../vite.custom.config.js', '../playwright.config.js'); - } } }) ], 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" } 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 f3c28f49ed09..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. @@ -3568,6 +3571,19 @@ declare module '$app/server' { export {}; } +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; + + export {}; +} + declare module '$app/state' { /** * A read-only reactive object with information about the current page, serving several use cases: 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"] }