Skip to content

build: migrate from Rollup/Babel/Terser to tsdown#829

Merged
krisk merged 1 commit into
mainfrom
build/tsdown-migration
Jun 5, 2026
Merged

build: migrate from Rollup/Babel/Terser to tsdown#829
krisk merged 1 commit into
mainfrom
build/tsdown-migration

Conversation

@krisk

@krisk krisk commented Jun 5, 2026

Copy link
Copy Markdown
Owner

What

Replaces the hand-rolled Rollup build (scripts/configs.cjs + config-types.cjs + build.main.cjs, 373 lines + 8 devDeps) with a single tsdown.config.ts (rolldown engine + built-in dts).

Why tsdown (not tsup, not consolidated Rollup)

A problem-blind Codex assessment leaned against an esbuild-class tool swap over CJS-interop and worker-import.meta.url risk. A spike then showed tsdown clears every gate that tsup would have needed hacks for:

  • require('fuse.js') === Fuse (constructor-direct CJS) — automatic, no footer hack
  • named fuse-worker.cjs export — automatic
  • feature-flag DCE intact; dev/prod default-export split preserved
  • minified sizes smaller (raw)

Behavior-preserving

  • Identical 15-file dist/ set (12 runtime + 3 dts), verified against main
  • CJS require() contract + named worker export unchanged
  • Fuse.version injected via define (bare __VERSION__ + ambient declare; honors process.env.VERSION)
  • Full suite 360/360, typecheck clean; new cjs-interop + worker-url tests gate the contracts

Worker-URL resolution

Reworked so it no longer depends on a bundler's import.meta.url codegen: a per-format __WORKER_IS_CJS__ define routes browser-CJS to a document base while ESM keeps the literal new URL('./fuse.worker.mjs', import.meta.url) for bundler asset detection. test/worker-url.test.js gates node-ESM, node-CJS, browser-ESM (Vite), and browser-CJS.

Sizes (accepted tradeoff)

Min artifacts are 287–313 bytes smaller raw; gzip is +8 to +28 bytes (≤0.4%) because rolldown's minifier compresses marginally worse than Terser. Accepted in favor of the dependency reduction.

⚠️ Build now requires Node ≥22.18 (tsdown engines)

CI builds on Node 22/24 and runs the Node 20 leg against the committed dist/ (the library runtime still supports Node ≥10). .nvmrc bumped to 22; DEVELOPERS.md + release docs updated. This CI matrix change is the main thing to watch in this PR's checks — it is the part that can't be validated locally.

Review

Plan 017 and the implementation were both reviewed cross-model (Codex): plan APPROVED (round 3) plus a cold-read fresh-pass (0 CRITICAL/HIGH); implementation review APPROVED with only the LOW gzip note above.

Out of scope

docs-deploy reconciliation is descoped: deploy-docs.sh is currently the only path that bumps docs before deploy, so deleting it needs a release-ordering fix first. Tracked separately in the roadmap.

Collapse the hand-rolled Rollup orchestrator (scripts/configs.cjs +
config-types.cjs + build.main.cjs, 373 lines) and 8 build devDeps into one
tsdown.config.ts (rolldown engine + built-in dts). Chosen over tsup after a
problem-blind Codex assessment and a spike: tsdown emits constructor-direct
CJS (require('fuse.js') === Fuse) and named worker CJS automatically, no
footer hacks.

Behavior-preserving on the public surface:
- identical 15-file dist set (12 runtime + 3 dts)
- CJS require() contract unchanged; named fuse-worker.cjs export preserved
- feature-flag DCE intact (basic build strips logical/extended/token)
- dev/prod default-export split preserved
- Fuse.version injected via define (bare __VERSION__ + ambient declare;
  honors process.env.VERSION for releases)

Worker-URL resolution no longer depends on a bundler's import.meta.url
codegen: a per-format __WORKER_IS_CJS__ define routes browser-CJS to a
document base while ESM keeps the literal new URL('./fuse.worker.mjs',
import.meta.url) so bundlers still detect and rewrite the worker asset.
test/worker-url.test.js gates node-ESM, node-CJS, browser-ESM (Vite), and
browser-CJS. test/cjs-interop.test.js gates require() === constructor for
every lib cjs plus the named worker cjs.

Sizes: minified artifacts are 287-313 bytes smaller raw; gzip is +8..+28
bytes (<=0.4%, accepted) since rolldown's minifier compresses marginally
worse than Terser.

Build/release now requires Node >=22.18 (tsdown engines): CI builds on 22/24
and runs the Node 20 leg against the committed dist. docs-deploy
reconciliation is descoped (deploy-docs.sh is the only path that bumps docs
before deploy; needs a release-ordering fix first).

Removed devDeps: rollup, @rollup/plugin-babel, @rollup/plugin-node-resolve,
@rollup/plugin-replace, @babel/core, @babel/preset-typescript, terser,
rollup-plugin-dts. Added: tsdown.
@krisk krisk merged commit 7c6af4e into main Jun 5, 2026
3 checks passed
@krisk krisk deleted the build/tsdown-migration branch June 5, 2026 19:42
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