Skip to content
Merged
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
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,18 +114,19 @@ Options for the TypeScript checker (`typescript` option object).
| `mode` | `'readonly'` or `'write-dts'` or `'write-tsbuildinfo'` or `'write-references'` | `build === true ? 'write-tsbuildinfo' ? 'readonly'` | Use `readonly` if you don't want to write anything on the disk, `write-dts` to write only `.d.ts` files, `write-tsbuildinfo` to write only `.tsbuildinfo` files, `write-references` to write both `.js` and `.d.ts` files of project references (last 2 modes requires `build: true`). |
| `diagnosticOptions` | `object` | `{ syntactic: false, semantic: true, declaration: false, global: false }` | Settings to select which diagnostics do we want to perform. |
| `profile` | `boolean` | `false` | Measures and prints timings related to the TypeScript performance. |
| `typescriptPath` | `string` | `require.resolve('typescript/package.json')` for TypeScript 7+, `require.resolve('@typescript/native-preview/package.json')` when `tsgo` falls back to preview, otherwise `require.resolve('typescript')` | If supplied this is a custom path where TypeScript can be found. In `tsgo` mode, it must be an absolute path to `typescript/package.json` from TypeScript 7+ or `@typescript/native-preview/package.json`. |
| `resolveRoot` | `string` | `undefined` | Root used to resolve the default TypeScript package. Relative paths are resolved from `compiler.options.context`. Only used when `typescriptPath` is not set. |
| `typescriptPath` | `string` | `require.resolve('typescript/package.json')` for TypeScript 7+, `require.resolve('@typescript/native-preview/package.json')` when `tsgo` falls back to preview, otherwise `require.resolve('typescript')` | Custom TypeScript path. In `tsgo` mode, use an absolute path to `typescript/package.json` or `@typescript/native-preview/package.json`. |
| `tsgo` | `boolean` | `true` when TypeScript 7+ is detected, otherwise `false` | Enables experimental TypeScript Go support. The plugin runs the TypeScript Go checker binary in a child process. |

### TypeScript Go support

`typescript.tsgo` can reduce type-checking time by about 5-10x, especially on large projects, by using the Go implementation of TypeScript. It enables experimental [typescript-go](https://github.com/microsoft/typescript-go) support through TypeScript 7+ or [`@typescript/native-preview`](https://www.npmjs.com/package/@typescript/native-preview).

When the configured or default installed `typescript` package is major version 7 or higher, the plugin enables `typescript.tsgo` automatically and runs the TypeScript Go executable from that package. When `typescript.tsgo: true` is set without a custom `typescriptPath` and TypeScript 7+ is not installed, the plugin falls back to `@typescript/native-preview`.
When the configured or default installed `typescript` package is major version 7 or higher, the plugin enables `typescript.tsgo` automatically and runs the TypeScript Go executable from that package. The default package detection uses `typescript.resolveRoot` when it is set. When `typescript.tsgo: true` is set without a custom `typescriptPath` and TypeScript 7+ is not installed, the plugin falls back to `@typescript/native-preview`.

In this mode, the plugin runs the TypeScript Go binary in a child process, parses its diagnostics, and reports them through the existing issue formatter when possible. If the output cannot be parsed safely, the raw output is printed and the build fails when it exits with errors.

Supported options include `typescript.configFile`, `typescript.context`, `typescript.build`, `typescript.typescriptPath`, `async`, and `logger`. It also supports `tsconfig.json` compiler options used by `tsgo`, including `incremental` and `composite`.
Supported options include `typescript.configFile`, `typescript.context`, `typescript.build`, `typescript.resolveRoot`, `typescript.typescriptPath`, `async`, and `logger`. It also supports `tsconfig.json` compiler options used by `tsgo`, including `incremental` and `composite`.

Install TypeScript 7.0 RC to enable `tsgo` automatically:

Expand Down
48 changes: 33 additions & 15 deletions src/typescript/type-script-worker-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ interface TypeScriptWorkerConfig {
mode: 'readonly' | 'write-dts' | 'write-tsbuildinfo' | 'write-references';
diagnosticOptions: TypeScriptDiagnosticsOptions;
profile: boolean;
resolveRoot?: string;
typescriptPath: string;
tsgo?: boolean;
tsgoPackage?: TypeScriptGoPackage;
Expand All @@ -35,9 +36,17 @@ type TypeScriptRuntimeConfig = Pick<
'typescriptPath' | 'tsgo' | 'tsgoPackage'
>;

function resolveInstalledTypeScriptPackageForTsgo(): ResolvedTypeScriptGoPackage | undefined {
function resolveModule(request: string, resolveRoot?: string): string {
return resolveRoot
? require.resolve(request, { paths: [resolveRoot] })
: require.resolve(request);
}

function resolveInstalledTsgoPackage(
resolveRoot?: string,
): ResolvedTypeScriptGoPackage | undefined {
try {
const packageJsonPath = require.resolve(TYPESCRIPT_PACKAGE_JSON);
const packageJsonPath = resolveModule(TYPESCRIPT_PACKAGE_JSON, resolveRoot);
const tsgoPackage = resolveTypeScriptGoPackage(packageJsonPath);

if (tsgoPackage?.tsgoPackage === 'typescript') {
Expand All @@ -50,9 +59,9 @@ function resolveInstalledTypeScriptPackageForTsgo(): ResolvedTypeScriptGoPackage
return undefined;
}

function resolveDefaultPreviewPackageJsonPath(): string {
function resolveDefaultPreviewPackageJsonPath(resolveRoot?: string): string {
try {
return require.resolve(TYPESCRIPT_PREVIEW_PACKAGE_JSON);
return resolveModule(TYPESCRIPT_PREVIEW_PACKAGE_JSON, resolveRoot);
} catch {
return TYPESCRIPT_PREVIEW_PACKAGE_JSON;
}
Expand All @@ -77,31 +86,31 @@ function resolveTypeScriptRuntimeConfig(

if (options.tsgo === false) {
return {
typescriptPath: require.resolve('typescript'),
typescriptPath: resolveModule('typescript', options.resolveRoot),
tsgo: false,
};
}

const installedTypeScriptTsgoPackage = resolveInstalledTypeScriptPackageForTsgo();
const installedTsgoPackage = resolveInstalledTsgoPackage(options.resolveRoot);

if (installedTypeScriptTsgoPackage) {
if (installedTsgoPackage) {
return {
typescriptPath: installedTypeScriptTsgoPackage.packageJsonPath,
typescriptPath: installedTsgoPackage.packageJsonPath,
tsgo: true,
tsgoPackage: 'typescript',
};
}

if (options.tsgo === true) {
return {
typescriptPath: resolveDefaultPreviewPackageJsonPath(),
typescriptPath: resolveDefaultPreviewPackageJsonPath(options.resolveRoot),
tsgo: true,
tsgoPackage: 'preview',
};
}

return {
typescriptPath: require.resolve('typescript'),
typescriptPath: resolveModule('typescript', options.resolveRoot),
};
}

Expand All @@ -121,25 +130,34 @@ function createTypeScriptWorkerConfig(

const optionsAsObject: Exclude<TypeScriptWorkerOptions, boolean> =
typeof options === 'object' ? options : {};
const typescriptRuntimeConfig = resolveTypeScriptRuntimeConfig(optionsAsObject);
const resolveRoot = optionsAsObject.resolveRoot
? path.isAbsolute(optionsAsObject.resolveRoot)
? optionsAsObject.resolveRoot
: path.resolve(compiler.options.context || process.cwd(), optionsAsObject.resolveRoot)
: undefined;
const normalizedOptions = {
...optionsAsObject,
...(resolveRoot ? { resolveRoot } : {}),
};
const typescriptRuntimeConfig = resolveTypeScriptRuntimeConfig(normalizedOptions);

return {
enabled: Boolean(options) || options === undefined,
memoryLimit: 8192,
build: false,
mode: optionsAsObject.build ? 'write-tsbuildinfo' : 'readonly',
profile: false,
...optionsAsObject,
...normalizedOptions,
...typescriptRuntimeConfig,
configFile: configFile,
configOverwrite: optionsAsObject.configOverwrite || {},
context: optionsAsObject.context || path.dirname(configFile),
configOverwrite: normalizedOptions.configOverwrite || {},
context: normalizedOptions.context || path.dirname(configFile),
diagnosticOptions: {
syntactic: false, // by default they are reported by the loader
semantic: true,
declaration: false,
global: false,
...(optionsAsObject.diagnosticOptions || {}),
...(normalizedOptions.diagnosticOptions || {}),
},
};
}
Expand Down
1 change: 1 addition & 0 deletions src/typescript/type-script-worker-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ type TypeScriptWorkerOptions = {
mode?: 'readonly' | 'write-tsbuildinfo' | 'write-dts' | 'write-references';
diagnosticOptions?: Partial<TypeScriptDiagnosticsOptions>;
profile?: boolean;
resolveRoot?: string;
typescriptPath?: string;
tsgo?: boolean;
};
Expand Down
89 changes: 86 additions & 3 deletions test/unit/typescript/type-script-worker-config.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,23 +40,42 @@ describe('typescript/type-scripts-worker-config', () => {
};
const tempDirs: string[] = [];

function createTypeScriptPackage(version: string) {
const packagePath = fs.mkdtempSync(path.join(os.tmpdir(), 'ts-checker-typescript-config-'));
function createResolveRoot() {
const resolveRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ts-checker-resolve-root-'));

tempDirs.push(resolveRoot);

return resolveRoot;
}

function createTypeScriptPackage(version: string, resolveRoot?: string) {
const packagePath = resolveRoot
? path.join(resolveRoot, 'node_modules', 'typescript')
: fs.mkdtempSync(path.join(os.tmpdir(), 'ts-checker-typescript-config-'));
const binDir = path.join(packagePath, 'bin');
const libDir = path.join(packagePath, 'lib');

tempDirs.push(packagePath);
if (!resolveRoot) {
tempDirs.push(packagePath);
}
fs.mkdirSync(binDir, { recursive: true });
fs.mkdirSync(libDir, { recursive: true });
fs.writeFileSync(
path.join(packagePath, 'package.json'),
JSON.stringify({
name: 'typescript',
version,
main: 'lib/typescript.js',
bin: {
tsc: 'bin/tsc',
},
}),
);
fs.writeFileSync(path.join(binDir, 'tsc'), '#!/usr/bin/env node\n');
fs.writeFileSync(
path.join(libDir, 'typescript.js'),
`module.exports = { version: ${JSON.stringify(version)} };\n`,
);

return path.join(packagePath, 'package.json');
}
Expand Down Expand Up @@ -163,6 +182,70 @@ describe('typescript/type-scripts-worker-config', () => {
}
});

it('uses resolveRoot to resolve the default TypeScript package', async () => {
const resolveRoot = createResolveRoot();
createTypeScriptPackage('6.1.0', resolveRoot);
const typescriptPath = require.resolve('typescript', { paths: [resolveRoot] });
const { createTypeScriptWorkerConfig } = await import(
'src/typescript/type-script-worker-config'
);

expect(
createTypeScriptWorkerConfig(compiler, {
resolveRoot,
})
).toEqual({
...configuration,
resolveRoot,
typescriptPath,
});
});

it('uses resolveRoot when detecting the default TypeScript Go package', async () => {
const resolveRoot = createResolveRoot();
createTypeScriptPackage('7.1.0', resolveRoot);
const packageJsonPath = require.resolve('typescript/package.json', {
paths: [resolveRoot],
});
const { createTypeScriptWorkerConfig } = await import(
'src/typescript/type-script-worker-config'
);

expect(
createTypeScriptWorkerConfig(compiler, {
resolveRoot,
})
).toEqual({
...configuration,
resolveRoot,
tsgo: true,
tsgoPackage: 'typescript',
typescriptPath: packageJsonPath,
});
});

it('prefers typescriptPath over resolveRoot', async () => {
const resolveRoot = createResolveRoot();
createTypeScriptPackage('7.1.0', resolveRoot);
const packageJsonPath = createTypeScriptPackage('7.2.0');
const { createTypeScriptWorkerConfig } = await import(
'src/typescript/type-script-worker-config'
);

expect(
createTypeScriptWorkerConfig(compiler, {
resolveRoot,
typescriptPath: packageJsonPath,
})
).toEqual({
...configuration,
resolveRoot,
tsgo: true,
tsgoPackage: 'typescript',
typescriptPath: packageJsonPath,
});
});

it('infers tsgo when typescriptPath points to supported TypeScript package', async () => {
const packageJsonPath = createTypeScriptPackage('7.1.0');
const { createTypeScriptWorkerConfig } = await import(
Expand Down
Loading