Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/quiet-typescript-compiler.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

fix: explain missing TypeScript compiler API instead of crashing with TypeScript 7
11 changes: 11 additions & 0 deletions documentation/docs/+assets/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1052,6 +1052,17 @@
],
"details": "SvelteKit reads your `tsconfig.json` (or `jsconfig.json`) to check that it is compatible with the configuration it generates, and TypeScript could not parse the file. Fix the problem at the reported location. Note that the file may contain comments and trailing commas, but must otherwise be valid JSON."
},
"typescript_compiler_api_missing": {
"variants": [
{
"text": "TypeScript %version% does not provide the JavaScript compiler API required by SvelteKit. Install `typescript@^6.0.0`. To use TypeScript 7 for type checking, install it separately as `@typescript/native`.",
"variables": [
"version"
]
}
],
"details": "SvelteKit uses the TypeScript 6 JavaScript compiler API to validate your `tsconfig.json` (or `jsconfig.json`) and generate route types. TypeScript 7 does not include this API. Keep TypeScript 6 installed as `typescript`, even if you use `@typescript/native` for type checking."
},
"adapter_missing": {
"variants": [
{
Expand Down
6 changes: 6 additions & 0 deletions packages/kit/messages/build-errors/tsconfig.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,9 @@
> Failed to parse TypeScript config: %details%

SvelteKit reads your `tsconfig.json` (or `jsconfig.json`) to check that it is compatible with the configuration it generates, and TypeScript could not parse the file. Fix the problem at the reported location. Note that the file may contain comments and trailing commas, but must otherwise be valid JSON.

## typescript_compiler_api_missing

> TypeScript %version% does not provide the JavaScript compiler API required by SvelteKit. Install `typescript@^6.0.0`. To use TypeScript 7 for type checking, install it separately as `@typescript/native`.

SvelteKit uses the TypeScript 6 JavaScript compiler API to validate your `tsconfig.json` (or `jsconfig.json`) and generate route types. TypeScript 7 does not include this API. Keep TypeScript 6 installed as `typescript`, even if you use `@typescript/native` for type checking.
12 changes: 11 additions & 1 deletion packages/kit/src/core/sync/ts.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import * as e from '../../messages/build-errors.js';

/** @type {typeof import('typescript')} */
// @ts-ignore
export let ts = undefined;
try {
ts = (await import('typescript')).default;
} catch {}
} catch {
// TypeScript is an optional peer dependency.
}

// TypeScript 7 imports successfully, but no longer provides the JavaScript compiler API.
// Keep this outside the catch so an incompatible installation is not treated as an absent one.
if (ts && typeof ts.createSourceFile !== 'function') {
e.typescript_compiler_api_missing({ version: ts.version }, { stackless: true });
}
80 changes: 80 additions & 0 deletions packages/kit/src/core/sync/ts.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { afterEach, expect, test, vi } from 'vitest';
import { process_config, validate_config } from '../config/index.js';

/** @type {string[]} */
const dirs = [];

afterEach(() => {
vi.doUnmock('typescript');
vi.resetModules();
for (const dir of dirs) fs.rmSync(dir, { recursive: true, force: true });
dirs.length = 0;
});

function setup() {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'svelte-kit-typescript-'));
dirs.push(root);
fs.writeFileSync(path.join(root, 'tsconfig.json'), '{ "extends": "$app/tsconfig" }');
return { root, config: process_config(validate_config({}), root) };
}

test('loads the TypeScript compiler API', async () => {
const { ts } = await import('./ts.js');
expect(ts).toBe((await import('typescript')).default);
expect(typeof ts.createSourceFile).toBe('function');
});

test('TypeScript remains optional for config generation and route types', async () => {
vi.doMock('typescript', () => {
throw new Error('not installed');
});

const { ts } = await import('./ts.js');
expect(ts).toBeUndefined();

const { root, config } = setup();
const { write_tsconfig } = await import('./write_tsconfig/index.js');
write_tsconfig(config, root);
expect(fs.existsSync(path.join(root, 'node_modules/$app/tsconfig.json'))).toBe(true);
expect(fs.existsSync(path.join(root, 'node_modules/$app/tsconfig/service-worker.json'))).toBe(
true
);

const { write_all_types } = await import('./write_types/index.js');
write_all_types(config, /** @type {any} */ ({}), root);
expect(fs.existsSync(path.join(config.outDir, 'types'))).toBe(false);
});

test.each(['compiler API', 'tsconfig', 'route types'])(
'explains the missing TypeScript compiler API when loading %s',
async (entry) => {
// TypeScript 7 still imports successfully, but no longer exports the JS compiler API.
const typescript = { version: '7.0.2', versionMajorMinor: '7.0' };
vi.doMock('typescript', () => ({
default: typescript,
...typescript,
readConfigFile: undefined,
sys: undefined
}));

async function run() {
if (entry === 'compiler API') {
await import('./ts.js');
} else if (entry === 'tsconfig') {
const { root, config } = setup();
const { write_tsconfig } = await import('./write_tsconfig/index.js');
write_tsconfig(config, root);
} else {
const { tweak_types } = await import('./write_types/index.js');
tweak_types('export const load = () => ({ greeting: "hello" });', false);
}
}

await expect(run()).rejects.toThrowKitError('typescript_compiler_api_missing', {
contains: ['7.0.2']
});
}
);
9 changes: 1 addition & 8 deletions packages/kit/src/core/sync/write_tsconfig/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import process from 'node:process';
import fs from 'node:fs';
import path from 'node:path';
import { write_if_changed } from '../utils.js';
import { ts } from '../ts.js';
import {
ESSENTIAL_OPTIONS,
normalize_config,
Expand All @@ -15,14 +16,6 @@ import * as w from '../../../messages/build-warnings.js';
import { posixify } from '../../../utils/os.js';
import { bullet_list } from '../../../utils/format.js';

/** @type {typeof import('typescript')} */
let ts;
try {
ts = await import('typescript');
} catch {
// The user has not installed TypeScript.
}

/**
* Generates the tsconfig that the user's tsconfig inherits from.
* @param {ValidatedConfig} kit
Expand Down
10 changes: 10 additions & 0 deletions packages/kit/src/messages/build-errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -919,6 +919,16 @@ export function tsconfig_parse_failed(_values, options) {
throw_error('tsconfig_parse_failed', (_values?.details !== undefined ? `Failed to parse TypeScript config: ${_values.details}` : `Failed to parse TypeScript config`), options, tsconfig_parse_failed);
}

/**
* TypeScript %version% does not provide the JavaScript compiler API required by SvelteKit. Install `typescript@^6.0.0`. To use TypeScript 7 for type checking, install it separately as `@typescript/native`.
* @param {{ "version": string }} _values
* @param {ThrowOptions} [options]
* @returns {never}
*/
export function typescript_compiler_api_missing(_values, options) {
throw_error('typescript_compiler_api_missing', `TypeScript ${_values.version} does not provide the JavaScript compiler API required by SvelteKit. Install \`typescript@^6.0.0\`. To use TypeScript 7 for type checking, install it separately as \`@typescript/native\`.`, options, typescript_compiler_api_missing);
}

/**
* The configured Vite SSR environment must be a `RunnableDevEnvironment`
* @param {void} _values
Expand Down
Loading