Skip to content

EPBDS-16211 E0: wire up TypeScript, zero behaviour change - #54

Merged
AlexSamBY merged 1 commit into
masterfrom
e0-typescript-infra
Sep 17, 2026
Merged

AlexSamBY merged 1 commit into
masterfrom
e0-typescript-infra

Conversation

@AlexSamBY

Copy link
Copy Markdown
Member

§9.6's whole track was blocked on this: an imported .ts would not build today, so nobody could convert even one file. No product code changes.

babel.config.js @babel/preset-typescript, listed last so it runs first — presets apply in reverse, and types must be stripped before preset-env/preset-react parse the file
tsconfig.json new, checker-only: strict, noEmit, allowJs + checkJs: false, isolatedModules, experimentalDecorators, jsx: react
npm run typecheck tsc --noEmit, a CI step in verify beside the linters
lint:js --ext now includes .ts,.tsx

checkJs: false is what makes the migration incremental: unconverted JS resolves but is not checked, so a .ts file may import any of the ~330 .js files without demanding they be converted first — while every converted file is strict from its first day.

Babel 7, deliberately

@babel/preset-typescript@8 peer-requires @babel/core@^8 and this project is on core 7, so the current default install fails with ERESOLVE. Pinned to ^7 rather than reaching for --legacy-peer-deps — that flag would install a Babel-8 preset against a Babel-7 core, papering over a real mismatch instead of resolving it.

Two things the probe found, which is what it is for

.test.ts had no Jest globals typed. The first TypeScript test in E1 would have broken the gate on day one. @types/jest added, matched to the installed major — types 30 ↔ Jest 30.3.0; npm's default ^29 would have been a major behind.

lint:js ran --ext .js,.jsx, so every .ts file would have slipped past the zero-warning gate. The ESLint config already listed src/**/*.ts in its overrides and eslint-config-react-app already ships @typescript-eslint — only the CLI flag excluded them. Side effect: src/library/types/index.ts and UIRender.tsx are linted for the first time (0 problems).

The probe is now a permanent guard

Item 4 asked for a probe, run and discarded. Discarding it leaves no .ts under include — so the gate would be green over zero TypeScript, which looks exactly like green over working TypeScript, right up until someone tries to convert something.

src/toolchain/typescriptSupport.ts + its test stay instead. Both pipelines read them: tsc proves the checker sees .ts, Jest proves Babel strips it. Verified to fail when the preset is removed, and to pass when restored. It sits outside src/core/src/library so it is outside collectCoverageFrom, nothing imports it, and it is confirmed absent from dist/index.js. Its own comment says to delete it once real converted modules cover the same ground.

This is the one place I went beyond the letter of E0 — say the word and I will drop it back to a throwaway.

Measured

typecheck 1.6 s over 375 source files (329 .js, 11 .jsx, 35 .json)
non-vacuous a deliberately mistyped file makes it exit non-zero; green again when removed
suite 166 → 167 suites, 2527 → 2529 tests — exactly the guard
coverage 97.04 / 94.06 / 96.88 / 97.76, unchanged; thresholds green
CSS static/all.css sha256 unchanged, dcdb0a40a6d8ee79…

The throwaway probe, wired into src/library/index.js, passed typecheck, Jest, build-lib and the demo build — and its output string was found in dist/index.js, proving TypeScript really compiles into the published bundle rather than being silently skipped.

A correction to the plan's own text

E0 said "all four pipelines". There are three — library build, demo build, Jest. watch-lib stopped being a fourth when webpack.watch.config.mjs was deleted under H7. Recorded in the plan.

Next gate is the plan's own: go/no-go after E1, on measured conversion velocity, before committing to the E2/E3 tail.

🤖 Generated with Claude Code

§9.6's whole track was blocked on this: an imported .ts would not build
today, so nobody could convert even one file. No product code changes.

  babel.config.js   @babel/preset-typescript, listed LAST so it runs FIRST
                    (presets apply in reverse) and types are stripped before
                    preset-env and preset-react parse the file.
  tsconfig.json     new, checker-only. strict, noEmit, allowJs + checkJs:false
                    (unconverted JS resolves but is not checked, which is what
                    makes the migration incremental), isolatedModules,
                    experimentalDecorators, jsx: react.
  npm run typecheck tsc --noEmit, a CI step in `verify` beside the linters.
  lint:js           --ext now includes .ts,.tsx.

BABEL 7, DELIBERATELY. @babel/preset-typescript@8 peer-requires
@babel/core@^8 and this project is on core 7, so the current default needs
--legacy-peer-deps. That would install a Babel-8 preset against a Babel-7
core: papering over a real mismatch, not resolving it. Pinned to ^7.

TWO THINGS THE PROBE FOUND, which is what it is for

  .test.ts had no Jest globals typed, so the first TypeScript test in E1
  would have broken the gate on its first day. @types/jest added, matched to
  the installed major (types 30 <-> Jest 30.3.0; npm's default ^29 would have
  been a major behind).

  lint:js ran --ext .js,.jsx, so every .ts file would have slipped past the
  zero-warning gate. The ESLint config ALREADY listed src/**/*.ts in its
  overrides and eslint-config-react-app already ships @typescript-eslint —
  only the CLI flag excluded them. Side effect: src/library/types/index.ts
  and UIRender.tsx are linted for the first time (0 problems).

THE PROBE IS NOW A PERMANENT GUARD, not a throwaway

Item 4 asked for a probe, run and discarded. Discarding it leaves no .ts
under `include`, so the gate would be green over ZERO TypeScript — which
looks exactly like green over working TypeScript, until someone tries to
convert something. src/toolchain/typescriptSupport.ts + its test stay
instead. Both pipelines read them: tsc proves the checker sees .ts, Jest
proves Babel strips it. Verified to FAIL when the preset is removed, and to
pass when it is restored. Outside src/core and src/library, so outside
collectCoverageFrom; imported by nothing, and confirmed absent from
dist/index.js. Delete it once real converted modules cover the same ground.

MEASURED

  typecheck  1.6 s over 375 source files (329 .js, 11 .jsx, 35 .json).
             Non-vacuous: a mistyped file makes it exit non-zero.
  suite      166 -> 167 suites, 2527 -> 2529 tests — exactly the guard.
  coverage   97.04 / 94.06 / 96.88 / 97.76, unchanged; thresholds green.
  CSS        static/all.css sha256 unchanged, dcdb0a40a6d8ee79...
  pipelines  the throwaway probe, wired into src/library/index.js, passed
             typecheck, Jest, build-lib and the demo build, and its output
             string was found in dist/index.js — TypeScript really compiles
             into the published bundle rather than being skipped.

A correction to the plan's own text, recorded there: E0 said "all four
pipelines". There are three — library build, demo build, Jest. watch-lib
stopped being a fourth when webpack.watch.config.mjs was deleted under H7.
@AlexSamBY
AlexSamBY merged commit da6e8ff into master Sep 17, 2026
5 checks passed
@AlexSamBY
AlexSamBY deleted the e0-typescript-infra branch September 17, 2026 14:27
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