Skip to content

Commit

Permalink
perf: auto import cache (#2237)
Browse files Browse the repository at this point in the history
Add `typescript-auto-import-cache` to more cache auto imports more reliably
#2232
#2193
  • Loading branch information
jasonlyu123 authored Jan 11, 2024
1 parent 5c08ff6 commit e1eacce
Show file tree
Hide file tree
Showing 10 changed files with 454 additions and 159 deletions.
1 change: 1 addition & 0 deletions packages/language-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"svelte-preprocess": "~5.1.0",
"svelte2tsx": "workspace:~",
"typescript": "^5.3.2",
"typescript-auto-import-cache": "^0.3.2",
"vscode-css-languageservice": "~6.2.10",
"vscode-html-languageservice": "~5.1.1",
"vscode-languageserver": "8.0.2",
Expand Down
5 changes: 5 additions & 0 deletions packages/language-server/src/ls-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,11 @@ export class LSConfigManager {
config.suggest?.objectLiteralMethodSnippets?.enabled ?? true,
preferTypeOnlyAutoImports: config.preferences?.preferTypeOnlyAutoImports,

// Although we don't support incompletion cache.
// But this will make ts resolve the module specifier more aggressively
// Which also makes the completion label detail show up in more cases
allowIncompleteCompletions: true,

includeInlayEnumMemberValueHints: inlayHints?.enumMemberValues?.enabled,
includeInlayFunctionLikeReturnTypeHints: inlayHints?.functionLikeReturnTypes?.enabled,
includeInlayParameterNameHints: inlayHints?.parameterNames?.enabled,
Expand Down
130 changes: 112 additions & 18 deletions packages/language-server/src/plugins/typescript/LSAndTSDocResolver.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { dirname } from 'path';
import { dirname, join } from 'path';
import ts from 'typescript';
import { TextDocumentContentChangeEvent } from 'vscode-languageserver';
import { Document, DocumentManager } from '../../lib/documents';
Expand All @@ -19,8 +19,10 @@ import {
LanguageServiceContainer,
LanguageServiceDocumentContext
} from './service';
import { createProjectService } from './serviceCache';
import { GlobalSnapshotsManager, SnapshotManager } from './SnapshotManager';
import { isSubPath } from './utils';
import { FileMap } from '../../lib/documents/fileCollection';

interface LSAndTSDocResolverOptions {
notifyExceedSizeLimit?: () => void;
Expand Down Expand Up @@ -71,6 +73,40 @@ export class LSAndTSDocResolver {
this.getCanonicalFileName = createGetCanonicalFileName(
(options?.tsSystem ?? ts.sys).useCaseSensitiveFileNames
);

this.tsSystem = this.wrapWithPackageJsonMonitoring(this.options?.tsSystem ?? ts.sys);
this.globalSnapshotsManager = new GlobalSnapshotsManager(this.tsSystem);
this.userPreferencesAccessor = { preferences: this.getTsUserPreferences() };
const projectService = createProjectService(this.tsSystem, this.userPreferencesAccessor);

configManager.onChange(() => {
const newPreferences = this.getTsUserPreferences();
const autoImportConfigChanged =
newPreferences.includePackageJsonAutoImports !==
this.userPreferencesAccessor.preferences.includePackageJsonAutoImports;

this.userPreferencesAccessor.preferences = newPreferences;

if (autoImportConfigChanged) {
forAllServices((service) => {
service.onAutoImportProviderSettingsChanged();
});
}
});

this.watchers = new FileMap(this.tsSystem.useCaseSensitiveFileNames);
this.lsDocumentContext = {
ambientTypesSource: this.options?.isSvelteCheck ? 'svelte-check' : 'svelte2tsx',
createDocument: this.createDocument,
transformOnTemplateError: !this.options?.isSvelteCheck,
globalSnapshotsManager: this.globalSnapshotsManager,
notifyExceedSizeLimit: this.options?.notifyExceedSizeLimit,
extendedConfigCache: this.extendedConfigCache,
onProjectReloaded: this.options?.onProjectReloaded,
watchTsConfig: !!this.options?.watch,
tsSystem: this.tsSystem,
projectService: projectService
};
}

/**
Expand All @@ -89,26 +125,15 @@ export class LSAndTSDocResolver {
return document;
};

private globalSnapshotsManager = new GlobalSnapshotsManager(
this.lsDocumentContext.tsSystem,
/* watchPackageJson */ !!this.options?.watch
);
private tsSystem: ts.System;
private globalSnapshotsManager: GlobalSnapshotsManager;
private extendedConfigCache = new Map<string, ts.ExtendedConfigCacheEntry>();
private getCanonicalFileName: GetCanonicalFileName;

private get lsDocumentContext(): LanguageServiceDocumentContext {
return {
ambientTypesSource: this.options?.isSvelteCheck ? 'svelte-check' : 'svelte2tsx',
createDocument: this.createDocument,
transformOnTemplateError: !this.options?.isSvelteCheck,
globalSnapshotsManager: this.globalSnapshotsManager,
notifyExceedSizeLimit: this.options?.notifyExceedSizeLimit,
extendedConfigCache: this.extendedConfigCache,
onProjectReloaded: this.options?.onProjectReloaded,
watchTsConfig: !!this.options?.watch,
tsSystem: this.options?.tsSystem ?? ts.sys
};
}
private userPreferencesAccessor: { preferences: ts.UserPreferences };
private readonly watchers: FileMap<ts.FileWatcher>;

private lsDocumentContext: LanguageServiceDocumentContext;

async getLSForPath(path: string) {
return (await this.getTSService(path)).getService();
Expand Down Expand Up @@ -251,4 +276,73 @@ export class LSAndTSDocResolver {
nearestWorkspaceUri ? urlToPath(nearestWorkspaceUri) : null
);
}

private getTsUserPreferences() {
return this.configManager.getTsUserPreferences('typescript', null);
}

private wrapWithPackageJsonMonitoring(sys: ts.System): ts.System {
if (!sys.watchFile || !this.options?.watch) {
return sys;
}

const watchFile = sys.watchFile;
return {
...sys,
readFile: (path, encoding) => {
if (path.endsWith('package.json') && !this.watchers.has(path)) {
this.watchers.set(
path,
watchFile(path, this.onPackageJsonWatchChange.bind(this), 3_000)
);
}

return sys.readFile(path, encoding);
}
};
}

private onPackageJsonWatchChange(path: string, onWatchChange: ts.FileWatcherEventKind) {
const dir = dirname(path);
const projectService = this.lsDocumentContext.projectService;
const packageJsonCache = projectService?.packageJsonCache;
const normalizedPath = projectService?.toPath(path);

if (onWatchChange === ts.FileWatcherEventKind.Deleted) {
this.watchers.get(path)?.close();
this.watchers.delete(path);
packageJsonCache?.delete(normalizedPath);
} else {
packageJsonCache?.addOrUpdate(normalizedPath);
}

forAllServices((service) => {
service.onPackageJsonChange(path);
});
if (!path.includes('node_modules')) {
return;
}

setTimeout(() => {
this.updateSnapshotsInDirectory(dir);
const realPath =
this.tsSystem.realpath &&
this.getCanonicalFileName(normalizePath(this.tsSystem.realpath?.(dir)));

// pnpm
if (realPath && realPath !== dir) {
this.updateSnapshotsInDirectory(realPath);
const realPkgPath = join(realPath, 'package.json');
forAllServices((service) => {
service.onPackageJsonChange(realPkgPath);
});
}
}, 500);
}

private updateSnapshotsInDirectory(dir: string) {
this.globalSnapshotsManager.getByPrefix(dir).forEach((snapshot) => {
this.globalSnapshotsManager.updateTsOrJsFile(snapshot.filePath);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { TextDocumentContentChangeEvent } from 'vscode-languageserver';
import { createGetCanonicalFileName, GetCanonicalFileName, normalizePath } from '../../utils';
import { EventEmitter } from 'events';
import { FileMap } from '../../lib/documents/fileCollection';
import { dirname } from 'path';

type SnapshotChangeHandler = (fileName: string, newDocument: DocumentSnapshot | undefined) => void;

Expand All @@ -18,20 +17,10 @@ export class GlobalSnapshotsManager {
private emitter = new EventEmitter();
private documents: FileMap<DocumentSnapshot>;
private getCanonicalFileName: GetCanonicalFileName;
private packageJsonCache: PackageJsonCache;

constructor(
private readonly tsSystem: ts.System,
watchPackageJson = false
) {
constructor(private readonly tsSystem: ts.System) {
this.documents = new FileMap(tsSystem.useCaseSensitiveFileNames);
this.getCanonicalFileName = createGetCanonicalFileName(tsSystem.useCaseSensitiveFileNames);
this.packageJsonCache = new PackageJsonCache(
tsSystem,
watchPackageJson,
this.getCanonicalFileName,
this.updateSnapshotsInDirectory.bind(this)
);
}

get(fileName: string) {
Expand Down Expand Up @@ -94,16 +83,6 @@ export class GlobalSnapshotsManager {
removeChangeListener(listener: SnapshotChangeHandler) {
this.emitter.off('change', listener);
}

getPackageJson(path: string) {
return this.packageJsonCache.getPackageJson(path);
}

private updateSnapshotsInDirectory(dir: string) {
this.getByPrefix(dir).forEach((snapshot) => {
this.updateTsOrJsFile(snapshot.filePath);
});
}
}

export interface TsFilesSpec {
Expand Down Expand Up @@ -267,76 +246,3 @@ export class SnapshotManager {
}

export const ignoredBuildDirectories = ['__sapper__', '.svelte-kit'];

class PackageJsonCache {
constructor(
private readonly tsSystem: ts.System,
private readonly watchPackageJson: boolean,
private readonly getCanonicalFileName: GetCanonicalFileName,
private readonly updateSnapshotsInDirectory: (directory: string) => void
) {
this.watchers = new FileMap(tsSystem.useCaseSensitiveFileNames);
}

private readonly watchers: FileMap<ts.FileWatcher>;

private packageJsonCache = new FileMap<
{ text: string; modifiedTime: number | undefined } | undefined
>();

getPackageJson(path: string) {
if (!this.packageJsonCache.has(path)) {
this.packageJsonCache.set(path, this.initWatcherAndRead(path));
}

return this.packageJsonCache.get(path);
}

private initWatcherAndRead(path: string) {
if (this.watchPackageJson) {
this.tsSystem.watchFile?.(path, this.onPackageJsonWatchChange.bind(this), 3_000);
}
const exist = this.tsSystem.fileExists(path);

if (!exist) {
return undefined;
}

return this.readPackageJson(path);
}

private readPackageJson(path: string) {
return {
text: this.tsSystem.readFile(path) ?? '',
modifiedTime: this.tsSystem.getModifiedTime?.(path)?.valueOf()
};
}

private onPackageJsonWatchChange(path: string, onWatchChange: ts.FileWatcherEventKind) {
const dir = dirname(path);

if (onWatchChange === ts.FileWatcherEventKind.Deleted) {
this.packageJsonCache.delete(path);
this.watchers.get(path)?.close();
this.watchers.delete(path);
} else {
this.packageJsonCache.set(path, this.readPackageJson(path));
}

if (!path.includes('node_modules')) {
return;
}

setTimeout(() => {
this.updateSnapshotsInDirectory(dir);
const realPath =
this.tsSystem.realpath &&
this.getCanonicalFileName(normalizePath(this.tsSystem.realpath?.(dir)));

// pnpm
if (realPath && realPath !== dir) {
this.updateSnapshotsInDirectory(realPath);
}
}, 500);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,8 @@ export function createSvelteModuleLoader(
resolveModuleNames,
resolveTypeReferenceDirectiveReferences,
mightHaveInvalidatedResolutions,
clearPendingInvalidations
clearPendingInvalidations,
getModuleResolutionCache: () => tsModuleCache
};

function resolveModuleNames(
Expand Down
Loading

0 comments on commit e1eacce

Please sign in to comment.