Skip to content

Simplify and modernise tsdown build configuration#1492

Open
smorimoto wants to merge 3 commits intoworkos:mainfrom
smorimoto:smorimoto/simplify-tsdown-config
Open

Simplify and modernise tsdown build configuration#1492
smorimoto wants to merge 3 commits intoworkos:mainfrom
smorimoto:smorimoto/simplify-tsdown-config

Conversation

@smorimoto
Copy link
Contributor

@smorimoto smorimoto commented Feb 14, 2026

Summary

  • Upgrade tsdown from 0.17 to 0.20 (rolldown 1.0.0-rc.3) and consolidate the build configuration from two separate objects (ESM and CJS) into a single unified configuration object
  • Remove redundant options:
    • target — auto-inferred from engines.node
    • unbundle — unnecessary as only two entry points are exported, reducing output from 566 to 12 files
    • outExtensions — auto-derived by tsdown 0.20
  • Replace noExternal with inlineOnly, the recommended tsdown 0.20 option that prevents unintended dependency bundling. Also fix the dependency list: remove uint8array-extras (recursively inlined via iron-webcrypto) and add the missing jose (direct ESM-only dependency)
  • Add exports option with customExports to auto-manage package.json exports, keeping them in sync with build output. Runtime-specific conditions (workerd, edge-light, convex) route to the worker entry for environments without process global
  • Remove deno and bun export conditions as both runtimes resolve correctly through the standard import/require fallback
  • Remove explicit types fields from export conditions as co-located .d.mts/.d.cts files are resolved automatically by TypeScript
  • Add publint validation to catch export mismatches on every build
  • Add attw validation with node16 profile to verify type declarations on every build, replacing the separate check:types npm script which used --ignore-rules no-resolution
  • Update ecosystem-check.ts to reference .mjs extensions matching tsdown 0.20 output

Package size comparison

Metric Before (main) After Change
lib/ file count 2,449 18 -99.3%
lib/ disk size 9.1 MB 1.6 MB -82.4%
npm pack (unpacked) 2.3 MB 1.6 MB -30.4%
npm pack (.tgz) 399 KB 267 KB -33.1%
Build time 1,407 ms 693 ms -50.7%

Breaking change note

Removing unbundle means individual source files are no longer emitted to lib/. Deep imports such as @workos-inc/node/lib/common/net/node-client that previously worked (despite never being officially supported via the exports map) will no longer resolve. Only the documented entry points (. and ./worker) are available. This aligns the actual output with the package's public API surface.

Housekeeping

  • Sort package.json fields with sort-package-json for consistent field ordering

Test plan

  • npx tsdown builds successfully with no warnings
  • publint reports no issues
  • attw reports no problems (node16 CJS/ESM, bundler all green)
  • npm run check:runtimes passes all 6 runtime tests (node-cjs, node-esm, deno, bun-cjs, bun-esm, worker)

@smorimoto smorimoto requested a review from a team as a code owner February 14, 2026 01:41
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 14, 2026

Greptile Overview

Greptile Summary

Consolidated and modernized the build configuration by upgrading tsdown from 0.17 to 0.20, replacing two separate build configs with a unified approach that reduces output from 566 to 12 files while maintaining full compatibility across Node.js, Deno, Bun, and Worker runtimes.

Key changes:

  • Switched from unbundle: true with glob patterns to explicit entry points (src/index.ts, src/index.worker.ts)
  • Replaced noExternal with inlineOnly and fixed the dependency list: added missing jose (direct ESM-only dependency) and removed uint8array-extras (transitively inlined via iron-webcrypto)
  • Added exports.customExports to auto-manage package.json exports, routing runtime-specific conditions (workerd, edge-light, convex) to worker entry
  • Removed redundant export conditions (deno, bun) and explicit types fields (auto-resolved by TypeScript)
  • Added publint: true for export validation on every build
  • Updated file extensions from .js to .mjs matching tsdown 0.20 output conventions

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk
  • The changes are well-tested infrastructure improvements to the build system. The PR author verified all 6 runtime tests pass (node-cjs, node-esm, deno, bun-cjs, bun-esm, worker), publint reports no issues, and the build completes successfully. The configuration changes align with tsdown 0.20 best practices and the dependency list corrections (jose added, uint8array-extras removed) are accurate based on the codebase analysis.
  • No files require special attention

Important Files Changed

Filename Overview
tsdown.config.ts Modernized tsdown configuration from v0.17 to v0.20, replacing two separate configs with a unified build, switching to inlineOnly with correct dependencies (jose added, uint8array-extras removed), and adding publint validation
package.json Updated dependencies (tsdown 0.17→0.20, added publint), changed file extensions to .mjs/.d.cts, simplified exports by removing redundant deno/bun conditions and explicit types fields
scripts/ecosystem-check.ts Updated import paths from .js to .mjs extensions to match tsdown 0.20 output format

Flowchart

flowchart TD
    A[Source Files] --> B[tsdown 0.20]
    B --> C{Build Format}
    C -->|ESM| D[index.mjs<br/>index.worker.mjs]
    C -->|CJS| E[index.cjs<br/>index.worker.cjs]
    C -->|Types| F[index.d.cts<br/>index.worker.d.mts]
    
    G[Dependencies] --> H{Bundling Strategy}
    H -->|inlineOnly| I[iron-webcrypto<br/>jose]
    H -->|External| J[Other deps]
    
    I --> D
    I --> E
    J --> D
    J --> E
    
    D --> K[package.json exports]
    E --> K
    F --> K
    
    K --> L{Runtime Condition}
    L -->|workerd/edge-light/convex| M[worker entry]
    L -->|import| N[main ESM]
    L -->|require| O[main CJS]
    
    P[publint] --> K
    P --> Q[Validation]
Loading

Last reviewed commit: d771b19

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

4 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

Upgrade tsdown from 0.17 to 0.20 and consolidate the build configuration
from two separate objects (one for ESM, one for CJS) into a single unified
configuration object.

Key changes to tsdown.config.ts:

- Merge ESM and CJS into one config with `format: ['esm', 'cjs']`. The
  previous setup maintained two independent configuration objects with
  duplicated entry, outDir, target, sourcemap, and dts options. A single
  object eliminates this repetition entirely.

- Remove `target: 'es2022'` because tsdown 0.20 automatically reads the
  `engines.node` field from package.json (currently `>=20.15.0`) to
  determine the compilation target, making an explicit target redundant.

- Remove `unbundle: true` because the package only exports two entry
  points (`.` and `./worker`), so preserving the full source file
  structure in the output is unnecessary. This reduces the output from
  566 files to 12 files with automatic code splitting for shared modules.

- Remove `outExtensions` because tsdown 0.20 automatically derives the
  correct file extensions (`.mjs`/`.d.mts` for ESM, `.cjs`/`.d.cts` for
  CJS) based on the output format.

- Replace `noExternal` with `inlineOnly`, the recommended option in
  tsdown 0.20 for explicitly marking which ESM-only dependencies should
  be bundled into the CJS output. Unlike `noExternal`, `inlineOnly`
  prevents unintended bundling of other dependencies.

- Replace `uint8array-extras` with `jose` in the inlined dependencies
  list. `uint8array-extras` was a transitive dependency of
  `iron-webcrypto` and is automatically inlined recursively when its
  parent is inlined, so listing it explicitly was unnecessary. `jose`
  is a direct ESM-only dependency that was previously missing from the
  inline list.

- Add `exports` option with `customExports` to let tsdown automatically
  manage the package.json exports field, keeping it in sync with the
  build output. The custom function adds the runtime-specific conditions
  (workerd, edge-light, convex) that route to the worker entry point for
  environments without `process` global.

- Remove `deno` and `bun` export conditions from package.json because
  both runtimes correctly resolve through the standard `import`/`require`
  fallback conditions, making dedicated conditions redundant.

- Remove explicit `types` fields from export conditions because the
  declaration files (`.d.mts`/`.d.cts`) are co-located with their
  corresponding JS files, allowing TypeScript to resolve them
  automatically.

- Add `publint: true` to validate the package.json configuration against
  the actual build output on every build, catching export mismatches
  early.

- Add `publint` as a devDependency to support the built-in validation.

- Update ecosystem-check.ts to reference `.mjs` extensions instead of
  `.js` to match the new tsdown 0.20 output naming convention.
…script

Add `attw: { profile: 'node16' }` to tsdown.config.ts so that Are The
Types Wrong validation runs automatically on every build alongside
publint. This makes the separate `check:types` npm script redundant,
as the same validation (with a clearer `node16` profile instead of
`--ignore-rules no-resolution`) now runs as part of `npx tsdown`.
@smorimoto smorimoto force-pushed the smorimoto/simplify-tsdown-config branch from 981ba60 to 380dfb4 Compare February 14, 2026 05:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant