Fast SWC-powered build tool that compiles TypeScript/JavaScript to dual ESM/CommonJS packages. Solve the dual package hazard and ship modern packages with ease.
Publishing JavaScript packages that work in both CommonJS and ESM environments is complicated:
- Dual Package Hazard: Risk of loading the same module twice in different formats
-
Extension Hell: Managing
.js
,.cjs
,.mjs
extensions correctly - Import Rewriting: Adjusting import paths for each module system
- Package.json Exports: Complex configuration for dual module support
inop
handles all of this automatically - just write TypeScript, get both formats.
npm install --save-dev inop
# or
pnpm add -D inop
# or
yarn add -D inop
Transform your TypeScript source to dual modules:
npx inop src build
This creates both ESM (.js
) and CommonJS (.cjs
) versions of your code with proper source maps.
Given this source structure:
src/
├── index.ts
├── utils.ts
└── constants.ts
Running npx inop src build
produces:
build/
├── index.js # ESM version
├── index.cjs # CommonJS version
├── index.js.map # Source map for ESM
├── index.cjs.map # Source map for CommonJS
├── utils.js
├── utils.cjs
├── constants.js
└── constants.cjs
Use the -p
flag to automatically configure package.json
for dual module publishing:
npx inop src build -p
This updates your package.json
with:
{
"type": "module",
"main": "build/index.cjs", // CommonJS entry
"module": "build/index.js", // ESM entry
"types": "build/index.d.ts", // TypeScript types
"exports": {
"require": "./build/index.cjs", // Node.js require()
"import": "./build/index.js" // ESM import
},
"files": ["build", "src"] // Include source for source maps
}
Exclude test files and mocks when building:
npx inop src build -i __tests__ -i __mocks__
Use different file extensions for your output:
# Use .mjs for ESM and .js for CommonJS
npx inop src dist --esm-ext .mjs --commonjs-ext .js
# Generate only ESM
npx inop src dist --skip-commonjs
# Generate only CommonJS
npx inop src dist --skip-esm
Create a standalone package with only used dependencies:
npx inop src dist -p --copy
This creates a dist/package.json
with only the dependencies actually imported in your code.
Since SWC doesn't generate type declarations, combine inop
with TypeScript:
# Build modules and generate types
npx inop src build && tsc --declaration --emitDeclarationOnly
Or in your package.json
:
{
"scripts": {
"build": "inop src build && tsc --declaration --emitDeclarationOnly"
}
}
{
"compilerOptions": {
"target": "ESNext",
"module": "NodeNext",
"declaration": true,
"esModuleInterop": true,
"moduleResolution": "NodeNext",
"rootDir": "src",
"outDir": "build"
}
}
For testing with Jest and SWC:
export default {
coverageProvider: 'v8',
extensionsToTreatAsEsm: ['.ts'],
moduleNameMapper: {
'^(\\.{1,2}/.*)\\.js$': '$1', // Handle .js extensions in imports
},
transform: {
'^.+\\.ts$': '@swc/jest', // Use SWC for fast transpilation
},
};
npx inop <source> <build> [options]
-
source
- Source directory containing TypeScript/JavaScript files -
build
- Output directory for compiled modules
-
-p, --package
- Update package.json with dual module configuration -
-c, --copy
- Copy package.json to build directory with optimized dependencies -
-i, --ignore [patterns...]
- Ignore file patterns (e.g.,__tests__
,*.spec.ts
) -
-t, --type <type>
- Source file type:js
orts
(default:ts
) -
-m, --match <pattern>
- Override file matching pattern (default:**/*.ts
) -
-s, --swcrc <path>
- Custom .swcrc config path (default:.swcrc
) -
--commonjs-ext <ext>
- CommonJS file extension (default:.cjs
) -
--esm-ext <ext>
- ESM file extension (default:.js
) -
--skip-commonjs
- Generate only ESM output -
--skip-esm
- Generate only CommonJS output -
-V, --version
- Show version number -
-h, --help
- Show help
Ensure your TypeScript uses .js
extensions in imports:
// ✅ Correct - will be transformed appropriately
import { utils } from './utils.js';
// ❌ Wrong - missing extension
import { utils } from './utils';
inop
focuses on fast transpilation. Run TypeScript separately for declarations:
tsc --declaration --emitDeclarationOnly
Make sure source files are included in your npm package by using the -p
flag or manually adding them to files
in package.json.
License The MIT License Copyright (c) 2023-2024 Ivan Zakharchanka