Skip to content

Commit c51cddc

Browse files
authored
feat: add TypeScript resolveRoot option (#111)
1 parent a780128 commit c51cddc

4 files changed

Lines changed: 124 additions & 21 deletions

File tree

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,18 +114,19 @@ Options for the TypeScript checker (`typescript` option object).
114114
| `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`). |
115115
| `diagnosticOptions` | `object` | `{ syntactic: false, semantic: true, declaration: false, global: false }` | Settings to select which diagnostics do we want to perform. |
116116
| `profile` | `boolean` | `false` | Measures and prints timings related to the TypeScript performance. |
117-
| `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`. |
117+
| `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. |
118+
| `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`. |
118119
| `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. |
119120

120121
### TypeScript Go support
121122

122123
`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).
123124

124-
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`.
125+
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`.
125126

126127
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.
127128

128-
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`.
129+
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`.
129130

130131
Install TypeScript 7.0 RC to enable `tsgo` automatically:
131132

src/typescript/type-script-worker-config.ts

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ interface TypeScriptWorkerConfig {
2525
mode: 'readonly' | 'write-dts' | 'write-tsbuildinfo' | 'write-references';
2626
diagnosticOptions: TypeScriptDiagnosticsOptions;
2727
profile: boolean;
28+
resolveRoot?: string;
2829
typescriptPath: string;
2930
tsgo?: boolean;
3031
tsgoPackage?: TypeScriptGoPackage;
@@ -35,9 +36,17 @@ type TypeScriptRuntimeConfig = Pick<
3536
'typescriptPath' | 'tsgo' | 'tsgoPackage'
3637
>;
3738

38-
function resolveInstalledTypeScriptPackageForTsgo(): ResolvedTypeScriptGoPackage | undefined {
39+
function resolveModule(request: string, resolveRoot?: string): string {
40+
return resolveRoot
41+
? require.resolve(request, { paths: [resolveRoot] })
42+
: require.resolve(request);
43+
}
44+
45+
function resolveInstalledTsgoPackage(
46+
resolveRoot?: string,
47+
): ResolvedTypeScriptGoPackage | undefined {
3948
try {
40-
const packageJsonPath = require.resolve(TYPESCRIPT_PACKAGE_JSON);
49+
const packageJsonPath = resolveModule(TYPESCRIPT_PACKAGE_JSON, resolveRoot);
4150
const tsgoPackage = resolveTypeScriptGoPackage(packageJsonPath);
4251

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

53-
function resolveDefaultPreviewPackageJsonPath(): string {
62+
function resolveDefaultPreviewPackageJsonPath(resolveRoot?: string): string {
5463
try {
55-
return require.resolve(TYPESCRIPT_PREVIEW_PACKAGE_JSON);
64+
return resolveModule(TYPESCRIPT_PREVIEW_PACKAGE_JSON, resolveRoot);
5665
} catch {
5766
return TYPESCRIPT_PREVIEW_PACKAGE_JSON;
5867
}
@@ -77,31 +86,31 @@ function resolveTypeScriptRuntimeConfig(
7786

7887
if (options.tsgo === false) {
7988
return {
80-
typescriptPath: require.resolve('typescript'),
89+
typescriptPath: resolveModule('typescript', options.resolveRoot),
8190
tsgo: false,
8291
};
8392
}
8493

85-
const installedTypeScriptTsgoPackage = resolveInstalledTypeScriptPackageForTsgo();
94+
const installedTsgoPackage = resolveInstalledTsgoPackage(options.resolveRoot);
8695

87-
if (installedTypeScriptTsgoPackage) {
96+
if (installedTsgoPackage) {
8897
return {
89-
typescriptPath: installedTypeScriptTsgoPackage.packageJsonPath,
98+
typescriptPath: installedTsgoPackage.packageJsonPath,
9099
tsgo: true,
91100
tsgoPackage: 'typescript',
92101
};
93102
}
94103

95104
if (options.tsgo === true) {
96105
return {
97-
typescriptPath: resolveDefaultPreviewPackageJsonPath(),
106+
typescriptPath: resolveDefaultPreviewPackageJsonPath(options.resolveRoot),
98107
tsgo: true,
99108
tsgoPackage: 'preview',
100109
};
101110
}
102111

103112
return {
104-
typescriptPath: require.resolve('typescript'),
113+
typescriptPath: resolveModule('typescript', options.resolveRoot),
105114
};
106115
}
107116

@@ -121,25 +130,34 @@ function createTypeScriptWorkerConfig(
121130

122131
const optionsAsObject: Exclude<TypeScriptWorkerOptions, boolean> =
123132
typeof options === 'object' ? options : {};
124-
const typescriptRuntimeConfig = resolveTypeScriptRuntimeConfig(optionsAsObject);
133+
const resolveRoot = optionsAsObject.resolveRoot
134+
? path.isAbsolute(optionsAsObject.resolveRoot)
135+
? optionsAsObject.resolveRoot
136+
: path.resolve(compiler.options.context || process.cwd(), optionsAsObject.resolveRoot)
137+
: undefined;
138+
const normalizedOptions = {
139+
...optionsAsObject,
140+
...(resolveRoot ? { resolveRoot } : {}),
141+
};
142+
const typescriptRuntimeConfig = resolveTypeScriptRuntimeConfig(normalizedOptions);
125143

126144
return {
127145
enabled: Boolean(options) || options === undefined,
128146
memoryLimit: 8192,
129147
build: false,
130148
mode: optionsAsObject.build ? 'write-tsbuildinfo' : 'readonly',
131149
profile: false,
132-
...optionsAsObject,
150+
...normalizedOptions,
133151
...typescriptRuntimeConfig,
134152
configFile: configFile,
135-
configOverwrite: optionsAsObject.configOverwrite || {},
136-
context: optionsAsObject.context || path.dirname(configFile),
153+
configOverwrite: normalizedOptions.configOverwrite || {},
154+
context: normalizedOptions.context || path.dirname(configFile),
137155
diagnosticOptions: {
138156
syntactic: false, // by default they are reported by the loader
139157
semantic: true,
140158
declaration: false,
141159
global: false,
142-
...(optionsAsObject.diagnosticOptions || {}),
160+
...(normalizedOptions.diagnosticOptions || {}),
143161
},
144162
};
145163
}

src/typescript/type-script-worker-options.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ type TypeScriptWorkerOptions = {
1010
mode?: 'readonly' | 'write-tsbuildinfo' | 'write-dts' | 'write-references';
1111
diagnosticOptions?: Partial<TypeScriptDiagnosticsOptions>;
1212
profile?: boolean;
13+
resolveRoot?: string;
1314
typescriptPath?: string;
1415
tsgo?: boolean;
1516
};

test/unit/typescript/type-script-worker-config.spec.ts

Lines changed: 86 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,23 +40,42 @@ describe('typescript/type-scripts-worker-config', () => {
4040
};
4141
const tempDirs: string[] = [];
4242

43-
function createTypeScriptPackage(version: string) {
44-
const packagePath = fs.mkdtempSync(path.join(os.tmpdir(), 'ts-checker-typescript-config-'));
43+
function createResolveRoot() {
44+
const resolveRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ts-checker-resolve-root-'));
45+
46+
tempDirs.push(resolveRoot);
47+
48+
return resolveRoot;
49+
}
50+
51+
function createTypeScriptPackage(version: string, resolveRoot?: string) {
52+
const packagePath = resolveRoot
53+
? path.join(resolveRoot, 'node_modules', 'typescript')
54+
: fs.mkdtempSync(path.join(os.tmpdir(), 'ts-checker-typescript-config-'));
4555
const binDir = path.join(packagePath, 'bin');
56+
const libDir = path.join(packagePath, 'lib');
4657

47-
tempDirs.push(packagePath);
58+
if (!resolveRoot) {
59+
tempDirs.push(packagePath);
60+
}
4861
fs.mkdirSync(binDir, { recursive: true });
62+
fs.mkdirSync(libDir, { recursive: true });
4963
fs.writeFileSync(
5064
path.join(packagePath, 'package.json'),
5165
JSON.stringify({
5266
name: 'typescript',
5367
version,
68+
main: 'lib/typescript.js',
5469
bin: {
5570
tsc: 'bin/tsc',
5671
},
5772
}),
5873
);
5974
fs.writeFileSync(path.join(binDir, 'tsc'), '#!/usr/bin/env node\n');
75+
fs.writeFileSync(
76+
path.join(libDir, 'typescript.js'),
77+
`module.exports = { version: ${JSON.stringify(version)} };\n`,
78+
);
6079

6180
return path.join(packagePath, 'package.json');
6281
}
@@ -163,6 +182,70 @@ describe('typescript/type-scripts-worker-config', () => {
163182
}
164183
});
165184

185+
it('uses resolveRoot to resolve the default TypeScript package', async () => {
186+
const resolveRoot = createResolveRoot();
187+
createTypeScriptPackage('6.1.0', resolveRoot);
188+
const typescriptPath = require.resolve('typescript', { paths: [resolveRoot] });
189+
const { createTypeScriptWorkerConfig } = await import(
190+
'src/typescript/type-script-worker-config'
191+
);
192+
193+
expect(
194+
createTypeScriptWorkerConfig(compiler, {
195+
resolveRoot,
196+
})
197+
).toEqual({
198+
...configuration,
199+
resolveRoot,
200+
typescriptPath,
201+
});
202+
});
203+
204+
it('uses resolveRoot when detecting the default TypeScript Go package', async () => {
205+
const resolveRoot = createResolveRoot();
206+
createTypeScriptPackage('7.1.0', resolveRoot);
207+
const packageJsonPath = require.resolve('typescript/package.json', {
208+
paths: [resolveRoot],
209+
});
210+
const { createTypeScriptWorkerConfig } = await import(
211+
'src/typescript/type-script-worker-config'
212+
);
213+
214+
expect(
215+
createTypeScriptWorkerConfig(compiler, {
216+
resolveRoot,
217+
})
218+
).toEqual({
219+
...configuration,
220+
resolveRoot,
221+
tsgo: true,
222+
tsgoPackage: 'typescript',
223+
typescriptPath: packageJsonPath,
224+
});
225+
});
226+
227+
it('prefers typescriptPath over resolveRoot', async () => {
228+
const resolveRoot = createResolveRoot();
229+
createTypeScriptPackage('7.1.0', resolveRoot);
230+
const packageJsonPath = createTypeScriptPackage('7.2.0');
231+
const { createTypeScriptWorkerConfig } = await import(
232+
'src/typescript/type-script-worker-config'
233+
);
234+
235+
expect(
236+
createTypeScriptWorkerConfig(compiler, {
237+
resolveRoot,
238+
typescriptPath: packageJsonPath,
239+
})
240+
).toEqual({
241+
...configuration,
242+
resolveRoot,
243+
tsgo: true,
244+
tsgoPackage: 'typescript',
245+
typescriptPath: packageJsonPath,
246+
});
247+
});
248+
166249
it('infers tsgo when typescriptPath points to supported TypeScript package', async () => {
167250
const packageJsonPath = createTypeScriptPackage('7.1.0');
168251
const { createTypeScriptWorkerConfig } = await import(

0 commit comments

Comments
 (0)