[heft-typescript-plugin] Watch-mode type checking runs synchronously on Heft's main thread and stalls every other task (first watch build ~2x slower)
メンテナーはふだん 1 日以内に返信
まだ誰も着手していません。
評価
- 難易度
- 5/5
- 見積もり時間
- 1週間以上
- 初心者へのやさしさ
- 45/100
- issue の種類
- バグ
- 明瞭さ
- 明確に書かれている
- 活発さ
- 活発
- 技術スタック
- node.js, typescript
- 領域
- build-system, tooling
調査の方向性
Start in heft-plugins/heft-typescript-plugin/src/TypeScriptBuilder.ts, especially _runWatchAsync, #getCreateBuilderProgram, and the existing #queueTranspileInWorker path. Trace how createWatchProgram and pendingOperations block the main thread, then compare the worker-thread plumbing. Done means an opt-in typeCheckInWorker configuration keeps independent tasks running while diagnostics and declaration emit complete correctly in watch mode.
索引モデルが issue の本文から書いたものです。
説明
🤖 Filed by GitHub Copilot acting for @namankanakiya (light review only).
Target: rushstack GitHub issues (package folder heft-plugins/heft-typescript-plugin)
Summary
In heft run-watch, the TypeScript task builds and checks its program with one synchronous call on the main
thread. Heft runs all tasks of a phase in that same process, so while the program is built (several seconds for a
large project) no other task can make progress, even tasks that do not depend on TypeScript. In our setup the type
check only produces diagnostics and declarations (JavaScript comes from a separate transpile task), and the bundler
task depends only on the transpile task. The bundler still waits for the type check: either it starts only after
the type check finishes, or, if it started first, its JavaScript side is paused for the duration. The first watch
build takes roughly the type check time plus the bundle time instead of the longer of the two.
Versions
- @rushstack/heft 1.3.2, @rushstack/heft-typescript-plugin 1.3.24 (same version as on
maintoday) - TypeScript 5.3.3 (type check only;
isolatedModules: true) - @rushstack/heft-isolated-typescript-transpile-plugin 1.2.29 (SWC emits the JavaScript)
- @rushstack/heft-rspack-plugin 0.3.30 with @rspack/core 1.7.7 (the webpack5 plugin runs in the same process and should behave the same; not measured)
- Node.js 22.16, Linux, 16 cores
Repro
A build phase whose bundler task does NOT depend on the typescript task:
// config/heft.json (abridged)
{
"phasesByName": {
"build": {
"tasksByName": {
"typescript": { "taskPlugin": { "pluginPackage": "@rushstack/heft-typescript-plugin" } },
"transpile": {
"taskPlugin": { "pluginPackage": "@rushstack/heft-isolated-typescript-transpile-plugin" }
},
"webpack": {
"taskDependencies": ["transpile"],
"taskPlugin": { "pluginPackage": "@rushstack/heft-rspack-plugin" }
}
}
}
}
}
Use a project large enough that program creation takes a few seconds (ours has 8,198 files in the program), then run:
heft --debug run-watch --only build -- --clean
and compare the timestamps of the [build:typescript], [build:transpile] and [build:webpack] lines.
Measured impact
Five consecutive runs of the command above, each stopped after the first build, warm OS file cache. Times are
seconds since start:
| Run | Task order | TS task start | TS compile (Starting compilation -> Finished, end - start) |
TS task duration (Heft's Finished incremental task execution) |
Bundler start | Bundler duration | First build done |
|---|---|---|---|---|---|---|---|
| 2 | bundler first | 1.18 | 1.30 -> 9.17 (7.87 s) | 7.98 s | 1.20 | 14.96 s | 16.49 |
| 3 | bundler first | 1.20 | 4.17 -> 11.91 (7.74 s) | 10.71 s | 1.22 | 14.80 s | 16.31 |
| 4 | TS first | 1.17 | 1.20 -> 8.91 (7.71 s) | 7.74 s | 8.91 | 7.02 s | 16.23 |
| 5 | TS first | 1.25 | 1.27 -> 8.95 (7.68 s) | 7.70 s | 8.95 | 6.79 s | 16.04 |
no --clean |
TS first | 1.16 | 1.19 -> 8.23 (7.04 s) | 7.08 s | 8.24 | 7.01 s | 15.54 |
In run 3 the TypeScript task started at 1.20 s but printed Starting compilation only at 4.17 s: the bundler's
JavaScript work, which started at 1.22 s, held the shared main thread for those 3 s. The two directions interfere.
TypeScript's own breakdown for one run: I/O read 0.28 s and parse 2.24 s over 8,198 files, Program (incl. read and
parse) 4.97 s, bind 1.03 s, check 0.68 s, emit 0.43 s.
- When the bundler starts first, its duration doubles (14.8-15.0 s instead of 6.8-7.0 s): it is paused while the
synchronous program build holds the event loop. - When TypeScript starts first, the bundler (which depends only on the transpile task) starts at 8.9 s. The
transpile task finishes its 28 files in 27 ms of work but reports completion only when the type check returns. - Either way, the first watch build takes ~16 s. If the type check did not block the main thread, it would take
about max(7.7, 1.2 + 7.0) ≈ 8.2-8.5 s, roughly half.
Root cause (main)
heft-plugins/heft-typescript-plugin/src/TypeScriptBuilder.ts:
_runWatchAsync(line 325) creates the watch program withtool.watchProgram = ts.createWatchProgram(compilerHost)
(line 361). That single synchronous call builds the program and runs the first semantic check and emit through the
watch host (#buildWatchCompilerHost, lines 1053-1075).- Later watch iterations run the queued TypeScript operations synchronously in the same function (the
pendingOperationsloop, lines 365-372). - The existing
useTranspilerWorkeroption (in the watch path,#getCreateBuilderProgram: TypeScriptBuilder.ts lines 934-942;#queueTranspileInWorkerat lines 1212-1223, which creates the Worker at line 1223) moves only the
JavaScript transpile to a worker. Program creation and type checking stay on the main thread.
Suggested fix
Add an opt-in option (for example "typeCheckInWorker": true in config/typescript.json) that hosts the watch
program in a worker_threads worker. The worker builds the program, type-checks and emits declarations; the main
thread awaits a promise and receives the diagnostics and emit results as messages. The plumbing is like the existing
transpiler worker (#queueTranspileInWorker). With this option the main thread stays free, so independent tasks
(transpile, generators, the bundler) run while the check proceeds. The first watch build drops to about the longer of
the type check and the bundle. Incremental watch iterations of a large program would no longer stall the bundler
either (not measured here).
- 主要言語
- TypeScript
- スター
- 6.5k
- フォーク
- 710
- 平均マージ
- 1日 23時間
- マージ済み PR(30日)
- 47
環境構築
このプロジェクトの開発コンテナを、あなたの GitHub アカウントでブラウザ上に起動します。
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートあり
- コントリビューションガイドなし
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
microsoft/rushstack のほかの issue
-
[rush] Upgrade the pnpm-sync-lib dependency to 0.3.5.対応中かも @martinnaj が 7 日前に担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
microsoft/rushstack#5971 · コメント 2 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 65/100
microsoft/rushstack#5902 · リアクション 1 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
microsoft/rushstack#5839 · リアクション 1 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 70/100
microsoft/rushstack#5683 · コメント 3 件 ·
メンテナーはふだん 1 日以内に返信
-
[rush] Managed Git LFS hooks conflict with reused Azure Pipelines workspaces対応中かも @iclanton が今日担当しました。 オープン
microsoft/rushstack#6120 · リアクション 1 件 · 担当者 2 名 ·
メンテナーはふだん 1 日以内に返信
microsoft/rushstack の issue をすべて見る
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
rajbos/ai-engineering-fluency#2340 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
community documentation first-timers-only good first issue hacktoberfest help wanted low hanging fruit up-for-grabs
難易度 1/5 1時間未満 初心者へのやさしさ 70/100
lingdojo/kana-dojo#31864 · コメント 1 件 · リアクション 5 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
zenstackhq/zenstack#2873 ·
メンテナーはふだん 1 日以内に返信
-
CLI: TUI shows onboarding when the provider's API key is only in the environment (e.g. OPENROUTER_API_KEY)対応中かも このイシューにリンクされたプルリクエストがオープン中、またはマージ済みです。 オープンCLI
難易度 2/5 1〜3時間 初心者へのやさしさ 67/100
cline/cline#14923 · コメント 2 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
paperclipai/paperclip#15490 ·
メンテナーはふだん 1 日以内に返信