Skip to content

feat: lower minimum Node.js to 16.20, matching TypeScript 7.1 - #1723

Closed
johnnyreilly wants to merge 1 commit into
nextfrom
feat/node-16-engines
Closed

johnnyreilly wants to merge 1 commit into
nextfrom
feat/node-16-engines

Conversation

@johnnyreilly

Copy link
Copy Markdown
Member

Closes #1721

Summary

Lowers engines.node from >=22.0.0 to >=16.20.0, the same minimum as TypeScript 7.1.

Changing engines alone would not have been enough. TypeScript 7.1 is ESM-only ("type": "module"), and ts-loader loaded it with require(). That only works on Node versions with require(esm) (20.19+ / 22.12+). On Node 16, ts-loader failed at load time with ERR_REQUIRE_ESM.

Changes

  • src/typeScriptApi.ts: <compiler>/sync and <compiler>/fs are now loaded with import(), cached per compiler package. Paths are resolved with require.resolve first, so the compiler option resolves the same way as before, including Windows absolute paths. createFileSystemLayer now also comes from the configured compiler package; before, it was always imported from typescript.
  • src/index.ts: loader already used this.async(). It is now an async function that awaits the (cached) TypeScript load before running the existing synchronous logic. Error stack traces keep the same at successLoader / at Object.loader frames, and all comparison snapshots pass unchanged.
  • test/minimum-node-tests/: a new smoke test with no test framework. It builds a clean project, runs the bundle, and checks that a type error (TS2345) is reported. The comparison/execution harnesses can't run on Node 16: Jasmine 7 needs structuredClone, and the comparison harness requires typescript/sync.
  • .github/workflows/minimum-node-tests.yml (wired into ci.yml): installs and builds on Node 26, then runs the smoke test on Node 16.20.0.
  • Updated README compatibility, CHANGELOG (Unreleased) and AGENTS.md.

Testing

  • npm run lint, npm run knip and prettier all pass
  • npm run comparison-tests and npm run execution-tests pass on Node 26
  • npm run minimum-node-tests passes on Node 16.20.2 and Node 26. On next without this change, it fails on Node 16 with ERR_REQUIRE_ESM.

🤖 Generated with Claude Code

TypeScript 7.1 is ESM-only and needs Node >=16.20. ts-loader loaded it with
require(), which only works on Node versions with require(esm) support, so
it now loads `<compiler>/sync` and `<compiler>/fs` with import() instead
(once per compiler package, awaited at the top of the already-async loader).

The comparison/execution harnesses can't run on Node 16, so a new
framework-free smoke test (test/minimum-node-tests) runs in CI on Node
16.20.0 after installing and building on Node 26.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Benchmark (Ubuntu)

Scenario transpileOnly PR branch median (ms) base branch median (ms) Δ vs base branch
Cold build false 1197.9 1188.4 +0.8%
Incremental rebuild (leaf touch) false 53.3 53.3 -0.0%
Incremental rebuild (hub touch) false 485.1 480.4 +1.0%
Cold build true 407.5 396.9 +2.7%
Incremental rebuild (leaf touch) true 33.8 34.3 -1.4%
Incremental rebuild (hub touch) true 32.9 31.8 +3.4%
Cold build (all orphan files) false 1257.5 1269.8 -1.0%
Incremental rebuild (hub touch, all orphan files) false 499.9 500.6 -0.1%

PR branch = /home/runner/work/ts-loader/ts-loader, base branch = /home/runner/work/ts-loader/ts-loader-main. 2 warmup + 10 measured iterations per scenario, median reported. Report-only - no threshold fails this check.

@github-actions

Copy link
Copy Markdown

Benchmark (macOS)

Scenario transpileOnly PR branch median (ms) base branch median (ms) Δ vs base branch
Cold build false 1598.2 1670.2 -4.3%
Incremental rebuild (leaf touch) false 79.1 81.6 -3.0%
Incremental rebuild (hub touch) false 605.4 640.7 -5.5%
Cold build true 474.8 483.9 -1.9%
Incremental rebuild (leaf touch) true 84.0 80.1 +4.9%
Incremental rebuild (hub touch) true 79.9 67.2 +19.0%
Cold build (all orphan files) false 1799.5 1786.6 +0.7%
Incremental rebuild (hub touch, all orphan files) false 672.5 688.2 -2.3%

PR branch = /Users/runner/work/ts-loader/ts-loader, base branch = /Users/runner/work/ts-loader/ts-loader-main. 2 warmup + 10 measured iterations per scenario, median reported. Report-only - no threshold fails this check.

@github-actions

Copy link
Copy Markdown

Benchmark (Windows)

Scenario transpileOnly PR branch median (ms) base branch median (ms) Δ vs base branch
Cold build false 1683.0 1683.2 -0.0%
Incremental rebuild (leaf touch) false 60.2 62.3 -3.4%
Incremental rebuild (hub touch) false 705.0 711.0 -0.8%
Cold build true 454.5 454.7 -0.1%
Incremental rebuild (leaf touch) true 44.2 44.0 +0.5%
Incremental rebuild (hub touch) true 43.8 42.7 +2.5%
Cold build (all orphan files) false 1843.8 1857.6 -0.7%
Incremental rebuild (hub touch, all orphan files) false 778.5 778.2 +0.0%

PR branch = C:\source\ts-loader, base branch = C:\source\ts-loader-main. 2 warmup + 10 measured iterations per scenario, median reported. Report-only - no threshold fails this check.

@johnnyreilly

Copy link
Copy Markdown
Member Author

Better approach attempted here: #1725

But not viable anymore since: microsoft/TypeScript#64174

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