Repository navigation
build: migrate from tsc to tsdown - #188
Conversation
|
Important Review skippedToo many files! This PR contains 138 files, which is 38 over the limit of 100. To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch. Upgrade to a paid plan to raise the limit. This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry.
|
@standard-server/aws-lambda
@standard-server/core
@standard-server/fastify
@standard-server/fetch
@standard-server/node
@standard-server/peer
@standard-server/shared
commit: |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
Generated by Claude Code |
7202587 to
0c805fc
Compare
Hooray! CodSpeed harness just leveled up!The base and head of this comparison were measured with different runner settings, so their benchmark values are not directly comparable. What changed between base and head:
Re-run the base with the same settings to get a valid performance comparison. Comparing Footnotes |
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
- Build pipeline: every
packages/*/package.jsonprepacknow runstsdown, driven by one roottsdown.config.tsthat derives entries from each package'sexports, keeps one output file per module (unbundle), externalizes dependencies (neverBundle+onlyImport), and emits declarations with the oxc generator. Builds for all seven packages succeed and the packed tarballs expose the dist-basedexports. - Module resolution: root
tsconfig.jsonmoves tomodule: preserve+moduleResolution: bundler, dropsrewriteRelativeImportExtensions, and relative imports across packages, tests, and benches drop their.tsextension. No stale.ts-extension ortscreferences remain outside the standalone playgrounds. AsyncIteratorClass:[Symbol.asyncDispose]is declared on a merged interface and installed on the prototype viaObject.definePropertyafter the class, delegating toreturn(). The descriptor matches a normal class method (writable, non-enumerable, configurable) and cleanup still reports{ kind: 'cancelled' }.- Cleanup: emit-only options are removed from
tsconfig.lib.json, andoxlint/oxlint-tsgolint/oxfmtare bumped.
Verified locally: pnpm check is clean, vitest run passes 1487 tests, all package builds succeed, and a nodenext consumer with skipLibCheck: false type-checks and runtime-imports packed shared/core/fetch/peer. The dropped declarations (core's dist/event-stream/index.*, peer's body.d.ts/octet-stream.d.ts) are not referenced by any emitted .js or .d.ts.
deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes (incremental)
.github/workflows/codspeed.yamlis the only delta since the prior review:CodSpeedHQ/action@v4→@v5. The v5 action still acceptsmode: simulationandrun, and the job already grantsid-token: writefor OIDC, so the workflow remains valid.
deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏
Build every package with tsdown from one shared root tsdown.config.ts. Each package's prepack runs `tsdown`, which finds the root config; entries come from the package's exports. unbundle keeps one output file per source module, as tsc did, and packages stay external. - Generate declarations with oxc. AsyncIteratorClass's asyncDispose method is declared on a merged interface and defined after the class, because isolated declarations can't infer a class member keyed by a conditional type. It now calls return(), as the spec's AsyncIterator dispose does. - Fail the build when dist imports a package that isn't in the package's dependencies or peerDependencies, plus node: built-ins (deps.onlyImport). - Switch the root tsconfig to module preserve with bundler resolution and drop the .ts extension from relative imports. - Remove the tsc-only tsconfig.lib.json options. - Bump oxlint, oxlint-tsgolint and oxfmt. - Bump CodSpeedHQ/action to v5. v4 installs a CodSpeed runner that exits with "Unsupported system" on the ubuntu-26.04 image ubuntu-latest now uses; openapi-spec already runs v5 there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VVaw7RpPRpasxuSAB8fbND
0c805fc to
ad98793
Compare
|
Both checks passed on the previous commit Generated by Claude Code |
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
platformswitched toneutral: the roottsdown.config.tsno longer forcesplatform: 'node'. Verified locally that all seven packages still build and thatneutraloutput is byte-identical to the previousnodeoutput for thenode,fetch,core, andpeerpackages, including declarations.fixedExtensionremoved rather than lost: tsdown's default isfixedExtension = platform === 'node', so withneutralit still defaults tofalseand emits.js/.d.ts(confirmed against the tsdown 0.23.7 source and an actual build — no.mjsoutput, sopublishConfig.exportsstays valid).node:imports explicitly allowlisted: underneutral, tsdown no longer auto-allows Node built-ins, so/^node:/was added todeps.onlyImport. Confirmed the pattern matchesnode:fs/node:fs/promisesthrough tsdown'sparsePackageSpecifier+ regex path, and thatnode/aws-lambdabuild cleanly.
pnpm check is clean. The /^node:/ entry applies repo-wide, so a stray node:* import in a browser-targeted package would not trip the guard — that hole predates this delta (the old node platform auto-allowed builtins everywhere too), so it is not a regression.
deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏
## Why Builds move from per-package `tsc` to tsdown, driven by one shared config at the root. Output keeps tsc's shape (one `.js` and `.d.ts` per source module), so `publishConfig.exports` and `sideEffects` stay as they are. Same migration as middleapi/standard-server#188 and middleapi/openapi-spec#395, which use a simpler config. ## Changes - **One root `tsdown.config.ts`.** tsdown finds it from each package directory, so every `prepack` is just `tsdown`. Entries come from each package's `exports`. `unbundle` keeps one file per module; relative imports like `../package.json` are still bundled in (`@orpc/opentelemetry` inlines only `name` and `version`), and packages stay external. - **Declarations come from oxc** (`dts: { generator: 'oxc' }`). The tsgo generator drops the `DecoratedProcedure` import from `@orpc/effect`'s `extensions/effect.d.ts`, because it is used only inside a `declare module '@orpc/server'` augmentation. That is the bug reported in #2446 (comment 19474307). oxc needs explicit types on exports. Only `@orpc/shared`'s regex constants lacked them; a missing annotation now fails the build. - **Re-exports stay tree-shakeable.** Rolldown writes re-exports as `import { a } from 'x'; export { a }`, which Expo's tree shaking can't see through (the problem behind #2362). A small `renderChunk` hook in the config turns them back into `export { a } from 'x'`, in the import's position so evaluation order is unchanged. tsdown and rolldown have no option for this. - **Undeclared imports fail the build.** `deps.onlyImport` lists each package's `dependencies` and `peerDependencies` plus the runtime modules (`bun`, `bun:*`, `node:*`, `cloudflare:*`). Any `.js` or `.d.ts` in `dist` that imports something else fails the build, including CommonJS-only packages and type-only imports. - **Runtime modules are marked external** through rolldown's `external` option, using the same list. `neverBundle: true` keeps Node built-ins and npm-style names as written, but other `prefix:` modules (e.g. `cloudflare:workers`) fall through to resolution and warn that they can't be resolved. - **Bundler module resolution.** The root tsconfig uses `module: preserve` with `moduleResolution: bundler`, and the `.ts`/`.tsx` extension is dropped from relative imports across packages, tests, benches and the docs site. `dist` still imports with `.js` extensions. `verbatimModuleSyntax` stays: without it `pnpm check` misses type-only re-exports, which then only fail at build time. - **Cleanup and bumps.** The emit-only options in `tsconfig.lib.json` and the bun/cloudflare/nest `tsconfig.build.json` files are gone. oxlint, oxlint-tsgolint, oxfmt and `@orpc/nest`'s `@oxc-project/runtime` are bumped. - **JSDoc kept under oxc.** `ORPCModule` is written as `export @module({}) class`, since oxc drops a class's JSDoc that sits above its decorators. `TmpFile.path` is now a documented field rather than a documented constructor parameter. ## Verification - `pnpm check`, `pnpm test` and `pnpm docs:validate` pass, and all 29 packages build without warnings. - Every relative import in `dist` resolves, and every `publishConfig.exports` and `sideEffects` path exists. - The emitted declarations type-check for a `nodenext` consumer with `skipLibCheck: false`. - **#2446 reproduction**, built against `dist`: with tsgo declarations, a missing required context and a wrong input type both compile. With oxc both are rejected, matching the source types. - **Declaration diff, tsgo vs oxc:** the same 312 files, 295 identical apart from formatting. The rest differ only in kept `override` modifiers, quoted member names, and three file-level comments in `json-schema` that aren't attached to any declaration. - **Re-export hook:** it rewrites 44 of 309 `.js` files and no `.d.ts`. Loading each rewritten file in Node gives the same export names and value types as a build without the hook. - **`onlyImport`:** adding an undeclared `valibot` (runtime and type-only) or `change-case` import to `@orpc/zod` fails the build with `… is imported in index.js but is not included in deps.onlyImport option`. A `bun:sqlite` import in `@orpc/bun` builds cleanly and stays an import. - **Expo SDK 58** (Android export, `EXPO_UNSTABLE_TREE_SHAKING=1`), importing only `OpenAPILink` and the client: | Build | Bundle | Server-only markers | |---|---|---| | tsc (beta.49) | 1,024,626 B | 0/12 | | tsdown, no hook | 1,109,354 B | 12/12 | | this PR | 1,027,558 B | 0/12 | ## Behavior changes - `dist` no longer contains empty `.js` files for type-only modules, nor `.d.ts` files that no public type refers to (for example `router-hidden.d.ts`). Nothing imports them. - Declarations no longer contain non-JSDoc comments. - `@orpc/nest` bundles its decorator helpers under `dist/_virtual/_@oxc-project_runtime@0.157.0/…`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01VVaw7RpPRpasxuSAB8fbND Co-authored-by: Claude <noreply@anthropic.com>

Why
Builds move from per-package
tscto tsdown with one shared root config, the same migration as middleapi/orpc#2448 in a simpler form. This repo needs neither oRPC's re-export hook for Expo tree shaking nor its runtime-module list. Output keeps tsc's shape (one.jsand.d.tsper source module), sopublishConfig.exportsstays as it is.Changes
tsdown.config.ts. tsdown finds it from each package directory, so everyprepackis justtsdown. Entries come from each package'sexports;unbundlekeeps one file per module, and packages stay external (neverBundle: true).platform: 'neutral', as in the other two repos.dts: { generator: 'oxc' }), which needs explicit types on exports. A missing one now fails the build.AsyncIteratorClass[asyncDisposeSymbol]. Isolated declarations can't infer a class member keyed byasyncDisposeSymbol, whose type is conditional. So the member is declared on a merged interface, and the method is defined after the class withObject.defineProperty. It callsthis.return(), as the spec'sAsyncIterator.prototype[Symbol.asyncDispose]does; the old body was a copy ofreturn()minus the return value. It is still a non-enumerable, writable, configurable prototype method.deps.onlyImportlists each package'sdependenciesandpeerDependencies, plusnode:built-ins (thenodeandaws-lambdapackages import them).module: preservewithmoduleResolution: bundler, and the.tsextension is dropped from relative imports in packages, tests and benches.diststill imports with.jsextensions.verbatimModuleSyntaxstays: without itpnpm checkmisses type-only re-exports, which then only fail at build time.tsconfig.lib.jsonare gone; oxlint, oxlint-tsgolint and oxfmt are bumped.CodSpeedHQ/actionmoves to v5, because v4's runner exits withUnsupported systemon theubuntu-26.04image thatubuntu-latestnow uses.Verification
pnpm check,pnpm test(1,487 vitest + 52 bun) and all 7 package builds pass without warnings.anyAbortSignal.nodenextconsumer withskipLibCheck: false.await usingandconst d: AsyncDisposable = iteratortype-check for consumers, and theSymbol.for('Symbol.asyncDispose')fallback test passes.platform: 'neutral'produces output byte-identical to'node'for all 7 packages. An undeclared import (srvxin@standard-server/fetch) fails the build, whilenode:and declared imports pass..jscompiles under@react-native/babel-preset.Behavior changes
distno longer contains empty.jsfiles for type-only modules, nor.d.tsfiles that no public type refers to. Nothing imports them.@standard-server/coreno longer shipsdist/event-stream/index.*; the root entry re-exports from the modules directly. It isn't an export path.Generated by Claude Code