Skip to content

Commit 8063299

Browse files
committed
Share watches with closed script info
1 parent 0e722f0 commit 8063299

File tree

3 files changed

+88
-44
lines changed

3 files changed

+88
-44
lines changed

src/server/editorServices.ts

Lines changed: 81 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -589,8 +589,11 @@ namespace ts.server {
589589
);
590590
}
591591

592-
interface ScriptInfoInNodeModulesWatcher extends FileWatcher {
593-
refCount: number;
592+
interface NodeModulesWatcher extends FileWatcher {
593+
/** How many watchers of this directory were for closed ScriptInfo */
594+
refreshScriptInfoRefCount: number;
595+
/** List of project names whose module specifier cache should be cleared when package.jsons change */
596+
affectedModuleSpecifierCacheProjects: Set<string>;
594597
}
595598

596599
function getDetailWatchInfo(watchType: WatchType, project: Project | NormalizedPath | undefined) {
@@ -671,7 +674,7 @@ namespace ts.server {
671674
*/
672675
/*@internal*/
673676
readonly filenameToScriptInfo = new Map<string, ScriptInfo>();
674-
private readonly scriptInfoInNodeModulesWatchers = new Map<string, ScriptInfoInNodeModulesWatcher>();
677+
private readonly nodeModulesWatchers = new Map<string, NodeModulesWatcher>();
675678
/**
676679
* Contains all the deleted script info's version information so that
677680
* it does not reset when creating script info again
@@ -2617,57 +2620,102 @@ namespace ts.server {
26172620
}
26182621
}
26192622

2620-
private watchClosedScriptInfoInNodeModules(dir: Path): ScriptInfoInNodeModulesWatcher {
2621-
// Watch only directory
2622-
const existing = this.scriptInfoInNodeModulesWatchers.get(dir);
2623-
if (existing) {
2624-
existing.refCount++;
2625-
return existing;
2626-
}
2627-
2628-
const watchDir = dir + "/node_modules" as Path;
2623+
private createNodeModulesWatcher(dir: Path, affectedModuleSpecifierCacheProject?: Project) {
26292624
const watcher = this.watchFactory.watchDirectory(
2630-
watchDir,
2625+
dir,
26312626
fileOrDirectory => {
26322627
const fileOrDirectoryPath = removeIgnoredPath(this.toPath(fileOrDirectory));
26332628
if (!fileOrDirectoryPath) return;
26342629

2635-
// Has extension
2636-
Debug.assert(result.refCount > 0);
2637-
if (watchDir === fileOrDirectoryPath) {
2638-
this.refreshScriptInfosInDirectory(watchDir);
2630+
// Clear module specifier cache for any projects whose cache was affected by
2631+
// dependency package.jsons in this node_modules directory
2632+
const basename = getBaseFileName(fileOrDirectoryPath);
2633+
if (result.affectedModuleSpecifierCacheProjects.size && (
2634+
basename === "package.json" || basename === "node_modules"
2635+
)) {
2636+
result.affectedModuleSpecifierCacheProjects.forEach(projectName => {
2637+
this.findProject(projectName)?.getModuleSpecifierCache()?.clear();
2638+
});
26392639
}
2640-
else {
2641-
const info = this.getScriptInfoForPath(fileOrDirectoryPath);
2642-
if (info) {
2643-
if (isScriptInfoWatchedFromNodeModules(info)) {
2644-
this.refreshScriptInfo(info);
2645-
}
2640+
2641+
// Refresh closed script info after an npm install
2642+
if (result.refreshScriptInfoRefCount) {
2643+
if (dir === fileOrDirectoryPath) {
2644+
this.refreshScriptInfosInDirectory(dir);
26462645
}
2647-
// Folder
2648-
else if (!hasExtension(fileOrDirectoryPath)) {
2649-
this.refreshScriptInfosInDirectory(fileOrDirectoryPath);
2646+
else {
2647+
const info = this.getScriptInfoForPath(fileOrDirectoryPath);
2648+
if (info) {
2649+
if (isScriptInfoWatchedFromNodeModules(info)) {
2650+
this.refreshScriptInfo(info);
2651+
}
2652+
}
2653+
// Folder
2654+
else if (!hasExtension(fileOrDirectoryPath)) {
2655+
this.refreshScriptInfosInDirectory(fileOrDirectoryPath);
2656+
}
26502657
}
26512658
}
26522659
},
26532660
WatchDirectoryFlags.Recursive,
26542661
this.hostConfiguration.watchOptions,
26552662
WatchType.NodeModulesForClosedScriptInfo
26562663
);
2657-
const result: ScriptInfoInNodeModulesWatcher = {
2664+
const result: NodeModulesWatcher = {
2665+
refreshScriptInfoRefCount: affectedModuleSpecifierCacheProject ? 0 : 1,
2666+
affectedModuleSpecifierCacheProjects: new Set(
2667+
affectedModuleSpecifierCacheProject
2668+
? [affectedModuleSpecifierCacheProject.getProjectName()]
2669+
: emptyArray),
26582670
close: () => {
2659-
if (result.refCount === 1) {
2671+
watcher.close();
2672+
this.nodeModulesWatchers.delete(dir);
2673+
},
2674+
};
2675+
this.nodeModulesWatchers.set(dir, result);
2676+
return result;
2677+
}
2678+
2679+
/*@internal*/
2680+
watchPackageJsonsInNodeModules(dir: Path, project: Project): FileWatcher {
2681+
const existing = this.nodeModulesWatchers.get(dir);
2682+
if (existing) {
2683+
existing.affectedModuleSpecifierCacheProjects.add(project.getProjectName());
2684+
return existing;
2685+
}
2686+
2687+
const watcher = this.createNodeModulesWatcher(dir, project);
2688+
return {
2689+
close: () => {
2690+
if (watcher.refreshScriptInfoRefCount === 0 && watcher.affectedModuleSpecifierCacheProjects.size === 1) {
26602691
watcher.close();
2661-
this.scriptInfoInNodeModulesWatchers.delete(dir);
26622692
}
26632693
else {
2664-
result.refCount--;
2694+
watcher.affectedModuleSpecifierCacheProjects.delete(project.getProjectName());
2695+
}
2696+
},
2697+
};
2698+
}
2699+
2700+
private watchClosedScriptInfoInNodeModules(dir: Path): FileWatcher {
2701+
const watchDir = dir + "/node_modules" as Path;
2702+
const existing = this.nodeModulesWatchers.get(watchDir);
2703+
if (existing) {
2704+
existing.refreshScriptInfoRefCount++;
2705+
return existing;
2706+
}
2707+
2708+
const watcher = this.createNodeModulesWatcher(watchDir);
2709+
return {
2710+
close: () => {
2711+
if (watcher.refreshScriptInfoRefCount === 1 && watcher.affectedModuleSpecifierCacheProjects.size === 0) {
2712+
watcher.close();
2713+
}
2714+
else {
2715+
watcher.refreshScriptInfoRefCount--;
26652716
}
26662717
},
2667-
refCount: 1
26682718
};
2669-
this.scriptInfoInNodeModulesWatchers.set(dir, result);
2670-
return result;
26712719
}
26722720

26732721
private getModifiedTime(info: ScriptInfo) {

src/server/project.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -785,6 +785,7 @@ namespace ts.server {
785785
this.resolutionCache.clear();
786786
this.resolutionCache = undefined!;
787787
this.cachedUnresolvedImportsPerFile = undefined!;
788+
this.moduleSpecifierCache = undefined!;
788789
this.directoryStructureHost = undefined!;
789790
this.projectErrors = undefined;
790791

@@ -1733,14 +1734,8 @@ namespace ts.server {
17331734
}
17341735

17351736
/*@internal*/
1736-
watchNodeModulesDirectory(directoryPath: string, cb: DirectoryWatcherCallback) {
1737-
return this.projectService.watchFactory.watchDirectory(
1738-
directoryPath,
1739-
cb,
1740-
WatchDirectoryFlags.Recursive,
1741-
this.projectService.getWatchOptions(this),
1742-
WatchType.NodeModulesForClosedScriptInfo
1743-
);
1737+
watchNodeModulesForPackageJsonChanges(directoryPath: string) {
1738+
return this.projectService.watchPackageJsonsInNodeModules(this.toPath(directoryPath), this);
17441739
}
17451740
}
17461741

src/services/utilities.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3274,7 +3274,7 @@ namespace ts {
32743274
}
32753275

32763276
export interface ModuleSpecifierResolutionCacheHost {
3277-
watchNodeModulesDirectory(directoryPath: string, cb: DirectoryWatcherCallback): FileWatcher;
3277+
watchNodeModulesForPackageJsonChanges(directoryPath: string): FileWatcher;
32783278
}
32793279

32803280
export function createModuleSpecifierCache(host: ModuleSpecifierResolutionCacheHost): ModuleSpecifierCache {
@@ -3301,11 +3301,12 @@ namespace ts {
33013301
if (cached.moduleSpecifiers) {
33023302
for (const p of cached.modulePaths) {
33033303
if (p.isInNodeModules) {
3304-
const nodeModulesPath = p.path.substring(0, p.path.indexOf(nodeModulesPathPart) + nodeModulesPathPart.length);
3304+
// No trailing slash
3305+
const nodeModulesPath = p.path.substring(0, p.path.indexOf(nodeModulesPathPart) + nodeModulesPathPart.length - 1);
33053306
if (!containedNodeModulesWatchers?.has(nodeModulesPath)) {
33063307
(containedNodeModulesWatchers ||= new Map()).set(
33073308
nodeModulesPath,
3308-
host.watchNodeModulesDirectory(nodeModulesPath, this.clear),
3309+
host.watchNodeModulesForPackageJsonChanges(nodeModulesPath),
33093310
);
33103311
}
33113312
}

0 commit comments

Comments
 (0)