Skip to content

perf(dev): compile with the project's tsc without npx - #38

Merged
Upd4ting merged 1 commit into
mainfrom
perf/tsc-without-npx
Nov 11, 2026
Merged

Upd4ting merged 1 commit into
mainfrom
perf/tsc-without-npx

Conversation

@Upd4ting

Copy link
Copy Markdown
Member

Why

A module created from this template should spend as little time as possible on install, build, test, start and reload (ANT-1957, the second wave of the org-wide perf pass). It ran tsc through npx in four places. npx adds about 200 ms of npm startup to every call.

Summary

The test config and the playground config now call the project's own compiler as node node_modules/typescript/bin/tsc:

  • antelope.test.ts: the installCommand that pnpm test runs.
  • playground/antelope.config.ts: installCommand and reloadCommand for the playground and for the module. pnpm dev runs both at start and the module's on each reload.

It is the same typescript package with the same arguments and the same incremental dist/.tsbuildinfo. The output and the diagnostics stay the same. Only the npm startup is gone. The command does not depend on PATH, so it also works with a global ajs. This is the same change as AntelopeJS/template-blank-typescript#36.

Readability cost: the command names a path inside node_modules instead of npx tsc.

Workload and numbers

The numbers come from a module created with ajs module init --template "Module Typescript" --pm pnpm through antelopejs-verify, then pnpm install --frozen-lockfile. Node 24.22.2, pnpm 10.6.5, on a shared 16-core server. Before and after runs alternate, and dist is warm, as in a developer's loop.

Entry point Before (npx tsc) After Runs per side
pnpm test median 1122 ms (1076 to 1185) median 947 ms (892 to 1076) 10
pnpm dev start, until Hello world median 1243 ms (1066 to 1956) median 951 ms (919 to 1975) 6
pnpm dev reload, from the edit until the module is rebuilt median 1181 ms (1163 to 1473) median 1054 ms (987 to 1258) 18

Start runs two compiles, so it saves about twice what one call saves. Evidence level: confirmed through end-to-end benchmarks. Impact: Low. It saves about 175 ms on each pnpm test and each CI run, about 290 ms on each pnpm dev start and about 130 ms on each reload.

npx tsc alone took 686 ms and node node_modules/typescript/bin/tsc took 477 ms (hyperfine, 8 runs each, warm dist).

Related issue

ANT-1957.

Verification

  • pnpm run checks: Checks: 12 passed, 0 failed.
  • pnpm build && pnpm --dir playground build passes.
  • antelopejs-verify journey <run> --plan: PASS smoke/module-build, PASS playground/playground-dev, PASS tests/module-test, PASS tests/module-test-fails.

Checklist

  • The pull request title follows Conventional Commits.
  • I added or updated tests when behavior changed. Behavior does not change. module-test, module-test-fails and playground-dev cover it.
  • I updated documentation when users or contributors are affected. AGENTS.md says the test config compiles with tsc, which stays true.
  • I documented breaking changes and migration steps. There are none.
  • I did not include credentials or unrelated changes.

@Upd4ting
Upd4ting merged commit 8353d8b into main Nov 11, 2026
8 checks passed
@Upd4ting
Upd4ting deleted the perf/tsc-without-npx branch November 11, 2026 16:48
Upd4ting added a commit that referenced this pull request Nov 11, 2026
…ild's (#39)

## Why

A module created from this template should spend as little time as
possible on install, build, test, start and reload (ANT-1957, the second
wave of the org-wide perf pass). `typecheck` (`tsc --noEmit`) and the
compiles of `pnpm build` and `pnpm test` (`tsc --skipLibCheck`) shared
one incremental state file, `dist/.tsbuildinfo`. Each run overwrote it
with different options. A `typecheck` that ran after a test or a build
found the `--skipLibCheck` state, so it checked every declaration file
again. That happens on every `pnpm run checks` after the first and on
every pre-push hook after a test.

## Summary

`typecheck` now keeps its own state file with `--tsBuildInfoFile
node_modules/.cache/typecheck/.tsbuildinfo`, next to the
`tsconfig.config.json` cache already in `node_modules/.cache`. It checks
the same program with the same options, so the diagnostics stay the
same. A cached error still fails the next run. `tsc` itself invalidates
the file from file hashes and options. `pnpm build` and `pnpm test` keep
`dist/.tsbuildinfo`. `antelopejs-sync --check` accepts the script.

Readability cost: one flag on the `typecheck` script.

## Workload and numbers

The numbers come from a module created with `ajs module init --template
"Module Typescript" --pm pnpm` through `antelopejs-verify`, then `pnpm
install --frozen-lockfile`, from current main. Node 24.22.2, pnpm
10.6.5, on a shared 16-core server.

| Command | Before | After | Runs per side |
| --- | --- | --- | --- |
| `pnpm run checks`, repeated, before and after alternating | median
3246 ms (3146 to 4016) | median 2528 ms (2286 to 3339) | 8 |
| `pnpm typecheck` right after `pnpm test` | mean 1486 ms (1413 to 1571)
| mean 592 ms (571 to 636) | 8 |

A second interleaved run of `checks` on a module that already had the
`npx` fix (#38) gave the same picture: median 3032 ms (2912 to 3480)
before, 2209 ms (2143 to 2486) after. The first `typecheck` on a fresh
clone still pays the full check, about 1.46 s, as before.

Evidence level: confirmed through end-to-end benchmarks. Impact: Medium.
It saves about 0.7 to 0.9 s on each `pnpm run checks` after the first, a
22 to 27% cut, and about 0.9 s on each pre-push hook that follows a test
or a build.

Correctness check: with `export const bad: number = "x";` appended to
`src/index.ts`, two `pnpm typecheck` runs in a row both fail with
`TS2322`, and the run after the fix passes.

## Related issue

ANT-1957. `@antelopejs/tooling-configs` writes `typecheck: "tsc
--noEmit"` as its default script, so other templates that also build
with `--skipLibCheck` likely pay the same cost. That belongs in
tooling-configs, not here.

## Verification

- `pnpm run checks`: `Checks: 12 passed, 0 failed.`
- `antelopejs-verify journey <run> --plan`: every journey passes,
`tooling/scaffold-lint-rejects` (an unused local still fails
`typecheck`) and `tooling/scaffold-checks` included.

## Checklist

- [x] The pull request title follows Conventional Commits.
- [x] I added or updated tests when behavior changed. Behavior does not
change. `scaffold-lint-rejects` and `scaffold-checks` cover it.
- [x] I updated documentation when users or contributors are affected.
No document names the `typecheck` command line.
- [x] I documented breaking changes and migration steps. There are none.
- [x] I did not include credentials or unrelated changes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant