I've been conducting a thorough analysis of performance degradation in a large-scale monorepo development environment, specifically focusing on the trade-offs between ESLint and Prettier when managing multiple programming languages. Our current setup involves approximately 150 packages with mixed TypeScript, JavaScript, Python, YAML, and JSON configurations, all managed through pnpm workspaces on Node.js 20.x.
The primary issue manifests as significant editor latency during file saves and incremental builds, particularly when both tools are configured to run in tandem. I've observed the following specific symptoms:
- **Memory consumption spikes** during linting operations, with ESLint processes occasionally exceeding 2GB RSS when processing TypeScript files with complex type-aware rules
- **Non-deterministic formatting conflicts** when Prettier plugins (like `prettier-plugin-organize-imports`) interact with ESLint's auto-fix capabilities
- **Language server protocol (LSP) interference** where the ESLint language server competes with TypeScript's tsserver for CPU cycles during hover operations
My current configuration employs a layered approach:
```jsonc
// .eslintrc.js (root)
module.exports = {
root: true,
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended-type-checked'],
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./packages/*/tsconfig.json', './apps/*/tsconfig.json'],
},
overrides: [
{
files: ['*.py'],
processor: 'python/python',
},
],
};
// .prettierrc
{
"plugins": ["prettier-plugin-organize-imports", "prettier-plugin-packagejson"],
"overrides": [
{
"files": "*.py",
"options": {
"parser": "python",
"tabWidth": 4
}
}
]
}
```
The critical performance bottlenecks appear to stem from:
1. **Duplicate AST parsing**: Both tools parse the same source files independently, despite sharing similar grammar foundations
2. **Plugin dependency graph complexity**: Each package's devDependencies create redundant installations of shared utilities like `@typescript-eslint/parser`
3. **Cache invalidation patterns**: Prettier's file-based caching doesn't align with ESLint's in-memory cache eviction strategies
I'm particularly interested in systematic approaches that others have validated through profiling. Has anyone successfully implemented:
- A unified parser architecture that feeds both linting and formatting pipelines?
- Selective rule disabling strategies for monorepo contexts where different packages have divergent quality requirements?
- Memory-efficient worker pool configurations for concurrent language processing?
My preliminary benchmarks using `0x` flamegraphs indicate that the TypeScript compiler API consumption dominates the cost profile, suggesting that reducing duplicate type checking might yield the most significant gains. However, I'm concerned about losing the safety guarantees provided by independent verification pipelines.
brianh
Your memory spikes track with what we see on 50+ package TS repos. ESLint's type-aware rules create entire program instances. That's the root cause.
Have you measured the impact of disabling type-aware linting during saves? Run it only in CI. The editor latency usually isn't worth the real-time type checking.
Prettier for formatting, ESLint for logic. Keep them completely separate. Let the language servers handle the hover info. Trying to make them work together in real-time is where your non-deterministic conflicts come from.
Five nines? Prove it.
Your layered config is making it worse. You're running type-aware rules AND Prettier plugins simultaneously on save.
Split them. Keep a fast local config and a heavy CI config.
Local `.eslintrc.js`:
```javascript
module.exports = {
extends: ['eslint:recommended'],
rules: {
// stylistic rules only
}
}
```
CI config extends that, adds the heavy type-aware rules. Run `eslint --config .eslintrc.ci.js` in your pipeline.
Don't let Prettier plugins touch imports in the editor. Do that in CI or a pre-commit hook.
—cp