TSFIX fixes ESM compatibility in tsc-compiled JavaScript by adding required file extensions to imports. ESM demands explicit file extensions that the TypeScript compiler doesn't provide automatically.
- Auto-detects TypeScript config and output paths
- Handles all import types (absolute, relative, dynamic)
- Offers regex (fast) and AST (accurate) extraction modes
- Adds
.js
extensions to compiled JS imports specifiers - Resolves directory imports to
index.js
- Handles
.ts
extensions whenallowImportingTsExtensions
is enabled - Fixes
.d.ts
files (unlike TypeScript's--rewriteRelativeImportExtensions
) - Compatible with all TypeScript versions
npm install --save-dev @2bad/tsfix
Add tsfix
as a post-build script in your project's package.json:
{
"scripts": {
"build": "tsc",
"postbuild": "tsfix"
}
}
This automatically runs tsfix
after TypeScript compilation.
npx @2bad/tsfix
# Use AST-based extraction (more accurate but slower)
npx @2bad/tsfix --mode ast
# Custom file matching pattern
npx @2bad/tsfix --pattern "**/*.js"
Consider the following TypeScript files:
// src/main.ts
import { helper } from './utils/helper.ts'
// src/utils/helper.ts
export const helper = () => 'helper function'
After running tsc, the compiled JavaScript files might look like this:
// dist/main.js
import { helper } from './utils/helper.ts'
// dist/utils/helper.js
export const helper = () => 'helper function'
Running tsfix will transform the import paths to:
// dist/main.js
import { helper } from './utils/helper.js'
Use the DEBUG
environment variable to enable detailed logging:
# Enable all debug logging
DEBUG=* tsfix
# Only enable specific components
DEBUG=tsfix:main,tsfix:extractor tsfix
# Show only fixer operations
DEBUG=tsfix:fixer tsfix
TypeScript still lacks proper ESM output support after 8+ years of requests. The recent --rewriteRelativeImportExtensions
flag in TS 5.5+ has limitations with .d.ts files. TSFIX resolves these issues, saving development time.
- #16577: Provide a way to add the '.js' file extension to the end of module specifiers (2017)
- #28288: Feature: disable extensionless imports (2018)
- #40878: Compiled JavaScript import is missing file extension (2020)
- #42151: TypeScript cannot emit valid ES modules due to file extension issue (2020)
- #50501: TypeScript is not an ECMAScript superset post-ES2015 (2022)
- #61037:
rewriteRelativeImportExtensions
doesn't rewrite extensions in emitted declaration files (2025) - #61213: Allow allowImportingTsExtensions: without either '--noEmit' or '--emitDeclarationOnly' (2025)
ESM has stricter requirements than CommonJS:
- Required file extensions (
.js
,.mjs
) - No automatic
index.js
resolution - No directory-to-file resolution
- Different package.json 'exports' handling
TSFIX offers two extraction engines:
- Regex mode (default): Fast pattern-based extraction (x5 faster than AST mode)
- AST mode: Precise syntax tree-based extraction
Contributions welcome! Open issues for bugs/features or submit PRs with improvements.