Skip to content
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

Allow consumers of Compiler API to override getDirectoryToWatchFailedLookupLocation #58856

Closed
6 tasks done
dmichon-msft opened this issue Jun 13, 2024 · 3 comments
Closed
6 tasks done
Assignees
Labels
Needs Investigation This issue needs a team member to investigate its status.

Comments

@dmichon-msft
Copy link
Contributor

πŸ” Search Terms

getDirectoryToWatchFailedLookupLocation, watch, resolver, compiler API

βœ… Viability Checklist

⭐ Suggestion

Consumers of TypeScript's compiler API are typically in a better position to understand the nuances of the layout of the repository and take advantage of that knowledge to optimize file watcher performance than any generic implementation in TypeScript.

For large monorepos especially, TypeScript's implementation of getDirectoryToWatchFailedLookupLocation does not scale well and results in watching tons of excess files and directories.

If TypeScript were to access this method (and possible also getDirectoryToWatchFromFailedLookupLocationDirectory) via the CompilerHost interface (defaulting to the current implementation), consumers of the compiler API who work with customized file system layouts could better instruct the compiler on how to efficiently watch their filesystem. This would also help with exotic file system scenarios (e.g. network file systems, virtual filesystems).

The current method:
https://github.com/microsoft/TypeScript/blob/b258429aaaa6206fd5f911360b094ca626544ce9/src/compiler/resolutionCache.ts#L347C17-L394

πŸ“ƒ Motivating Example

I work in a large monorepo that has a small number of root folder (>1) and hundreds of projects under each root. Frequently projects under one root reference projects under another root, and under the default implementation this results in TypeScript watching hundreds of projects instead of the handful that are relevant to its execution.

On Linux especially, the decision to ask for a recursive watcher by default results in significant over-allocation of file and directory watchers, since the platform has no native support and therefore it results in allocating a non-recursive watcher for each and every directory in the tree.

πŸ’» Use Cases

  1. What do you want to use this for?

Integrating with Heft for use in Rush monorepos, particularly on GitHub codespaces (which are Linux-based)

  1. What shortcomings exist with current approaches?

The current workaround is brittle and will need to be updated any time I update to a newer version of TypeScript.

  1. What workarounds are you using in the meantime?

I have locally patched the installation of TypeScript in my monorepo to override this function with an alternative implementation that non-recursively watches the immediate parent folder of the failed resolution location.

diff --git a/lib/typescript.js b/lib/typescript.js
index fe732a6cb262712d4d79b917b7fa0f0eee9d58b4..e2534aa5dc62b7ea70b337687ed7d194b0ccf1e9 100644
--- a/lib/typescript.js
+++ b/lib/typescript.js
@@ -124353,6 +124353,13 @@ ${lanes.join("\n")}
     if (failedLookupPathComponents.length <= perceivedOsRootLength + 1)
       return void 0;
     const nodeModulesIndex = failedLookupPathComponents.indexOf("node_modules");
+
+    if (process.env.CODESPACE_NAME) {
+      // Hack to prevent excessive directory watching on Linux.
+      // In a Rush repo, the node_modules folder is considered immutable while running, so don't watch it.
+      // Linux does not support recursive watching, so always only watch the immediate parent folder.
+      return nodeModulesIndex >= 0 || failedLookupComponents.length < 3 ? void 0 : getDirectoryOfFailedLookupWatch(failedLookupComponents, failedLookupPathComponents, failedLookupComponents.length - 1, true);
+    }
     if (nodeModulesIndex !== -1 && nodeModulesIndex + 1 <= perceivedOsRootLength + 1)
       return void 0;
     if (isInDirectoryPath(rootPathComponents, failedLookupPathComponents)) {
@@ -124376,6 +124383,13 @@ ${lanes.join("\n")}
     );
   }
   function getDirectoryToWatchFromFailedLookupLocationDirectory(dirComponents, dirPathComponents, dirPathComponentsLength, perceivedOsRootLength, nodeModulesIndex, rootPathComponents) {
+    if (process.env.CODESPACE_NAME) {
+      // Hack to prevent excessive directory watching on Linux.
+      // In a Rush repo, the node_modules folder is considered immutable while running, so don't watch it.
+      // Linux does not support recursive watching, so always only watch the immediate parent folder.
+      return nodeModulesIndex >= 0 || failedLookupComponents.length < 3 ? void 0 : getDirectoryOfFailedLookupWatch(failedLookupComponents, failedLookupPathComponents, failedLookupComponents.length - 1, true);
+    }
+
     if (nodeModulesIndex !== -1) {
       return getDirectoryOfFailedLookupWatch(dirComponents, dirPathComponents, nodeModulesIndex + 1);
     }
@RyanCavanaugh RyanCavanaugh added the Needs Investigation This issue needs a team member to investigate its status. label Jun 18, 2024
@RyanCavanaugh
Copy link
Member

@sheetalkamat thoughts?

@dmichon-msft
Copy link
Contributor Author

Related #58866.

@sheetalkamat
Copy link
Member

I think better approach is to handle this like in #58866 than adding API for this as this is not just resolutionCache issues but also needs to handle closed script info's node_modules folder etc as well

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs Investigation This issue needs a team member to investigate its status.
Projects
None yet
Development

No branches or pull requests

3 participants