Skip to content

Support TypeScript 7 - #258

Open
CyberDex wants to merge 5 commits into
mainfrom
claude/typescript-7-support-aef607
Open

CyberDex wants to merge 5 commits into
mainfrom
claude/typescript-7-support-aef607

Conversation

@CyberDex

@CyberDex CyberDex commented Aug 26, 2026 •

Copy link
Copy Markdown
Member

What

Makes the project compile under TypeScript 7, the native compiler rewrite, and adds the checks that keep the published declarations usable there.

Rebased on main. #285 landed the ScrollBox NodeJS.Timeout fix independently, so that part of this branch merged away — @pixi/[email protected] ships clean of it. What remains is the part main still lacks.

The blocker

TypeScript 7 removed moduleResolution: "node" (node10), and main still sets it. The compiler refuses the project before reading a single source file:

tsconfig.json(9,29): error TS5108: Option 'moduleResolution=node10' has been removed.

The sources are bundled by rollup, so "bundler" is the mode that matches how they're consumed, and it needs no changes to import specifiers (unlike node16/nodenext). With that one line changed, main's sources — Drawer, the input package, Signal and all — type-check clean on 7.0.2.

Why TypeScript 7 is installed alongside 5.x, not instead of it

typescript@7 ships a native binary and a thin wrapper — the classic JS compiler API is gone (its only main export is lib/version.cjs). eslint, ts-jest and storybook's fork-ts-checker all reach for that API, so a straight bump takes the toolchain down. Confirmed by trying it:

TypeError: Failed to load plugin '@typescript-eslint': Cannot read properties of undefined (reading 'Intrinsic')
    at .../ts-api-utils/lib/index.cjs:779:57

So 7.x goes in under an alias:

"typescript": "^5.8.3",
"typescript-7": "npm:typescript@^7.0.2"

node_modules/.bin/tsc keeps resolving to 5.x (verified deterministic across install orders and npm ci), so xs lint, xs types and xs build are untouched. The new scripts invoke 7.x by path. Nothing new is published — the alias is a devDependency, and the platform binaries are optional entries in the lockfile, so each CI runner only downloads its own.

What's checked now

  • npm run types — sources, through both compilers.
  • npm run types:dist (new CI step, after build) — the built declarations, two ways.

Content. consumer.ts imports the root entry with skipLibCheck off and no ambient @types. That's what gives the check teeth: a bad type on a protected member only ever surfaces as a diagnostic inside a .d.ts file, so skipLibCheck: true hides it completely — which is exactly how the NodeJS.Timeout leak reached npm and had to be fixed reactively in #285. Since the root entry re-exports every module, naming it pulls the whole declaration output into the program.

Wiring. #286 added seventeen per-component subpath exports, each promising its own types target, and nothing verifies those point at files that were built. A typo in a subpath's types doesn't fail a build — the import quietly resolves to any in the consumer's project. check.mjs now walks the exports map and fails on any entry whose declaration is missing.

npm run release runs the check between build and publish, so this guards the path the bug actually escaped through, not just pull requests.

Both failure modes verified:

Scenario Exit
clean 0
NodeJS.Timeout reintroduced into lib/ScrollBox.d.ts 1
exports entry pointing at a missing declaration 1

Because skipLibCheck is off, dependency declarations get checked too — 181 diagnostics against the pinned pixi.js 8.6.2, which this repo can't fix. check.mjs counts and reports them but only fails on diagnostics from files inside the repo, so an upstream regression can't block a release here.

Unrelated one-liner, but it's blocking CI

Main's lint is currently red, and it takes every PR with it: scripts/build-umd.mjs from #286 reports to stdout, and xs lint runs --max-warnings 0.

/home/runner/work/ui/ui/scripts/build-umd.mjs
  57:1  warning  Unexpected console statement  no-console
✖ 1 problem (0 errors, 1 warning)
ESLint found too many warnings (maximum: 0).

Silenced here with the same disable comment used elsewhere in the repo, only so this PR can be verified — probably worth landing on main directly. Worth noting testing.yml only runs on: [pull_request], so nothing catches this after a merge.

On pixi.js and TypeScript 7

Measured by installing published @pixi/ui next to pixi.js and compiling a consumer with skipLibCheck off:

pixi.js TypeScript 7.0
8.6.2 (this repo's dev pin) 185 errors
8.20.1 183 errors
8.21.0+ clean

pixi.js v8.21.0 dropped the @webgpu/types reference that collided with the WebGPU types now built into TypeScript 7's lib.dom.d.ts. So pixi.js is not a blocker for TypeScript 7 consumers — but this repo develops against 8.6.2, sixteen minors behind. Updating that pin is a migration of its own (8.22.0 tightens Container.parent and maxWidth to nullable — six new strict-null errors — and breaks the jest transform), so it's deliberately left for a separate PR.

Verification

Full CI sequence on the merged branch, clean install:

Step Result
npm run lint pass
npm run types (5.x + 7.x) pass
npm run test pass
npm run docs pass
npm run build pass
npm run types:dist pass — 18 exports entries

🤖 Generated with Claude Code

TypeScript 7 is the native compiler rewrite, and two things in this repo stop
it dead:

- `moduleResolution: "node"` (node10) was removed outright, so `tsc` refuses
  the project before it reads a single source file. The sources are bundled by
  rollup, so `"bundler"` is the mode that matches how they are consumed.
- `ScrollBox` typed a timeout handle as `NodeJS.Timeout`. That only ever
  resolved because TypeScript 5.x silently includes every `@types` package it
  can find, and `@types/node` is a devDependency here. TypeScript 7 does not,
  so it fails - and because the member is `protected`, the name is emitted into
  `lib/ScrollBox.d.ts`, which means the published declarations have been asking
  consumers for `@types/node` all along. `ReturnType<typeof setTimeout>` says
  the same thing without reaching for a Node global.

To keep it working, TypeScript 7 is installed alongside 5.x rather than
replacing it: eslint, ts-jest and storybook all reach for the compiler's JS
API, which 7.x no longer ships, so a straight bump breaks the toolchain. The
alias keeps `node_modules/.bin/tsc` pointing at 5.x - `xs lint`, `xs types` and
`xs build` are untouched - and the new scripts invoke 7.x by path.

- `npm run types` now checks the sources with both compilers.
- `npm run types:dist` checks the emitted `lib/*.d.ts` with 7.x, through the
  entry point consumers import, with no ambient `@types` and `skipLibCheck`
  off. That last part is what would have caught the `NodeJS.Timeout` leak: a
  bad type on a `protected` member only ever surfaces as a diagnostic inside a
  `.d.ts` file. It also drags pixi.js's own declarations into the check, and
  those are not clean on 7.x - pixi.js references `@webgpu/types`, which now
  collides with the WebGPU types built into `lib.dom.d.ts` - so diagnostics
  from outside this repo are counted and reported but not treated as failures.

Co-Authored-By: Claude Opus 5 <[email protected]>
@CyberDex
CyberDex marked this pull request as draft September 30, 2026 15:42
CyberDex and others added 3 commits October 2, 2026 11:30
The `@webgpu/types` collision was described as an open pixi.js problem. It is
not: pixi.js v8.21.0 dropped the `@webgpu/types` reference from its non-legacy
declarations, and a consumer on it compiles clean under TypeScript 7 with
`skipLibCheck` off. The hundreds of diagnostics this repo sees come from the
pixi.js it pins for development - 8.6.2, sixteen minors behind what `^8.6.2`
resolves to for anyone installing today.

Measured against the published `@pixi/[email protected]` with `skipLibCheck` off:

  pixi.js 8.6.2    TypeScript 7   185 errors
  pixi.js 8.20.1   TypeScript 7   183 errors
  pixi.js 8.21.0   TypeScript 7     1 error
  pixi.js 8.22.0   TypeScript 7     1 error

That last error is ours - the `NodeJS.Timeout` leak this branch fixes. So on
current pixi.js it is the only thing standing between a consumer and a clean
TypeScript 7 build, rather than one problem among many.

Updating the pinned pixi.js is left alone deliberately: it is a migration of
its own, not a TypeScript 7 change. 8.22.0 tightens `Container.parent` and
`maxWidth` to nullable, which is six new strict-null errors in `List`,
`Slider`, `SliderBase` and `DoubleSlider`, and it also breaks the jest
transform.

Co-Authored-By: Claude Opus 5 <[email protected]>
… not

Merging main makes half this branch redundant and the other half more
worthwhile.

#285 already changed `ScrollBox`'s timeout handle to
`ReturnType<typeof setTimeout>`, identically, so that part merged away and
`@pixi/[email protected]` ships clean of the Node global. What main still has is
`moduleResolution: "node"`, which TypeScript 7 removed, so the project does not
compile under it at all. That, and the checks, are what is left here.

The declaration check reads better against main than it did against 2.3.2: #286
added seventeen per-component subpath exports, each promising its own `types`
target, and nothing verifies those point at files that were actually built. A
subpath with a typo in `types` does not fail a build - the import quietly
resolves to `any` in the consumer's project. So check.mjs now walks the
`exports` map and fails on any entry whose declaration is missing, while
consumer.ts keeps covering declaration *content* through the root entry, which
re-exports every module.

Also updates consumer.ts for `Drawer`, `Signal` and the `input` subpath, and
adds a `paths` wildcard mirroring how `exports` resolves subpaths.

Unrelated, but main's lint is red and takes this PR's CI with it:
`scripts/build-umd.mjs` from #286 reports to stdout, and `xs lint` runs with
`--max-warnings 0`. Silenced with the same disable comment used elsewhere in
the repo. Worth landing on main directly - `testing.yml` only runs on
pull_request, so nothing catches this after a merge.

Co-Authored-By: Claude Opus 5 <[email protected]>
@CyberDex
CyberDex marked this pull request as ready for review October 2, 2026 09:32
`types:dist` only guarded pull requests, so the hole it exists to close was
still open where it matters: `npm run release` goes straight from `build` to
`publish`. A bad declaration reaching npm is exactly how `NodeJS.Timeout`
shipped and had to be fixed after the fact in #285.

The build output is already there by that point, so it costs a fraction of a
second and `release` stops before `publish` rather than after.

Co-Authored-By: Claude Opus 5 <[email protected]>

@CatchABus CatchABus left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can proceed with this but I'd treat it as a workaround solution until TS 7 is mature enough and the mentioned packages finally support it.

This branch has not been deployed

No deployments
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.

2 participants