Skip to content

feat(build): compile with the TypeScript 7 native compiler - #11793

Open
raymondfeng wants to merge 2 commits into
masterfrom
upgrade-typescript-7
Open

raymondfeng wants to merge 2 commits into
masterfrom
upgrade-typescript-7

Conversation

@raymondfeng

Copy link
Copy Markdown
Contributor

Upgrades the monorepo to TypeScript 7, using the side-by-side install recommended
for TypeScript 7:

"@typescript/native": "npm:typescript@~7.0.2",
"typescript": "npm:@typescript/typescript6@~6.0.2"

TypeScript 7 no longer ships the JS compiler API — its exports["."] resolves to
lib/version.cjs — so anything that introspects the compiler (@typescript-eslint,
editor language services, tsserver) needs the API that @typescript/typescript6
still provides. The two packages declare different bin names (tsc vs tsc6), so
they coexist with no collision.

packages/build/package.json is the source of truth; bin/sync-dev-deps propagates
the spec to every workspace package, and bin/update-template-deps carries it into
the lb4 scaffolding templates. lb-tsc prefers the native compiler and falls back
to typescript/lib/tsc when @typescript/native is absent, so downstream apps get
the faster build without a hard requirement on TypeScript 7.

A cold build of the monorepo drops from ~10.7s to ~2.1s.

TypeScript 6/7 changes that required source updates

  • moduleResolution: node (node10) was removed. Dropped from
    tsconfig.common.json and from the CLI project template. The default resolver for
    module: commonjs honors exports maps, so @loopback/graphql no longer imports
    Middleware from a deep type-graphql path (that path is not exported) and
    declares the type itself.
  • @types/* packages are no longer auto-included. This accounted for 5630 of the
    5668 initial errors (describe, process, NodeJS all unresolved);
    tsconfig.common.json now lists types: ["node", "mocha"].
  • Reading a parent class field through super is an error (TS2855). cron
    declares fireOnTick as a field but installs it on the prototype, so
    @loopback/cron now calls through BaseCronJob.prototype.
  • Declaration emit no longer adds /// <reference path> for ambient files.
    TypeScript 5.2 emitted one into testlab/dist/expect.d.ts, which is how consumers
    resolved the global Internal type. Without it, expect silently becomes any in
    every consuming package and api-extractor fails with
    Unable to follow symbol for "Internal". should-as-function.d.ts therefore moves
    to src/should-as-function.ts and exports its types as a module.

Lint

@typescript-eslint moves to v8 — v7 caps at typescript <5.6.0 and does not support
TypeScript 6. Rules that v8 renamed or split are disabled to preserve the rule set this
config already had (no-var-requiresno-require-imports, ban-types → its three
successors); that covered 1135 of 1232 findings. The remaining 97 are fixed in code.

v8's no-misused-promises also crashed on cli-main.js, which used a top-level
CommonJS return — the rule assumes every return has an enclosing function. That is
restructured as if/else.

Defects found along the way

  • belongs-to-repository-factory.unit.ts never assigned companyRepo; its stub helper
    assigned customerRepo twice, clobbering the previous beforeEach.
  • rest.application.ts declared the same route(verb, path, spec, handler) overload
    twice.
  • lb4 app generated process.env.HOST || '127.0.0.1', so every newly scaffolded app
    failed its own npm run lint. The adjacent PORT line already used ??.
  • bin/sync-dev-deps.js had been broken since the Lerna → npm workspaces migration
    (d5c4994 removed loadLernaRepo); repaired with the @npmcli/map-workspaces
    pattern already used by update-template-deps.js, since propagating the new spec
    depends on it.

Behavior notes

The ||?? conversions touch framework code. No boolean operand was converted, so
false ?? true cannot bite. Two numeric operands now honor 0:
examples/rpc-server (port: 0 means an ephemeral port) and http-caching-proxy
(error.statusCode). Nine string sites now honor an explicit ''. The rest are arrays
and are equivalent.

Verification

Check Result
Cold build clean, ~2.1s (was ~10.7s)
CI package tests 3572 passing, 0 failing
CI extension tests 233 passing, 0 failing
CI example tests 346 passing, 0 failing
npm run lint clean
api-extractor / tsdocs clean; docs/apidocs output byte-identical
npm ci + check-package-metadata clean
Scaffolded lb4 app builds on TS7; tests, eslint and prettier clean

Pre-existing and unrelated: packages/cli/test/** generator tests time out locally
(CI does not run that glob), and packages/cli/.yo-rc.json is stale — --meta
regenerates 126 lines of difference with or without this change.

Checklist

  • DCO (Developer Certificate of Origin) signed in all commits
  • npm test passes on your machine
  • New tests added or existing tests modified to cover all changes
  • Code conforms with the style guide
  • API Documentation in code was updated
  • Documentation in /docs/site was updated
  • Affected artifact templates in packages/cli were updated
  • Affected example projects in examples/* were updated

🤖 Generated with Claude Code

Install TypeScript side by side, as recommended for TypeScript 7: `typescript`
is aliased to `@typescript/typescript6` so that tools consuming the JS compiler
API keep working, and `@typescript/native` provides the native compiler that
`lb-tsc` runs. A cold build of the monorepo drops from ~10.7s to ~2.1s.

`lb-tsc` prefers the native compiler and falls back to `typescript/lib/tsc` when
`@typescript/native` is not installed.

TypeScript 6/7 changes that required source updates:

- `moduleResolution: node` (node10) was removed. It is dropped from
  `tsconfig.common.json` and from the CLI project template. The default resolver
  for `module: commonjs` honors `exports` maps, so `@loopback/graphql` no longer
  imports `Middleware` from a deep `type-graphql` path and declares it instead.
- `@types/*` packages are no longer included automatically, so
  `tsconfig.common.json` now lists `types: ["node", "mocha"]`.
- Reading a parent class field through `super` is an error (TS2855), so
  `@loopback/cron` calls `fireOnTick` through `BaseCronJob.prototype`.
- Declaration emit no longer adds `/// <reference path>` for ambient files, so
  `@loopback/testlab` exports the should.js types from a module rather than a
  global declaration file. Without it, `expect` resolves to `any` in every
  consuming package and api-extractor cannot follow the `Internal` symbol.

`@typescript-eslint` is upgraded to v8, v7 does not support TypeScript 6. Rules
that v8 renamed or split are disabled to keep the rule set this configuration
already had, and the remaining new findings are fixed.

Two defects surfaced by the stricter compiler are fixed as well:
`belongs-to-repository-factory.unit.ts` never assigned `companyRepo` and
clobbered `customerRepo` instead, and `rest.application.ts` declared the same
`route()` overload twice.

BREAKING CHANGE: `@loopback/build` compiles with TypeScript 7 when
`@typescript/native` is installed and with TypeScript 6 otherwise. Projects
extending `@loopback/build/config/tsconfig.common.json` have to remove
`moduleResolution: node` and list ambient type packages in `types`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Raymond Feng <enjoyjava@gmail.com>
Comment thread fixtures/mock-oauth2-provider/src/mock-oauth2-social-app.ts Fixed
Comment thread fixtures/mock-oauth2-provider/src/mock-oauth2-social-app.ts Fixed
…n redirect

Registered apps and issued tokens were held in plain objects keyed by values
taken from the request, so a `__proto__` key reached `Object.prototype`. They
are `Map`s now, which also drops the `[key: string]: any` index signature from
the `App` interface (CodeQL js/prototype-polluting-assignment).

`redirect_uri` is validated before redirecting and the callback url is built
from the parsed `URL` rather than by concatenating the request value. A real
authorization server matches `redirect_uri` against the callback urls
registered for the client; this provider only ever serves test applications
running on the same machine, so it accepts loopback hosts
(CodeQL js/server-side-unvalidated-url-redirection).

Both findings predate the TypeScript 7 upgrade - they were reported on this
pull request only because wrapping the handlers re-indented the lines.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Raymond Feng <enjoyjava@gmail.com>
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