This guide helps you migrate from the original TSDX (v0.x) to the modern TSDX 2.0.
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 |
For most projects, follow these steps:
# macOS/Linux
curl -fsSL https://bun.sh/install | bash
# Windows
powershell -c "irm bun.sh/install.ps1 | iex"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"
}
}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 typescriptCreate 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 |
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.jsonUpdate 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"]
}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"
}
}# Install dependencies
bun install
# Run tests
bun run test
# Build
bun run build
# Lint
bun run lintOld 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;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);
});
});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.
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.
Old Prettier (.prettierrc):
{
"semi": true,
"singleQuote": true,
"tabWidth": 2
}New oxfmt (.oxfmtrc.json) - optional:
{
"indentWidth": 2,
"lineWidth": 100
}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 testNew 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- 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
- 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
No changes needed! The build output format is compatible:
- ESM and CommonJS dual publish
- TypeScript declarations
- Same export patterns
| 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 |
Install bun:
curl -fsSL https://bun.sh/install | bashAdd vitest imports:
import { describe, it, expect, vi } from 'vitest';Or enable globals in vitest.config.ts:
export default defineConfig({
test: {
globals: true,
},
});Update tsconfig.json:
{
"compilerOptions": {
"moduleResolution": "bundler"
}
}Ensure your package.json has:
{
"type": "module"
}Check your vitest.config.ts has correct paths:
export default defineConfig({
test: {
include: ['test/**/*.test.ts'],
},
});