Repository navigation
feat(build): migrate plugin and CLI builds to tsdown - #858
Conversation
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
React Doctor found 1 new issue in 1 file · 1 warning · score 93 / 100 (Great) · 0 fixed · vs Reviewed by React Doctor for commit |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f993eb2561
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| "oxlint-tsgolint": versionsPackageJson.oxlintTsgolint, | ||
| }), | ||
| "tsc-alias": versionsPackageJson.tscAlias, | ||
| tsdown: versionsPackageJson.tsdown, |
There was a problem hiding this comment.
Align generated plugins with the supported Node range
Generated plugins now install tsdown@^0.23.0, whose lockfile declares Node ^22.18.0 || ^24.11.0 || >=26.0.0, while the main setup documentation still supports every Node 22 release. On Node 22.0–22.17, engine-strict package managers reject the generated project, and otherwise its required build tool runs on an unsupported runtime, so vitnode build/dev cannot be relied on. Either select a compatible tsdown release or raise and enforce the project's Node requirement consistently.
Useful? React with 👍 / 👎.
… tsdown - vitnode build compiles plugin/adapter packages with tsdown in unbundle mode (shared VitNode-owned config), with tsc --noEmit type checking alongside - vitnode dev runs tsc --watch plus separate tsdown watchers for JavaScript and declarations, restarting on added/removed sources - core CLI/scripts build with tsdown.config.ts instead of tsup - plugin detection no longer requires .swcrc; .swcrc files, SWC, tsc-alias (outside API apps) and tsup removed; generator and versions updated - integration tests for builds, watch lifecycle, built CLI and generated plugins - docs: plugin build/watch explanation and SWC migration guide Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DspbmiWQwLSS7JbZQrPYJT
f993eb2 to
c3e4e0b
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c3e4e0b108
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| */ | ||
| export const listPackageEntries = (root: string): string[] => | ||
| listSourceFiles(root).filter( | ||
| file => /\.tsx?$/.test(file) && !DECLARATION.test(file), |
There was a problem hiding this comment.
Compile all TypeScript module extensions classified as code
When a plugin contains an unreferenced .mts or .cts module, this filter omits it from tsdown's entries, while SCRIPT also prevents it from being copied as an asset. The type check can therefore pass and the build report success without producing the module under dist/src—and pruning can delete its previous output—even though these extensions are explicitly recognized elsewhere as scripts and declarations. Include the supported TypeScript module extensions in the entry filter.
Useful? React with 👍 / 👎.
| try { | ||
| await sync(); |
There was a problem hiding this comment.
Serialize the initial watcher build with queued restarts
If an entry or asset changes while the initial tsdown build is pending for longer than the 200 ms debounce, the filesystem callback queues sync() on the already-resolved pending promise while this direct sync() is still running. Both builds can then overlap; because current is assigned only after each build resolves, one handle can be overwritten without being closed, leaving duplicate watchers rebuilding and writing the same output. Put the initial synchronization onto the same pending chain used by restarts.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a63546afb9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| for (const entry of readdirSync(current)) { | ||
| const path = join(current, entry); | ||
| if (statSync(path).isDirectory() && removeEmpty(path)) continue; | ||
| empty = false; | ||
| } | ||
| if (empty && current !== join(outDir, dir)) rmdirSync(current); |
There was a problem hiding this comment.
Make directory pruning safe across parallel watchers
When vitnode dev deletes the last source in a nested directory, the JavaScript and declaration watcher processes both call prune concurrently on the same output tree. One process can delete a file or directory after the other's readdirSync but before this statSync/rmdirSync, causing an ENOENT that fails that rebuild. Tolerate already-removed paths or centralize empty-directory cleanup so the two watchers cannot race.
Useful? React with 👍 / 👎.
…mentation path generation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 68c97a5bbb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const typeCheck = runStep(check, project).then(result => ({ | ||
| ...result, | ||
| duration: Date.now() - startedAt, | ||
| })); |
There was a problem hiding this comment.
Handle type-check rejection before starting tsdown
When runStep rejects before the tsdown build finishes—for example, because typescript is missing and resolveBin throws, or because the child process cannot be spawned—the derived typeCheck promise has no rejection handler until after await tsdown.build(...). Node can therefore treat it as an unhandled rejection and terminate the CLI instead of returning the intended configuration/runtime error. Attach a rejection handler immediately or store an always-settled result before starting the concurrent build.
Useful? React with 👍 / 👎.
| ); | ||
| } | ||
| task.succeed(); | ||
| finishStep(ui, task, step, await runStep(step, project)); |
There was a problem hiding this comment.
React Doctor · react-doctor/async-await-in-loop (warning)
This for…of loop waits before starting the next iteration. If iterations perform independent asynchronous work, consider bounded concurrency; await syntax alone does not establish a speedup.
Fix → Consider concurrent calls only for independent asynchronous work. Shared queues or synchronous work may not benefit. Preserve resource limits, transaction ordering, and failure/cancellation semantics; observe all callback promises.
Improving Documentation
oxlint --type-aware) and formatting (oxfmt --check) pass on the changed packages.cli/build.mdx,cli/dev.mdxandcli/index.mdxupdated; newplugins/build.mdx(how plugin builds and watch mode work) andplugins/migrate-to-tsdown.mdx(migrating an existing plugin from SWC). Both new pages compile in the web build and render with HTTP 200.Description
What?
Replaces the plugin/package pipeline (
tsc+ SWC +tsc-alias) and the core CLI build (tsup) with tsdown 0.23.0.vitnode buildin a plugin or adapter packagescripts/cli/builder/package-build.ts); plugin authors write no config..ts/.tsxundersrc(except tests,src/tests,__tests__,__fixtures__) compiles one-to-one intodist/src, so modules not reachable from an index are still built..jsextensions, automatic JSX runtime,*.server.jsnames, dynamic imports, side effects (no tree shaking),@/aliases rewritten in JS and.d.ts, all package imports external. Non-code files (JSON, CSS) are copied and their imports keep import attributes. Production output is minified with declaration maps and no JS maps, the same file set as before.dts.eager. Without it,global.d.tsandtypes/api-registry.gen.d.tsare ignored and inferred fetcher types silently becomeany; a test covers this.tsc --noEmit -p tsconfig.build.jsonruns concurrently and still fails the build.dist/srconly. The CLI build cleans onlydist/scripts, so the two never delete each other's output.vitnode devin a plugin packagetsc --watch --noEmit, tsdown for JavaScript, and tsdown for declarations. They are separate processes because the declaration rebuild blocks its thread and delayed JS rebuilds to ~3s when shared.srcitself and restarts tsdown when files are added or removed. tsdown's own glob restart has a race that left builds failing (UNRESOLVED_ENTRY) after a quick add-then-delete.Other changes
tsdown.config.tsreplacestsup.config.tsand also builds thepackage-watch.jsrunner. ThecreateRequirebanner was dropped after confirming the bundle has norequire(and the built CLI runs.tsconfig.build.jsonplusexportspointing into./dist/src/(no.swcrc). It classifies every package in the repo the same way as before..swcrcfiles,@swc/cli,@swc/core, tsup and (outside API apps)tsc-aliasremoved. Generator template, versions, core's optionaltsdownpeer dependency and the lockfile updated; the lockfile only drops SWC/tsup packages and adds tsdown's.dist/*.tsbuildinfois excluded from published packages.tsc+tsc-alias) and Vite app builds are unchanged.Verification
create-vitnode-apptests, typecheck, lint and format pass. turbobuild:plugins --forcesucceeds (10/10).create-vitnode-appbuilds, runs in watch mode and passesvitnode plugin validate.@vitnode/coreand@vitnode/blogin an isolated consumer: no type errors in@vitnode/*declarations, fetcher inference intact, and the modules and CLI run under Node.Performance (same machine, A/B)
.d.ts(example)Known limitations
tscreused its incremental declaration output, tsdown regenerates every declaration. Its incremental mode got the repeat to 22.6s but made the cold build worse (65s) and needs a generated tsconfig plus a cache; not adopted here.index.tsfiles no longer get a.d.ts.map.^22.18,^24.11or>=26.Why?
Moves plugin builds and dev watch mode to one coordinated tool. tsdown handles alias rewriting, JavaScript and declarations together, so there's no separate alias pass racing against compiler output, and plugin authors need no build config of their own.
🤖 Generated with Claude Code
https://claude.ai/code/session_01DspbmiWQwLSS7JbZQrPYJT
Generated by Claude Code