TypeScript Dual Module Builder
ts-dual-module is a command-line tool designed to simplify the creation of dual-module (ESM + CommonJS) packages using TypeScript. It targets projects that define `"type": "module"` in their `package.json` and automatically builds both module formats from TypeScript sources, typically located in a `./src` directory and outputting to `./dist`. The tool handles the complexities of `package.json` `exports` and `typesVersions` fields to ensure correct module resolution and type declarations for both environments, specifically addressing CommonJS subpath export issues where TypeScript's default `moduleResolution` might fall short. The current stable version is 0.6.3, indicating it's actively maintained but still in a pre-1.0 development phase, which implies a focus on adding features and refining functionality, potentially with breaking changes between minor versions. Its key differentiator is the automation of dual-module bundling and `package.json` configuration, saving developers from complex manual setup or reliance on multiple build steps with different TypeScript configurations.
Common errors
-
Cannot find module 'your-package/subpath' or its corresponding type declarations.
cause Subpath exports are not correctly configured in `package.json` for CommonJS consumers, leading to type resolution failures.fixUse `npx tsmod export <subpath> <target_file>` to add the subpath export. This command automatically sets up the `exports` and `typesVersions` fields in `package.json` to correctly map types for both ESM and CJS. -
Error: The 'exports' field in package.json does not allow imports of 'your-package/dist/index.js' (or similar errors related to exports condition).
cause Your `package.json` `exports` field is misconfigured or conflicts with how Node.js is trying to resolve the module. This often happens if `import` and `require` conditions are not correctly defined, or if `default` is not set up properly.fixAllow `ts-dual-module` to manage your `exports` field. If you have custom exports, ensure they align with Node.js's dual package hazard guidelines, explicitly defining `import`, `require`, and `default` conditions. -
TS2304: Cannot find name 'SomeType' / TS2307: Cannot find module 'some-module'.
cause This is a general TypeScript compilation error indicating that types or modules are not being resolved. It could be due to a missing `tsconfig.json`, incorrect `moduleResolution` settings, or uninstalled `@types/` packages.fixEnsure `tsconfig.json` exists and is correctly configured (run `npx tsmod init` if unsure). Verify `compilerOptions.moduleResolution` is set to `node` or `nodenext`. Install any missing type declaration packages (e.g., `npm install -D @types/node`).
Warnings
- breaking The tool assumes your project is an ESM package, meaning your root `package.json` must contain `"type": "module"`. Projects without this declaration may not build correctly or exhibit unexpected module resolution behavior.
- gotcha The `ts-dual-module` tool automatically modifies your `package.json` to include `exports` and `typesVersions` fields. Manual edits to these fields may be overwritten or conflict with the tool's output.
- gotcha For subpath exports to work correctly with CommonJS builds, `ts-dual-module` adds a `typesVersions` entry. Without this, TypeScript's default `moduleResolution` (especially below `Node16`) might not resolve types for subpath imports in CJS environments.
- gotcha A `tsconfig.json` file is required in the project root as the base configuration for TypeScript compilation. The tool will prompt to create one via `npx tsmod init` if missing, but incorrect configurations can lead to build failures or unexpected output.
Install
-
npm install ts-dual-module -
yarn add ts-dual-module -
pnpm add ts-dual-module
Imports
- tsmod build
npx tsmod build
- tsmod init
npx tsmod init
- tsmod export
npx tsmod export <subpath> <target_file>
Quickstart
mkdir my-dual-package
cd my-dual-package
npm init -y
# Add "type": "module" to package.json
node -e "const pkg = require('./package.json'); pkg.type = 'module'; require('fs').writeFileSync('./package.json', JSON.stringify(pkg, null, 2));"
npm install -D typescript ts-dual-module
mkdir src
echo 'export const greet = (name: string) => `Hello, ${name} from ESM!`;' > src/index.ts
echo 'export const farewell = (name: string) => `Goodbye, ${name} from CJS!`;' > src/cjs.ts
npx tsmod init # Follow prompts, e.g., default 'dist' output directory
npx tsmod build
# Verify output
cat dist/index.d.ts
cat dist/index.mjs
cat dist/index.js
cat package.json # Check for updated exports and typesVersions
node -e "import { greet } from './dist/index.mjs'; console.log(greet('ESM User'));"
node -e "require('./dist/index.js').greet ? console.log(require('./dist/index.js').greet('CJS User')) : console.log(require('./dist/index.js').farewell('CJS User - CJS path'));"