Goal
Reduce the runtime memory usage of VSCode tsserver through proper configuration of tsconfig.json, without breaking type checking or the build pipeline. Suitable for batch optimization of any number and any form of TypeScript/Vue projects.
Measured results: On 2026-09-05, rolled out across 14 repositories (five forms: standalone/solution/composite/monorepo subpackage/nitro extends) with zero regressions, 15 commits pushed.
Core configuration (ordered by memory-reduction contribution)
| # | Configuration | Mechanism | Applicability |
|---|---|---|---|
| 1 | incremental: true + tsBuildInfoFile: "./node_modules/.cache/tsconfig.tsbuildinfo" |
Incremental compilation cache, eliminating the memory peak from tsserver repeatedly compiling everything in full | All real compilation units |
| 2 | skipLibCheck: true |
Skip full type checking of .d.ts inside node_modules |
Leave it alone if already present; add it if absent |
| 3 | include / exclude Explicitly limit |
Narrow the scope of files tsserver collects | If include is already explicit, only add the missing exclude entries |
| 4 | assumeChangesOnlyAffectDirectDependencies: true |
On change, recheck only direct dependencies | Monorepo subpackages only |
| 5 | User-level settings companion (see below) | Process-level memory reduction | One-time global |
User-level settings.json companion (one-time, not part of tsconfig but in the same task chain):
"typescript.tsserver.useSeparateSyntaxServer": true, // syntax/semantic 进程拆分
"typescript.tsserver.maxTsServerMemoryForSyntax": 1024, // syntax server 低堆上限
"typescript.tsserver.experimental.enableProjectDiagnostics": false,
"typescript.disableAutomaticTypeAcquisition": true,
"typescript.preferences.includePackageJsonAutoImports": "off",
"typescript.suggest.autoImports": false
Project form determination (determine first, then apply configuration)
Core principle: adding the same key in the wrong place is a no-op. Before starting, you must determine which form the target tsconfig is:
| Form | Identifying characteristics | Strategy |
|---|---|---|
| A. Standalone application | Has complete compilerOptions + non-empty include pointing to real source code |
Directly add incremental + tsBuildInfoFile; do not add skipLibCheck if already present |
| B. solution-style root | files: [] + references, has no source code itself |
Adding keys to the root config is a no-op; do not add at the root. Drill down to the real sub-configs (tsconfig.app.json / tsconfig.node.json, etc.) and add separately; for multiple sub-configs, name tsBuildInfoFile per file(tsconfig.app.tsbuildinfo / tsconfig.node.tsbuildinfo) to avoid write conflicts |
| C. composite subproject | Sub-config contains "composite": true |
Skip—composite enforces incremental behavior, so adding it is redundant |
| D. monorepo subpackage | Referenced by references / depends on workspace protocol packages | On top of A, additionally add assumeChangesOnlyAffectDirectDependencies: true |
| E. extends external preset | "extends": "nitro/tsconfig" such as |
Local compilerOptions writing incremental + tsBuildInfoFile to override is enough to take effect |
| F. No tsconfig / no TS source code / example project | File does not exist / empty directory / examples scaffold | Skip, do not create(Creating a tsconfig is beyond the scope of memory optimization) |
Standard execution process (5 steps per repository)
1. 基线对照 → 改前跑 tsc(或项目自带 typecheck),记录 exit 码与错误数
2. 幂等核查 → grep '"incremental"' tsconfig.json,已有则跳过改写
3. 兜底确认 → 确认原文件在 git HEAD 中(代替 .bak 备份);gitignore 确认含 *.tsbuildinfo
4. 改写 → 只增不删,插入块带注释标记(日期 + 任务来源);缓存统一写 node_modules/.cache/
5. 验证闭环 → 改后 tsc 错误数与基线一致 = 零回归;确认 tsbuildinfo 生成
L1 acceptance criteria (key discipline)
- Error count comparison: after change
error TScount === pre-change baseline → zero regression.Existing errors are not regressions(many projects already report errors with bare tsc; the real type chain may be vue-tsc / IDE). - Prefer the project's own typecheck: if there is
vue-tscusepnpm typecheck, baretscfor.vueprojects is a false negative (TS2307 is a toolchain limitation). - After a pipe,
$?is unreliable:cmd | tailwhat you get after is tail's exit code, use the(cmd > log 2>&1; echo $?)structure. - build chain verification: the consumer of the root tsconfig is tsc itself; a monorepo root's turbo build does not go through the root config—build N/A must be given with justification, no pretending.
Red lines and common pitfalls
| # | Pitfall | Rule |
|---|---|---|
| 1 | include/exclude two-way contradiction | When include explicitly contains *.d.ts , exclude must notadd **/*.d.ts(declaration files need to enter compilation, and shouldn't have been excluded anyway) |
| 2 | solution root pretending | When the root is files:[], adding any compilation key is a no-op; you must drill down into sub-configs |
| 3 | Dual sub-config cache conflict | app/node sharing a tsbuildinfo with the same name will overwrite each other; names must be separated |
| 4 | MSYS paths given to native programs | /tmp/xxx passed to node.exe/git.exe will be parsed as 盘符:\tmp—put temporary scripts/message files on real drive-letter paths |
| 5 | PowerShell 5.1 encoding | Get-Content -Raw defaults to ANSI reading UTF-8 files producing mojibake—use Node for config file read/write fs end-to-end utf8 |
| 6 | Workspace type: module |
temporary Node scripts use the .cjs extension,.js will be treated as ESM and report require undefined |
| 7 | Dirty files polluting commits | When committing across many repositories, only git add planned paths, -Ais forbidden; zero touching of the user's/others' dirty files |
| 8 | User touching GUI during development | Batch invoking code CLI is forbidden (each one spins up a whole set of instances); language service verification is to be completed within the user's window |
Complete real-world case study
For the form determination, modification approach, and verification details of the 14 repositories, see references/strategy-matrix.md。
Acceptance criteria
- Every rewritten tsconfig has a pre-change/post-change error count comparison record
- All tsbuildinfo files are generated normally and covered by gitignore
- Repositories judged as skipped have clear justification (form + reason)
- Commits follow Conventional Commits (
🔨 build(tsconfig)), and are confirmed by the user before push