Migration Guide: TSDX v0.x to v2.0
December 29, 2025 ยท View on GitHub
This guide helps you migrate from the original TSDX (v0.x) to the modern TSDX 2.0.
What's Changed
TSDX 2.0 is a complete rewrite that replaces the original toolchain with modern, high-performance alternatives:
| Old (v0.x) | New (v2.0) | Why |
|---|---|---|
| Rollup + Babel | bunchee | Zero-config, SWC-powered, faster |
| Jest | vitest | Vite-native, faster, Jest-compatible |
| ESLint | oxlint | 50-100x faster, Rust-powered |
| Prettier | oxfmt | 35x faster, Rust-powered |
| yarn/npm | bun | Faster installs and execution |
| Node 10+ | Node 20+ | LTS only |
Quick Migration
For most projects, follow these steps:
1. Install Bun
# macOS/Linux
curl -fsSL https://bun.sh/install | bash
# Windows
powershell -c "irm bun.sh/install.ps1 | iex"
2. Update package.json
Replace your scripts:
{
"scripts": {
"dev": "tsdx dev",
"build": "tsdx build",
"test": "tsdx test",
"lint": "tsdx lint",
"format": "tsdx format",
"typecheck": "tsdx typecheck",
"prepublishOnly": "bun run build"
}
}
3. Update Dependencies
Remove old dependencies and add new ones:
# Remove old dependencies
bun remove tsdx rollup @rollup/plugin-* babel-* @babel/* jest ts-jest eslint @typescript-eslint/* prettier husky lint-staged
# Add new tsdx
bun add -D tsdx typescript
4. Replace Jest with Vitest
Create vitest.config.ts:
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'node', // or 'jsdom' for React/DOM testing
},
});
Update test files:
- Change
import { describe, it, expect } from 'jest'toimport { describe, it, expect } from 'vitest' - Or use
globals: truein vitest.config.ts to avoid imports
Jest to Vitest Cheatsheet:
| Jest | Vitest |
|---|---|
jest.fn() | vi.fn() |
jest.mock() | vi.mock() |
jest.spyOn() | vi.spyOn() |
jest.useFakeTimers() | vi.useFakeTimers() |
beforeAll/afterAll | Same |
beforeEach/afterEach | Same |
describe/it/test | Same |
expect() | Same |
5. Remove Old Config Files
Delete these files (they're no longer needed):
rm -f tsdx.config.js
rm -f jest.config.js
rm -f .babelrc babel.config.js babel.config.json
rm -f .eslintrc .eslintrc.js .eslintrc.json .eslintignore
rm -f .prettierrc .prettierrc.js .prettierrc.json .prettierignore
rm -f rollup.config.js
rm -f yarn.lock package-lock.json
6. Update tsconfig.json
Update to modern settings:
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"lib": ["ES2022", "DOM"],
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"declarationMap": true,
"outDir": "./dist",
"rootDir": "./src",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}
7. Update package.json Exports
Ensure your package.json has modern exports:
{
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
}
},
"./package.json": "./package.json"
},
"files": ["dist", "src"],
"engines": {
"node": ">=20"
}
}
8. Install and Test
# Install dependencies
bun install
# Run tests
bun run test
# Build
bun run build
# Lint
bun run lint
Detailed Migration
Build Configuration
Old TSDX (tsdx.config.js):
module.exports = {
rollup(config, options) {
// Custom rollup config
return config;
},
};
New TSDX: No configuration needed! bunchee reads your package.json exports field.
For advanced customization, create bunchee.config.ts:
import { BuncheeConfig } from 'bunchee';
export default {
// See bunchee documentation
} satisfies BuncheeConfig;
Testing
Old Jest test:
import { sum } from './index';
describe('sum', () => {
it('adds numbers', () => {
expect(sum(1, 2)).toBe(3);
});
});
New Vitest test (same syntax!):
import { describe, it, expect } from 'vitest';
import { sum } from './index';
describe('sum', () => {
it('adds numbers', () => {
expect(sum(1, 2)).toBe(3);
});
});
Or with globals: true in vitest.config.ts:
import { sum } from './index';
describe('sum', () => {
it('adds numbers', () => {
expect(sum(1, 2)).toBe(3);
});
});
React Testing
Old (enzyme/react-testing-library with Jest):
import { render, screen } from '@testing-library/react';
import { MyComponent } from './MyComponent';
test('renders', () => {
render(<MyComponent />);
expect(screen.getByText('Hello')).toBeInTheDocument();
});
New (same, just with Vitest!):
import { describe, it, expect } from 'vitest';
import { render, screen } from '@testing-library/react';
import { MyComponent } from './MyComponent';
describe('MyComponent', () => {
it('renders', () => {
render(<MyComponent />);
expect(screen.getByText('Hello')).toBeDefined();
});
});
Note: Replace toBeInTheDocument() with toBeDefined() or add @testing-library/jest-dom and configure in vitest setup.
Linting
Old ESLint (.eslintrc.js):
module.exports = {
extends: ['react-app', 'prettier'],
rules: {
'no-unused-vars': 'warn',
},
};
New oxlint (.oxlintrc.json) - optional:
{
"rules": {
"no-unused-vars": "warn"
}
}
Most ESLint rules have equivalents in oxlint. Check oxlint rules documentation.
Formatting
Old Prettier (.prettierrc):
{
"semi": true,
"singleQuote": true,
"tabWidth": 2
}
New oxfmt (.oxfmtrc.json) - optional:
{
"indentWidth": 2,
"lineWidth": 100
}
GitHub Actions
Old workflow:
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node: [12, 14, 16]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
- run: yarn install
- run: yarn build
- run: yarn test
New workflow:
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
node: ['20', '22']
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: bun install
- run: bun run lint
- run: bun run typecheck
- run: bun run build
- run: bun run test
Breaking Changes
Removed Features
- Storybook template - Use Storybook CLI directly
- Custom Rollup config - Use bunchee config or raw rollup if needed
- tsdx lint - Now wraps oxlint instead of ESLint
- Node.js < 20 - Only Node.js 20+ (LTS) is supported
Changed Behavior
- Build output - Slightly different but compatible
- Watch mode - Now uses bunchee's watch, may have different behavior
- Test runner - Vitest instead of Jest (mostly compatible API)
- Default branch - Uses
maininstead ofmasterin templates
Compatibility
Your Library Consumers
No changes needed! The build output format is compatible:
- ESM and CommonJS dual publish
- TypeScript declarations
- Same export patterns
Your Development Workflow
| Task | Old Command | New Command |
|---|---|---|
| Create project | npx tsdx create mylib | bunx tsdx create mylib |
| Development | yarn start | bun run dev |
| Build | yarn build | bun run build |
| Test | yarn test | bun run test |
| Lint | yarn lint | bun run lint |
| Format | yarn prettier --write . | bun run format |
Troubleshooting
"bun: command not found"
Install bun:
curl -fsSL https://bun.sh/install | bash
Tests fail with "vi is not defined"
Add vitest imports:
import { describe, it, expect, vi } from 'vitest';
Or enable globals in vitest.config.ts:
export default defineConfig({
test: {
globals: true,
},
});
TypeScript errors with moduleResolution
Update tsconfig.json:
{
"compilerOptions": {
"moduleResolution": "bundler"
}
}
ESM/CJS interop issues
Ensure your package.json has:
{
"type": "module"
}
"Cannot find module" in tests
Check your vitest.config.ts has correct paths:
export default defineConfig({
test: {
include: ['test/**/*.test.ts'],
},
});