From 211b29da72adc8f8221152b35af6d03b4ae26285 Mon Sep 17 00:00:00 2001 From: std4453 Date: Wed, 7 May 2025 16:01:21 +0800 Subject: [PATCH 1/4] Fix host.ts problems to speed up compilation Originally, the TypeScript cache gets constantly invalidated, causing build time to increase non-linearly, bloating into minutes for fairly complicated projects. This commit identified and fixed these problems that caused this problem: - File version is increased unnecessarily when source hasn't changed. - Files in node_modules are added incorrectly into LanguageServiceHost.fileNames, since `setSnapshot` is called indirectly for all dependency files discovered when type checking. `fileNames` represents the entry files for this build, so it shouldn't be updating during the process. - Also creating snapshot when file read in `getScriptSnapshot` is empty (which is still a valid file whatsoever), otherwise it would be marked as 'missing' and invalidate the cache. --- src/host.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/host.ts b/src/host.ts index b0a2b615..6b5d6108 100644 --- a/src/host.ts +++ b/src/host.ts @@ -31,10 +31,16 @@ export class LanguageServiceHost implements tsTypes.LanguageServiceHost { fileName = normalize(fileName); + const originalSnapshot = this.snapshots[fileName]; + + if (originalSnapshot && originalSnapshot.getText(0, originalSnapshot.getLength()) === source) { + return originalSnapshot; + } + const snapshot = tsModule.ScriptSnapshot.fromString(source); this.snapshots[fileName] = snapshot; this.versions[fileName] = (this.versions[fileName] || 0) + 1; - this.fileNames.add(fileName); + return snapshot; } @@ -46,7 +52,7 @@ export class LanguageServiceHost implements tsTypes.LanguageServiceHost return this.snapshots[fileName]; const source = tsModule.sys.readFile(fileName); - if (source) + if (source !== undefined) return this.setSnapshot(fileName, source); return undefined; From e84d9fcedb10fd7f2448ef3416236231165a1168 Mon Sep 17 00:00:00 2001 From: std4453 Date: Fri, 9 May 2025 16:33:01 +0800 Subject: [PATCH 2/4] fix(host): Fix code style in `setSnapshot` Co-authored-by: Anton Gilgur <4970083+agilgur5@users.noreply.github.com> --- src/host.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/host.ts b/src/host.ts index 6b5d6108..61aa3ff5 100644 --- a/src/host.ts +++ b/src/host.ts @@ -31,11 +31,10 @@ export class LanguageServiceHost implements tsTypes.LanguageServiceHost { fileName = normalize(fileName); - const originalSnapshot = this.snapshots[fileName]; - - if (originalSnapshot && originalSnapshot.getText(0, originalSnapshot.getLength()) === source) { - return originalSnapshot; - } + // don't update the snapshot if there are no changes + const prevSnapshot = this.snapshots[fileName]; + if (prevSnapshot?.getText(0, prevSnapshot.getLength()) === source) + return prevSnapshot; const snapshot = tsModule.ScriptSnapshot.fromString(source); this.snapshots[fileName] = snapshot; From 38e9cd4db8096153b1fcfd87fc2c689da30b2cac Mon Sep 17 00:00:00 2001 From: std4453 Date: Mon, 12 May 2025 12:41:08 +0800 Subject: [PATCH 3/4] fix(host): Filter before adding to fileNames --- __tests__/host.spec.ts | 18 +++++++++++++----- src/host.ts | 5 ++++- src/index.ts | 2 +- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/__tests__/host.spec.ts b/__tests__/host.spec.ts index 8d16ef6f..14240dd9 100644 --- a/__tests__/host.spec.ts +++ b/__tests__/host.spec.ts @@ -15,10 +15,14 @@ setTypescriptModule(ts); }; const defaultConfig = { fileNames: [], errors: [], options: {} }; +const defaultFilter = () => true; const unaryFunc = "const unary = (x: string): string => x.reverse()"; const unaryFuncSnap = { text: unaryFunc }; +const binaryFunc = "const binary = (a: number, b: number): number => a - b"; +const binaryFuncSnap = { text: binaryFunc }; + // host.ts uses `/` normalized path, as does TS itself (https://github.com/microsoft/TypeScript/blob/7f022c58fb8b7253f23c49f0d9eee6fde82b477b/src/compiler/path.ts#L4) const local = (x: string) => normalize(path.resolve(__dirname, x)); const testDir = local("__temp/host"); @@ -37,14 +41,18 @@ test("LanguageServiceHost", async () => { const testOpts = { test: "this is a test" }; const config = { ...defaultConfig, options: testOpts }; const transformers = [() => ({})]; - const host = new LanguageServiceHost(config, transformers, testDir); + const host = new LanguageServiceHost(config, transformers, testDir, defaultFilter); // test core snapshot functionality expect(host.getScriptSnapshot(testFile)).toEqual(unaryFuncSnap); expect(host.getScriptVersion(testFile)).toEqual("1"); - expect(host.setSnapshot(testFile, unaryFunc)).toEqual(unaryFuncSnap); // version 2 + expect(host.setSnapshot(testFile, unaryFunc)).toEqual(unaryFuncSnap); // unchanged expect(host.getScriptSnapshot(testFile)).toEqual(unaryFuncSnap); // get from dict + expect(host.getScriptVersion(testFile)).toEqual("1"); + + expect(host.setSnapshot(testFile, binaryFunc)).toEqual(binaryFuncSnap); // version 2 + expect(host.getScriptSnapshot(testFile)).toEqual(binaryFuncSnap); expect(host.getScriptVersion(testFile)).toEqual("2"); expect(host.getScriptSnapshot(nonExistent)).toBeFalsy(); @@ -89,7 +97,7 @@ test("LanguageServiceHost - getCustomTransformers", () => { after: () => "testAfter", afterDeclarations: () => "testAfterDeclarations", })]; - const host = new LanguageServiceHost(config, transformers as any, testDir); + const host = new LanguageServiceHost(config, transformers as any, testDir, defaultFilter); host.setLanguageService(true as any); const customTransformers = host.getCustomTransformers(); @@ -107,13 +115,13 @@ test("LanguageServiceHost - getCustomTransformers -- undefined cases", () => { const config = { ...defaultConfig }; // no LS and no transformers cases - let host = new LanguageServiceHost(config, undefined as any, testDir); + let host = new LanguageServiceHost(config, undefined as any, testDir, defaultFilter); expect(host.getCustomTransformers()).toBeFalsy(); // no LS host.setLanguageService(true as any); expect(host.getCustomTransformers()).toBeFalsy(); // no transformers // empty transformers case - host = new LanguageServiceHost(config, [], testDir); + host = new LanguageServiceHost(config, [], testDir, defaultFilter); host.setLanguageService(true as any); expect(host.getCustomTransformers()).toBeFalsy(); // empty transformers }); diff --git a/src/host.ts b/src/host.ts index 61aa3ff5..decacf2e 100644 --- a/src/host.ts +++ b/src/host.ts @@ -11,7 +11,7 @@ export class LanguageServiceHost implements tsTypes.LanguageServiceHost private service?: tsTypes.LanguageService; private fileNames: Set; - constructor(private parsedConfig: tsTypes.ParsedCommandLine, private transformers: TransformerFactoryCreator[], private cwd: string) + constructor(private parsedConfig: tsTypes.ParsedCommandLine, private transformers: TransformerFactoryCreator[], private cwd: string, private filter: (id: string | unknown) => boolean) { this.fileNames = new Set(parsedConfig.fileNames); } @@ -40,6 +40,9 @@ export class LanguageServiceHost implements tsTypes.LanguageServiceHost this.snapshots[fileName] = snapshot; this.versions[fileName] = (this.versions[fileName] || 0) + 1; + if (this.filter(fileName)) + this.fileNames.add(fileName); + return snapshot; } diff --git a/src/index.ts b/src/index.ts index 3ccca119..502d32b3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -174,7 +174,7 @@ const typescript: PluginImpl = (options) => filter = createFilter(context, pluginOptions, parsedConfig); - servicesHost = new LanguageServiceHost(parsedConfig, pluginOptions.transformers, pluginOptions.cwd); + servicesHost = new LanguageServiceHost(parsedConfig, pluginOptions.transformers, pluginOptions.cwd, filter); service = tsModule.createLanguageService(servicesHost, documentRegistry); servicesHost.setLanguageService(service); From 67a97a9c71039f4767e0301ba5988edea32eb4c5 Mon Sep 17 00:00:00 2001 From: std4453 Date: Wed, 14 May 2025 18:06:28 +0800 Subject: [PATCH 4/4] fix(host,index): Force update of version under watch mode --- src/host.ts | 9 +++++++-- src/index.ts | 3 ++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/host.ts b/src/host.ts index decacf2e..d5b0d0b8 100644 --- a/src/host.ts +++ b/src/host.ts @@ -27,14 +27,19 @@ export class LanguageServiceHost implements tsTypes.LanguageServiceHost this.service = service; } - public setSnapshot(fileName: string, source: string): tsTypes.IScriptSnapshot + public setSnapshot(fileName: string, source: string, forcedUpdate = false): tsTypes.IScriptSnapshot { fileName = normalize(fileName); // don't update the snapshot if there are no changes const prevSnapshot = this.snapshots[fileName]; - if (prevSnapshot?.getText(0, prevSnapshot.getLength()) === source) + if (prevSnapshot?.getText(0, prevSnapshot.getLength()) === source) { + if (forcedUpdate) { + this.versions[fileName] = (this.versions[fileName] || 0) + 1; + } + return prevSnapshot; + } const snapshot = tsModule.ScriptSnapshot.fromString(source); this.snapshots[fileName] = snapshot; diff --git a/src/index.ts b/src/index.ts index 502d32b3..bbdcc891 100644 --- a/src/index.ts +++ b/src/index.ts @@ -245,7 +245,8 @@ const typescript: PluginImpl = (options) => if (!filter(id)) return undefined; - const snapshot = servicesHost.setSnapshot(id, code); + // force version update under watch mode to ensure recompile + const snapshot = servicesHost.setSnapshot(id, code, watchMode); // getting compiled file from cache or from ts const result = cache.getCompiled(id, snapshot, () =>