Hacktoberfest 2026:维护者为十月标记出来的 issue,仍然开放、适合新手。 浏览 Hacktoberfest issue

[heft-typescript-plugin] Watch-mode type checking runs synchronously on Heft's main thread and stalls every other task (first watch build ~2x slower)

未关闭
#6,113 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 1 天内回复

还没有人认领这个 Issue。

评估

难度
5/5
预计耗时
一周以上
新手友好度
45/100
Issue 类型
缺陷
描述清晰度
描述清楚
活跃度
活跃

调研方向

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 main today)
  • 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 with tool.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
    pendingOperations loop, lines 365-372).
  • The existing useTranspilerWorker option (in the watch path, #getCreateBuilderProgram: TypeScriptBuilder.ts lines 934-942; #queueTranspileInWorker at 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 天 13 小时
30 天内合并 PR
45

环境准备

在 Codespaces 中打开

在浏览器里用你自己的 GitHub 账号启动这个项目的开发容器。

  • 没有 Dockerfile 或 Docker Compose 文件
  • 有 Pull Request 模板
  • 没有贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

microsoft/rushstack 的其他 Issue

查看 microsoft/rushstack 的全部 Issue

相似的 Issue

更多 TypeScript Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。