-
-
Notifications
You must be signed in to change notification settings - Fork 223
(perf) typescript language service size limit #1194
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
018e80c
dbe8524
49832db
5053a5a
4b5a15d
b6b0eec
03787c8
ac36bfc
466c939
eddb949
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,7 +13,7 @@ import { | |
| ignoredBuildDirectories, | ||
| SnapshotManager | ||
| } from './SnapshotManager'; | ||
| import { ensureRealSvelteFilePath, findTsConfigPath } from './utils'; | ||
| import { ensureRealSvelteFilePath, findTsConfigPath, hasTsExtensions } from './utils'; | ||
|
|
||
| export interface LanguageServiceContainer { | ||
| readonly tsconfigPath: string; | ||
|
|
@@ -39,13 +39,16 @@ export interface LanguageServiceContainer { | |
| fileBelongsToProject(filePath: string): boolean; | ||
| } | ||
|
|
||
| const maxProgramSizeForNonTsFiles = 20 * 1024 * 1024; // 20 MB | ||
| const services = new Map<string, Promise<LanguageServiceContainer>>(); | ||
| const serviceSizeMap: Map<string, number> = new Map(); | ||
|
|
||
| export interface LanguageServiceDocumentContext { | ||
| ambientTypesSource: string; | ||
| transformOnTemplateError: boolean; | ||
| createDocument: (fileName: string, content: string) => Document; | ||
| globalSnapshotsManager: GlobalSnapshotsManager; | ||
| notifyExceedSizeLimit: (() => void) | undefined; | ||
| } | ||
|
|
||
| export async function getService( | ||
|
|
@@ -123,12 +126,14 @@ async function createLanguageService( | |
| './svelte-native-jsx.d.ts' | ||
| ].map((f) => ts.sys.resolvePath(resolve(svelteTsPath, f))); | ||
|
|
||
| let languageServiceReducedMode = false; | ||
|
|
||
| const host: ts.LanguageServiceHost = { | ||
| getCompilationSettings: () => compilerOptions, | ||
| getScriptFileNames: () => | ||
| Array.from( | ||
| new Set([ | ||
| ...snapshotManager.getProjectFileNames(), | ||
| ...(languageServiceReducedMode ? [] : snapshotManager.getProjectFileNames()), | ||
| ...snapshotManager.getFileNames(), | ||
| ...svelteTsxFiles | ||
| ]) | ||
|
|
@@ -150,6 +155,8 @@ async function createLanguageService( | |
| transformOnTemplateError: docContext.transformOnTemplateError | ||
| }; | ||
|
|
||
| reduceLanguageServiceCapabilityIfFileSizeTooBig(); | ||
|
|
||
| return { | ||
| tsconfigPath, | ||
| compilerOptions, | ||
|
|
@@ -232,7 +239,15 @@ async function createLanguageService( | |
| } | ||
|
|
||
| function updateProjectFiles(): void { | ||
| const projectFileCountBefore = snapshotManager.getProjectFileNames().length; | ||
| snapshotManager.updateProjectFiles(); | ||
| const projectFileCountAfter = snapshotManager.getProjectFileNames().length; | ||
|
|
||
| if (projectFileCountAfter <= projectFileCountBefore) { | ||
| return; | ||
| } | ||
|
|
||
| reduceLanguageServiceCapabilityIfFileSizeTooBig(); | ||
| } | ||
|
|
||
| function hasFile(filePath: string): boolean { | ||
|
|
@@ -350,4 +365,68 @@ async function createLanguageService( | |
| function getDefaultExclude() { | ||
| return ['node_modules', ...ignoredBuildDirectories]; | ||
| } | ||
|
|
||
| /** | ||
| * Disable usage of project files. | ||
| * running language service in a reduced mode for | ||
| * large projects with improperly excluded tsconfig. | ||
| */ | ||
| function reduceLanguageServiceCapabilityIfFileSizeTooBig() { | ||
| if (exceedsTotalSizeLimitForNonTsFiles(compilerOptions, tsconfigPath, snapshotManager)) { | ||
| languageService.cleanupSemanticCache(); | ||
| languageServiceReducedMode = true; | ||
| docContext.notifyExceedSizeLimit?.(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * adopted from https://github.com/microsoft/TypeScript/blob/3c8e45b304b8572094c5d7fbb9cd768dbf6417c0/src/server/editorServices.ts#L1955 | ||
| */ | ||
| function exceedsTotalSizeLimitForNonTsFiles( | ||
| compilerOptions: ts.CompilerOptions, | ||
| tsconfigPath: string, | ||
| snapshotManager: SnapshotManager | ||
| ): boolean { | ||
| if (compilerOptions.disableSizeLimit) { | ||
| return false; | ||
| } | ||
|
|
||
| let availableSpace = maxProgramSizeForNonTsFiles; | ||
| serviceSizeMap.set(tsconfigPath, 0); | ||
|
|
||
| serviceSizeMap.forEach((size) => { | ||
| availableSpace -= size; | ||
| }); | ||
|
|
||
| let totalNonTsFileSize = 0; | ||
|
|
||
| const fileNames = snapshotManager.getProjectFileNames(); | ||
| for (const fileName of fileNames) { | ||
| if (hasTsExtensions(fileName)) { | ||
| continue; | ||
| } | ||
|
|
||
| totalNonTsFileSize += ts.sys.getFileSize?.(fileName) ?? 0; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Out of curiosity: Is this performance heavy to check that? How often is this called? My fear is that if this results in many IO operations, that it slows down the intellisense for people.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Currently, it only runs on startup and when there're new .ts or .js files added. With the file-count-check, it should not be very often. It's 10-20ms on my SSD with 300-500 project files workspaces, and 20-30ms on a hard drive. So I guess it shouldn't be too noticeable to most people. |
||
|
|
||
| if (totalNonTsFileSize > availableSpace) { | ||
| const top5LargestFiles = fileNames | ||
| .filter((name) => !hasTsExtensions(name)) | ||
| .map((name) => ({ name, size: ts.sys.getFileSize?.(name) ?? 0 })) | ||
| .sort((a, b) => b.size - a.size) | ||
| .slice(0, 5); | ||
|
|
||
| Logger.log( | ||
| `Non TS file size exceeded limit (${totalNonTsFileSize}). ` + | ||
| `Largest files: ${top5LargestFiles | ||
| .map((file) => `${file.name}:${file.size}`) | ||
| .join(', ')}` | ||
| ); | ||
|
|
||
| return true; | ||
| } | ||
| } | ||
|
|
||
| serviceSizeMap.set(tsconfigPath, totalNonTsFileSize); | ||
| return false; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍