diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 32c99305535d6..af75f4ac59e14 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -265,6 +265,13 @@ $ cd docs
$ BUILD_DOCS_DEV=1 ./build-docs.sh
```
+### Tooling Assists
+#### Jetbrains (WebStorm/IntelliJ)
+This project uses lerna and utilizes symlinks inside nested node_modules directories. You may encounter an issue during
+indexing where the IDE attempts to index these directories and keeps following links until the process runs out of
+available memory and crashes. To fix this, you can run ```node ./scripts/jetbrains-remove-node-modules.js``` to exclude
+these directories.
+
## Dependencies
### Adding Dependencies
diff --git a/scripts/jetbrains-remove-node-modules.js b/scripts/jetbrains-remove-node-modules.js
new file mode 100644
index 0000000000000..875ebfbc3b231
--- /dev/null
+++ b/scripts/jetbrains-remove-node-modules.js
@@ -0,0 +1,60 @@
+const fs = require('fs');
+const path = require('path');
+const os = require('os');
+
+const getAllChildDirectories = (dir) => fs.readdirSync(dir).map(name => path.join(dir, name.toString())).filter(name => fs.lstatSync(name).isDirectory());
+const isNodeModulesDirectory = (name) => name.toString().endsWith('node_modules');
+
+function getAllNodeModulesPaths(dir) {
+ let nodeModulesPaths = [];
+ getAllChildDirectories(dir).forEach(name => {
+ if (isNodeModulesDirectory(name)) {
+ console.log('Excluding ' + name);
+ nodeModulesPaths.push(name);
+ } else {
+ const subNodeModulesPaths = getAllNodeModulesPaths(name);
+ nodeModulesPaths = nodeModulesPaths.concat(subNodeModulesPaths);
+ }
+ });
+ return nodeModulesPaths;
+}
+
+// Should be run at the root directory
+if (!fs.existsSync('lerna.json')) {
+ throw new Error('This script should be run from the root of the repo.');
+}
+
+const nodeModulesPaths = getAllNodeModulesPaths('.');
+
+// Hardcoded exclusions for this project (in addition to node_modules)
+const exclusions = nodeModulesPaths.map(path => ``);
+exclusions.push('');
+exclusions.push('');
+exclusions.push('');
+exclusions.push('');
+
+exclusionsString = exclusions.join(os.EOL);
+
+// Let filename be passed in as an override
+let fileName = process.argv[2] || process.cwd().split('/').slice(-1).pop() + '.iml';
+
+// Jetbrains IDEs store iml in .idea except for IntelliJ, which uses root.
+if (fs.existsSync('.idea/' + fileName)) {
+ fileName = '.idea/' + fileName;
+} else if (!fs.existsSync(fileName)) {
+ throw new Error('iml file not found in .idea or at root. Please pass in a path explicitly as the first argument.');
+}
+
+// Keep the contents. We are only updating exclusions.
+const exclusionInfo = fs.readFileSync(fileName);
+
+const toWrite = exclusionInfo.toString().replace(/(?:\s.+)+\/content>/m, `${os.EOL}${exclusionsString}${os.EOL}`);
+
+console.log(os.EOL + 'Writing to file...');
+
+// "Delete" the file first to avoid strange concurrent use errors.
+fs.unlinkSync(fileName);
+
+fs.writeFileSync(fileName, toWrite);
+
+console.log('Done!');