Skip to content

Commit 0f97620

Browse files
authored
Merge pull request #25884 from Microsoft/optimizeOpenExternalProject
Delay load configured project referenced from external project when opening it
2 parents 3e201e7 + f67bdd4 commit 0f97620

File tree

7 files changed

+184
-168
lines changed

7 files changed

+184
-168
lines changed

src/server/editorServices.ts

Lines changed: 113 additions & 103 deletions
Large diffs are not rendered by default.

src/server/project.ts

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@ namespace ts.server {
149149
*/
150150
private projectStateVersion = 0;
151151

152+
protected isInitialLoadPending: () => boolean = returnFalse;
153+
152154
/*@internal*/
153155
dirty = false;
154156

@@ -1033,7 +1035,10 @@ namespace ts.server {
10331035

10341036
/* @internal */
10351037
getChangesSinceVersion(lastKnownVersion?: number): ProjectFilesWithTSDiagnostics {
1036-
updateProjectIfDirty(this);
1038+
// Update the graph only if initial configured project load is not pending
1039+
if (!this.isInitialLoadPending()) {
1040+
updateProjectIfDirty(this);
1041+
}
10371042

10381043
const info: protocol.ProjectVersionInfo = {
10391044
projectName: this.getProjectName(),
@@ -1091,9 +1096,8 @@ namespace ts.server {
10911096
this.rootFilesMap.delete(info.path);
10921097
}
10931098

1094-
protected enableGlobalPlugins() {
1099+
protected enableGlobalPlugins(options: CompilerOptions) {
10951100
const host = this.projectService.host;
1096-
const options = this.getCompilationSettings();
10971101

10981102
if (!host.require) {
10991103
this.projectService.logger.info("Plugins were requested but not running in environment that supports 'require'. Nothing will be loaded");
@@ -1244,7 +1248,7 @@ namespace ts.server {
12441248
if (!projectRootPath && !projectService.useSingleInferredProject) {
12451249
this.canonicalCurrentDirectory = projectService.toCanonicalFileName(this.currentDirectory);
12461250
}
1247-
this.enableGlobalPlugins();
1251+
this.enableGlobalPlugins(this.getCompilerOptions());
12481252
}
12491253

12501254
addRoot(info: ScriptInfo) {
@@ -1316,46 +1320,53 @@ namespace ts.server {
13161320

13171321
private projectErrors: Diagnostic[] | undefined;
13181322

1323+
private projectReferences: ReadonlyArray<ProjectReference> | undefined;
1324+
1325+
/*@internal*/
1326+
projectOptions?: ProjectOptions | true;
1327+
1328+
protected isInitialLoadPending: () => boolean = returnTrue;
1329+
13191330
/*@internal*/
13201331
constructor(configFileName: NormalizedPath,
13211332
projectService: ProjectService,
13221333
documentRegistry: DocumentRegistry,
1323-
hasExplicitListOfFiles: boolean,
1324-
compilerOptions: CompilerOptions,
1325-
lastFileExceededProgramSize: string | undefined,
1326-
public compileOnSaveEnabled: boolean,
1327-
cachedDirectoryStructureHost: CachedDirectoryStructureHost,
1328-
private projectReferences: ReadonlyArray<ProjectReference> | undefined) {
1334+
cachedDirectoryStructureHost: CachedDirectoryStructureHost) {
13291335
super(configFileName,
13301336
ProjectKind.Configured,
13311337
projectService,
13321338
documentRegistry,
1333-
hasExplicitListOfFiles,
1334-
lastFileExceededProgramSize,
1335-
compilerOptions,
1336-
compileOnSaveEnabled,
1339+
/*hasExplicitListOfFiles*/ false,
1340+
/*lastFileExceededProgramSize*/ undefined,
1341+
/*compilerOptions*/ {},
1342+
/*compileOnSaveEnabled*/ false,
13371343
cachedDirectoryStructureHost,
13381344
getDirectoryPath(configFileName));
13391345
this.canonicalConfigFilePath = asNormalizedPath(projectService.toCanonicalFileName(configFileName));
1340-
this.enablePlugins();
13411346
}
13421347

13431348
/**
13441349
* If the project has reload from disk pending, it reloads (and then updates graph as part of that) instead of just updating the graph
13451350
* @returns: true if set of files in the project stays the same and false - otherwise.
13461351
*/
13471352
updateGraph(): boolean {
1353+
this.isInitialLoadPending = returnFalse;
13481354
const reloadLevel = this.pendingReload;
13491355
this.pendingReload = ConfigFileProgramReloadLevel.None;
1356+
let result: boolean;
13501357
switch (reloadLevel) {
13511358
case ConfigFileProgramReloadLevel.Partial:
1352-
return this.projectService.reloadFileNamesOfConfiguredProject(this);
1359+
result = this.projectService.reloadFileNamesOfConfiguredProject(this);
1360+
break;
13531361
case ConfigFileProgramReloadLevel.Full:
13541362
this.projectService.reloadConfiguredProject(this);
1355-
return true;
1363+
result = true;
1364+
break;
13561365
default:
1357-
return super.updateGraph();
1366+
result = super.updateGraph();
13581367
}
1368+
this.projectService.sendProjectTelemetry(this);
1369+
return result;
13591370
}
13601371

13611372
/*@internal*/
@@ -1382,8 +1393,12 @@ namespace ts.server {
13821393
}
13831394

13841395
enablePlugins() {
1396+
this.enablePluginsWithOptions(this.getCompilerOptions());
1397+
}
1398+
1399+
/*@internal*/
1400+
enablePluginsWithOptions(options: CompilerOptions) {
13851401
const host = this.projectService.host;
1386-
const options = this.getCompilationSettings();
13871402

13881403
if (!host.require) {
13891404
this.projectService.logger.info("Plugins were requested but not running in environment that supports 'require'. Nothing will be loaded");
@@ -1407,7 +1422,7 @@ namespace ts.server {
14071422
}
14081423
}
14091424

1410-
this.enableGlobalPlugins();
1425+
this.enableGlobalPlugins(options);
14111426
}
14121427

14131428
/**
@@ -1547,6 +1562,12 @@ namespace ts.server {
15471562
getDirectoryPath(projectFilePath || normalizeSlashes(externalProjectName)));
15481563
}
15491564

1565+
updateGraph() {
1566+
const result = super.updateGraph();
1567+
this.projectService.sendProjectTelemetry(this);
1568+
return result;
1569+
}
1570+
15501571
getExcludedFiles() {
15511572
return this.excludedFiles;
15521573
}

src/server/utilities.ts

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ namespace ts.server {
120120
};
121121
}
122122

123+
/*@internal*/
123124
export interface ProjectOptions {
124125
configHasExtendsProperty: boolean;
125126
/**
@@ -128,16 +129,6 @@ namespace ts.server {
128129
configHasFilesProperty: boolean;
129130
configHasIncludeProperty: boolean;
130131
configHasExcludeProperty: boolean;
131-
132-
projectReferences: ReadonlyArray<ProjectReference> | undefined;
133-
/**
134-
* these fields can be present in the project file
135-
*/
136-
files?: string[];
137-
wildcardDirectories?: Map<WatchDirectoryFlags>;
138-
compilerOptions?: CompilerOptions;
139-
typeAcquisition?: TypeAcquisition;
140-
compileOnSave?: boolean;
141132
}
142133

143134
export function isInferredProjectName(name: string) {

src/testRunner/unittests/telemetry.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ namespace ts.projectSystem {
6868
}, "/hunter2/foo.csproj");
6969

7070
// Also test that opening an external project only sends an event once.
71+
et.service.closeClientFile(file1.path);
7172

7273
et.service.closeExternalProject(projectFileName);
7374
checkNumberOfProjects(et.service, { externalProjects: 0 });
@@ -82,6 +83,7 @@ namespace ts.projectSystem {
8283
projectFileName,
8384
});
8485
checkNumberOfProjects(et.service, { externalProjects: 1 });
86+
et.service.openClientFile(file1.path); // Only on file open the project will be updated
8587
}
8688
});
8789

src/testRunner/unittests/tsserverProjectSystem.ts

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -663,12 +663,15 @@ namespace ts.projectSystem {
663663
options: {}
664664
});
665665
service.checkNumberOfProjects({ configuredProjects: 1 });
666-
checkProjectActualFiles(configuredProjectAt(service, 0), [upperCaseConfigFilePath]);
666+
const project = service.configuredProjects.get(config.path)!;
667+
assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.Full); // External project referenced configured project pending to be reloaded
668+
checkProjectActualFiles(project, emptyArray);
667669

668670
service.openClientFile(f1.path);
669671
service.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: 1 });
670672

671-
checkProjectActualFiles(configuredProjectAt(service, 0), [upperCaseConfigFilePath]);
673+
assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.None); // External project referenced configured project is updated
674+
checkProjectActualFiles(project, [upperCaseConfigFilePath]);
672675
checkProjectActualFiles(service.inferredProjects[0], [f1.path]);
673676
});
674677

@@ -778,7 +781,7 @@ namespace ts.projectSystem {
778781

779782
// Add a tsconfig file
780783
host.reloadFS(filesWithConfig);
781-
host.checkTimeoutQueueLengthAndRun(1);
784+
host.checkTimeoutQueueLengthAndRun(2); // load configured project from disk + ensureProjectsForOpenFiles
782785

783786
projectService.checkNumberOfProjects({ inferredProjects: 2, configuredProjects: 1 });
784787
assert.isTrue(projectService.inferredProjects[0].isOrphan());
@@ -1229,7 +1232,7 @@ namespace ts.projectSystem {
12291232

12301233

12311234
host.reloadFS([file1, configFile, file2, file3, libFile]);
1232-
host.checkTimeoutQueueLengthAndRun(1);
1235+
host.checkTimeoutQueueLengthAndRun(2); // load configured project from disk + ensureProjectsForOpenFiles
12331236
checkNumberOfConfiguredProjects(projectService, 1);
12341237
checkNumberOfInferredProjects(projectService, 1);
12351238
checkProjectActualFiles(projectService.inferredProjects[0], [file2.path, file3.path, libFile.path]);
@@ -1893,7 +1896,7 @@ namespace ts.projectSystem {
18931896
checkProjectActualFiles(projectService.inferredProjects[1], [file3.path]);
18941897

18951898
host.reloadFS([file1, file2, file3, configFile]);
1896-
host.checkTimeoutQueueLengthAndRun(1);
1899+
host.checkTimeoutQueueLengthAndRun(2); // load configured project from disk + ensureProjectsForOpenFiles
18971900
checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 2 });
18981901
checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, file3.path, configFile.path]);
18991902
assert.isTrue(projectService.inferredProjects[0].isOrphan());
@@ -2973,10 +2976,7 @@ namespace ts.projectSystem {
29732976
checkNumberOfProjects(projectService, { configuredProjects: 1, externalProjects: 0, inferredProjects: 0 });
29742977

29752978
const configProject = configuredProjectAt(projectService, 0);
2976-
checkProjectActualFiles(configProject, [libFile.path, configFile.path]);
2977-
2978-
const diagnostics = configProject.getAllProjectErrors();
2979-
assert.equal(diagnostics[0].code, Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code);
2979+
checkProjectActualFiles(configProject, []); // Since no files opened from this project, its not loaded
29802980

29812981
host.reloadFS([libFile, site]);
29822982
host.checkTimeoutQueueLengthAndRun(1);
@@ -3334,6 +3334,9 @@ namespace ts.projectSystem {
33343334
checkNumberOfProjects(projectService, { configuredProjects: 1 });
33353335

33363336
const configuredProject = configuredProjectAt(projectService, 0);
3337+
// configured project is just created and not yet loaded
3338+
checkProjectActualFiles(configuredProject, emptyArray);
3339+
projectService.ensureInferredProjectsUpToDate_TestOnly();
33373340
checkProjectActualFiles(configuredProject, [file1.path, tsconfig.path]);
33383341

33393342
// Allow allowNonTsExtensions will be set to true for deferred extensions.
@@ -3975,6 +3978,8 @@ namespace ts.projectSystem {
39753978
options: {}
39763979
});
39773980
projectService.checkNumberOfProjects({ configuredProjects: 1 });
3981+
checkProjectActualFiles(configuredProjectAt(projectService, 0), emptyArray); // Configured project created but not loaded till actually needed
3982+
projectService.ensureInferredProjectsUpToDate_TestOnly();
39783983
checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, tsconfig.path]);
39793984

39803985
// rename tsconfig.json back to lib.ts
@@ -4032,6 +4037,9 @@ namespace ts.projectSystem {
40324037
options: {}
40334038
});
40344039
projectService.checkNumberOfProjects({ configuredProjects: 2 });
4040+
checkProjectActualFiles(configuredProjectAt(projectService, 0), emptyArray); // Configured project created but not loaded till actually needed
4041+
checkProjectActualFiles(configuredProjectAt(projectService, 1), emptyArray); // Configured project created but not loaded till actually needed
4042+
projectService.ensureInferredProjectsUpToDate_TestOnly();
40354043
checkProjectActualFiles(configuredProjectAt(projectService, 0), [cLib.path, cTsconfig.path]);
40364044
checkProjectActualFiles(configuredProjectAt(projectService, 1), [dLib.path, dTsconfig.path]);
40374045

@@ -4063,6 +4071,9 @@ namespace ts.projectSystem {
40634071
options: {}
40644072
});
40654073
projectService.checkNumberOfProjects({ configuredProjects: 2 });
4074+
checkProjectActualFiles(configuredProjectAt(projectService, 0), emptyArray); // Configured project created but not loaded till actually needed
4075+
checkProjectActualFiles(configuredProjectAt(projectService, 1), emptyArray); // Configured project created but not loaded till actually needed
4076+
projectService.ensureInferredProjectsUpToDate_TestOnly();
40664077
checkProjectActualFiles(configuredProjectAt(projectService, 0), [cLib.path, cTsconfig.path]);
40674078
checkProjectActualFiles(configuredProjectAt(projectService, 1), [dLib.path, dTsconfig.path]);
40684079

src/testRunner/unittests/typingsInstaller.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,6 @@ namespace ts.projectSystem {
430430

431431
const p = projectService.externalProjects[0];
432432
projectService.checkNumberOfProjects({ externalProjects: 1 });
433-
434433
checkProjectActualFiles(p, [jqueryJs.path]);
435434

436435
installer.checkPendingCommands(/*expectedCount*/ 0);

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5751,24 +5751,6 @@ declare namespace ts.server {
57515751
remove(path: NormalizedPath): void;
57525752
}
57535753
function createNormalizedPathMap<T>(): NormalizedPathMap<T>;
5754-
interface ProjectOptions {
5755-
configHasExtendsProperty: boolean;
5756-
/**
5757-
* true if config file explicitly listed files
5758-
*/
5759-
configHasFilesProperty: boolean;
5760-
configHasIncludeProperty: boolean;
5761-
configHasExcludeProperty: boolean;
5762-
projectReferences: ReadonlyArray<ProjectReference> | undefined;
5763-
/**
5764-
* these fields can be present in the project file
5765-
*/
5766-
files?: string[];
5767-
wildcardDirectories?: Map<WatchDirectoryFlags>;
5768-
compilerOptions?: CompilerOptions;
5769-
typeAcquisition?: TypeAcquisition;
5770-
compileOnSave?: boolean;
5771-
}
57725754
function isInferredProjectName(name: string): boolean;
57735755
function makeInferredProjectName(counter: number): string;
57745756
function createSortedArray<T>(): SortedArray<T>;
@@ -8238,6 +8220,7 @@ declare namespace ts.server {
82388220
* This property is different from projectStructureVersion since in most cases edits don't affect set of files in the project
82398221
*/
82408222
private projectStateVersion;
8223+
protected isInitialLoadPending: () => boolean;
82418224
private readonly cancellationToken;
82428225
isNonTsProject(): boolean;
82438226
isJsOnlyProject(): boolean;
@@ -8322,7 +8305,7 @@ declare namespace ts.server {
83228305
filesToString(writeProjectFileNames: boolean): string;
83238306
setCompilerOptions(compilerOptions: CompilerOptions): void;
83248307
protected removeRoot(info: ScriptInfo): void;
8325-
protected enableGlobalPlugins(): void;
8308+
protected enableGlobalPlugins(options: CompilerOptions): void;
83268309
protected enablePlugin(pluginConfigEntry: PluginImport, searchPaths: string[]): void;
83278310
/** Starts a new check for diagnostics. Call this if some file has updated that would cause diagnostics to be changed. */
83288311
refreshDiagnostics(): void;
@@ -8351,14 +8334,14 @@ declare namespace ts.server {
83518334
* Otherwise it will create an InferredProject.
83528335
*/
83538336
class ConfiguredProject extends Project {
8354-
compileOnSaveEnabled: boolean;
8355-
private projectReferences;
83568337
private typeAcquisition;
83578338
private directoriesWatchedForWildcards;
83588339
readonly canonicalConfigFilePath: NormalizedPath;
83598340
/** Ref count to the project when opened from external project */
83608341
private externalProjectRefCount;
83618342
private projectErrors;
8343+
private projectReferences;
8344+
protected isInitialLoadPending: () => boolean;
83628345
/**
83638346
* If the project has reload from disk pending, it reloads (and then updates graph as part of that) instead of just updating the graph
83648347
* @returns: true if set of files in the project stays the same and false - otherwise.
@@ -8391,6 +8374,7 @@ declare namespace ts.server {
83918374
compileOnSaveEnabled: boolean;
83928375
excludedFiles: ReadonlyArray<NormalizedPath>;
83938376
private typeAcquisition;
8377+
updateGraph(): boolean;
83948378
getExcludedFiles(): ReadonlyArray<NormalizedPath>;
83958379
getTypeAcquisition(): TypeAcquisition;
83968380
setTypeAcquisition(newTypeAcquisition: TypeAcquisition): void;
@@ -8692,15 +8676,13 @@ declare namespace ts.server {
86928676
private findConfiguredProjectByProjectName;
86938677
private getConfiguredProjectByCanonicalConfigFilePath;
86948678
private findExternalProjectByProjectName;
8695-
private convertConfigFileContentToProjectOptions;
86968679
/** Get a filename if the language service exceeds the maximum allowed program size; otherwise returns undefined. */
86978680
private getFilenameForExceededTotalSizeLimitForNonTsFiles;
86988681
private createExternalProject;
8699-
private sendProjectTelemetry;
8700-
private addFilesToNonInferredProjectAndUpdateGraph;
8682+
private addFilesToNonInferredProject;
87018683
private createConfiguredProject;
87028684
private updateNonInferredProjectFiles;
8703-
private updateNonInferredProject;
8685+
private updateRootAndOptionsOfNonInferredProject;
87048686
private sendConfigFileDiagEvent;
87058687
private getOrCreateInferredProjectForProjectRootPathIfEnabled;
87068688
private getOrCreateSingleInferredProjectIfEnabled;

0 commit comments

Comments
 (0)