presetter-preset-hybrid
v8.3.0
Published
An opinionated presetter preset for delivering a dual CommonJS/ESM package
Downloads
19
Readme
🚀 presetter-preset-hybrid
Dual-module TypeScript libraries — maximum compatibility, automatic dual builds
• Usage • Configuration • Comparison • FAQ •
This is a configuration template that works with Presetter, the configuration management tool.
⚡ TL;DR / Quick Start
# Install hybrid preset
npm i -D presetter presetter-preset-hybrid
# Create presetter.config.ts
echo "export { default } from 'presetter-preset-hybrid';" > presetter.config.ts
# Bootstrap your project
npx presetter bootstrapYour project now generates both CommonJS and ESM outputs from single TypeScript source — maximum Node.js ecosystem compatibility!
✨ Hybrid Modules: Best of Both Worlds
Need to support everyone?
Modern Node.js ecosystem is divided: legacy tools need CommonJS, modern environments prefer ESM, and library authors face an impossible choice. Choose CommonJS and miss modern optimizations. Choose ESM and break compatibility.
What if you could have both?
The dual-module distribution challenge
| Challenge | Single Format | With preset-hybrid | | ------------------------ | -------------------------- | ----------------------------- | | Legacy Node.js | ❌ ESM breaks old versions | ✅ CommonJS works everywhere | | Modern Bundlers | ❌ CJS misses tree-shaking | ✅ ESM enables optimizations | | Library Adoption | ⚠️ Forces users to choose | ✅ Works in any environment | | Package Complexity | ⚠️ Simple but limiting | ✅ Automated dual builds | | Maintenance Burden | ⚠️ Manual dual configs | ✅ Single source, dual output | | Import Compatibility | ❌ Breaking changes | ✅ Seamless for all users |
What you get instead
presetter-preset-hybrid is a configuration template that provides dual CommonJS/ESM TypeScript library builds.
When used with Presetter (the configuration management tool), this preset extends preset-essentials with sophisticated dual-module generation, automatically creating both .js (CommonJS) and .mjs (ESM) outputs from your single TypeScript codebase.
- 🔄 Dual Builds: Generates both CommonJS and ESM from single source
- 🛠️ Automatic Transforms: Handles import extensions,
__dirnameconversion, path aliases - 📦 Package Integration: Perfect dual-module package.json structure
- ⚡ Modern DX: Write TypeScript once, support all Node.js environments
- 🎯 Library-First: Optimized for npm packages and open-source libraries
- 🚀 Zero Config: Complex dual builds work perfectly out of the box
🎯 Dual Modules Without the Pain
The hybrid package setup problem
Creating dual CommonJS/ESM packages manually is extraordinarily complex:
- Multiple TypeScript configs: Need separate CommonJS and ESM compilation targets
- Import transformations: ESM requires
.jsextensions,__dirnamedoesn't exist - Package.json complexity: Conditional exports, dual entry points, proper module fields
- Build orchestration: Sequential builds, post-processing, source map handling
- Path alias resolution: TypeScript paths must work in both output formats
Getting it right requires mastering TypeScript compiler edge cases, Node.js module systems, and complex build tooling.
From hybrid confusion to dual-module mastery
# Before: Manual dual-module setup
my-library/
├── tsconfig.json ← Single config, wrong for dual builds
├── tsconfig.cjs.json ← Missing or incorrect CommonJS config
├── tsconfig.esm.json ← Missing or incorrect ESM config
├── webpack.config.js ← Complex bundling setup
├── src/
│ └── index.ts ← Import paths break in one format
└── lib/
- ├── index.js ← Only CommonJS OR only ESM
- └── index.d.ts ← Single declaration file
# After: Generated from hybrid template
my-library/
+├── presetter.config.ts ← References this hybrid preset template
├── package.json ← Perfect dual-module structure with exports
+├── tsconfig.json ← Generated base configuration
+├── tsconfig.cjs.json ← Generated CommonJS build config
+├── tsconfig.mjs.json ← Generated ESM build config
+├── All dev tools ← Generated with dual-module awareness
├── src/
│ └── index.ts ← Import paths work in both formats
└── lib/
+ ├── index.js ← CommonJS output (require/exports)
+ ├── index.mjs ← ESM output (import/export)
+ └── index.d.ts ← TypeScript declarations for bothHow dual-module template generation works
- Dual TypeScript Compilation — Separate
tsconfig.cjs.jsonandtsconfig.mjs.jsonwith format-specific settings - Automated Transformations —
tsc-esm-fixhandles import extensions,__dirnameconversion,.mjsrenaming - Sequential Build Pipeline — ESM build → CommonJS build → alias resolution → transformations
- Package Structure — Perfect dual-module package.json with conditional exports
Why this solves the real problem
- Maximum compatibility: Works in any Node.js environment or bundler
- Automatic transformations: No manual import fixing or environment-specific code
- Library distribution: Perfect for npm packages that need broad adoption
- Single codebase: Write TypeScript once, get both CommonJS and ESM
- Production ready: Handles all the edge cases and gotchas of dual modules
🔍 Understanding Presetter vs This Preset
Important distinction:
| Component | Role | What it does | | ---------------------------------------------------------------------------------- | ---------------------------------- | -------------------------------------------------------------------- | | Presetter | Configuration management tool | CLI that processes presets, generates config files, executes scripts | | presetter-preset-hybrid | Dual-module configuration template | Extends essentials with sophisticated dual CommonJS/ESM builds |
Think of it like:
- Presetter = The engine that builds houses
- This preset = The blueprint for a sophisticated, dual-compatible foundation
This preset extends preset-essentials with dual-module build capabilities. For advanced usage, customization, and troubleshooting, visit the main Presetter documentation.
🚀 Usage
🟢 Basic Dual-Module Library Setup
Step 1: Install Preset
// package.json
{
"main": "lib/index.js",
"module": "lib/index.mjs",
"types": "lib/index.d.ts",
"exports": {
".": {
"require": "./lib/index.js",
"import": "./lib/index.mjs",
},
},
"scripts": {
"build": "run build",
"test": "run test",
},
"devDependencies": {
"presetter": "latest",
"presetter-preset-hybrid": "latest",
},
}// presetter.config.ts
export { default } from 'presetter-preset-hybrid';Step 2: Bootstrap & Develop
npm install
# Perfect dual-module configuration generated automatically
# Write TypeScript, get both CommonJS AND ESM output!That's it! TypeScript compiles to both formats. Your library works everywhere.
🧑🔬 Advanced Usage: Custom Dual-Module Optimizations
// presetter.config.ts
import { preset } from 'presetter';
import hybrid from 'presetter-preset-hybrid';
export default preset('my-hybrid-library', {
extends: [hybrid],
override: {
variables: {
target: 'ES2020', // Custom compilation target
},
assets: {
'tsconfig.cjs.json': {
compilerOptions: {
target: 'ES2017', // Conservative CommonJS target
},
},
'tsconfig.mjs.json': {
compilerOptions: {
target: 'ES2022', // Modern ESM target
},
},
},
},
});Need more customization options? Check the main Presetter documentation for complete guides on overrides, extensions, and advanced configurations.
📖 API Reference
Core Hybrid Configuration Template
This preset extends preset-essentials with dual-module build capabilities:
| Configuration | Purpose | Hybrid Features |
| ---------------------- | -------------------- | ---------------------------------------------------------- |
| TypeScript Configs | Dual compilation | Separate CommonJS and ESM tsconfig files |
| Build Pipeline | Sequential builds | ESM → CommonJS → transformations → aliases |
| Package Structure | Dual exports | Conditional exports for require/import |
| Transformations | Format compatibility | Import extensions, __dirname conversion, .mjs renaming |
Generated TypeScript Configurations
tsconfig.cjs.json (CommonJS Build)
{
"extends": "./tsconfig.build",
"compilerOptions": {
"module": "commonjs",
"target": "ES2017",
"moduleResolution": "node"
}
}tsconfig.mjs.json (ESM Build)
{
"extends": "./tsconfig.build",
"compilerOptions": {
"module": "ES2022",
"target": "ES2022"
}
}Configuration Variables
Inherited from preset-essentials with hybrid-optimized defaults:
| Variable | Default | Description |
| -------- | ---------- | ---------------------------------- |
| source | "src" | Source code directory |
| output | "lib" | Build output directory |
| test | "spec" | Test files directory |
| target | "ES2022" | Base TypeScript compilation target |
🔧 Configuration Details
Dual Build Pipeline
# Generated build scripts
build:typescript: run-s build:typescript:mjs build:typescript:cjs build:typescript:alias
build:typescript:mjs: run-s build:typescript:mjs:tsc build:typescript:mjs:fix
build:typescript:mjs:tsc: tsc -p tsconfig.mjs.json
build:typescript:mjs:fix: tsc-esm-fix --sourceMap --target {output} --ext .mjs
build:typescript:cjs: run-s build:typescript:cjs:*
build:typescript:cjs:tsc: tsc -p tsconfig.cjs.jsonAutomatic Transformations
- Import Extension Addition:
import './foo'→import './foo.js' - ESM Global Replacement:
__dirname→import.metaequivalents - File Extension Renaming:
.js→.mjsfor ESM outputs - Path Alias Resolution: TypeScript paths work in both formats
Perfect Package.json Structure
{
"main": "lib/index.js", // CommonJS entry
"module": "lib/index.mjs", // ESM entry
"types": "lib/index.d.ts", // TypeScript definitions
"exports": {
".": {
"require": "./lib/index.js", // CommonJS conditional export
"import": "./lib/index.mjs", // ESM conditional export
},
},
}🏎️ Performance
| Metric | Single Format | With preset-hybrid | | -------------------- | -------------------- | ---------------------------- | | Build time | Fast (single target) | Moderate (dual builds) | | Bundle compatibility | Limited | Universal | | Tree shaking | Format-dependent | Both CJS + ESM optimized | | Package adoption | Restricted | Maximum | | User experience | May break | Seamless |
🌐 Compatibility
| Environment | Support | | ----------- | ---------------------------------------- | | Node.js | CommonJS: ≥8, ESM: ≥14 | | Bundlers | Universal (webpack, Rollup, Vite, etc.) | | Tools | Works with both CommonJS and ESM tooling | | TypeScript | ≥ 5.0 |
Extends
presetter-preset-essentials- Core TypeScript development toolkit
Used By Libraries Needing
- Broad Node.js ecosystem compatibility
- Modern bundler optimization support
- Seamless user experience across environments
🆚 Comparison
| Feature | preset-hybrid | preset-cjs | preset-esm | | ------------------------ | ---------------- | ---------- | ------------ | | CommonJS Support | ✅ Native | ✅ Native | ❌ Limited | | ESM Support | ✅ Native | ❌ Limited | ✅ Native | | Tree Shaking | ✅ ESM optimized | ❌ No | ✅ Optimized | | Legacy Compatibility | ✅ Full | ✅ Full | ❌ Limited | | Build Complexity | ⚠️ Complex | ✅ Simple | ✅ Simple | | Library Distribution | ✅ Perfect | ⚠️ Limited | ⚠️ Limited |
When to Use
✅ Use preset-hybrid when:
- Building npm packages or open-source libraries
- Need maximum Node.js ecosystem compatibility
- Users require both CommonJS and ESM support
- Want modern bundler optimizations without breaking legacy support
- Library adoption is more important than build simplicity
❌ Consider alternatives when:
- Building applications (not libraries)
- Team can commit to single module format
- Build simplicity is more important than compatibility
- Target environment is known and consistent
🛠️ Troubleshooting
General Presetter issues? See the main troubleshooting guide for common Presetter problems and solutions.
Dual-Module Specific Issues
| Issue | Symptoms | Solution |
| --------------------------- | ------------------------------ | ----------------------------------------------------- |
| Import extension errors | Cannot resolve module in ESM | tsc-esm-fix automatically adds .js extensions |
| __dirname undefined | ReferenceError in ESM | Automatically replaced with import.meta equivalents |
| Wrong module format | CJS syntax in .mjs files | Check build pipeline runs both compilations |
| Package.json exports | Module resolution failures | Ensure proper conditional exports structure |
Need help with Presetter CLI commands? Check the CLI reference in the main documentation.
❓ FAQ
General Presetter questions? Check the main FAQ for general usage, configuration, and customization questions.
Dual-Module Specific FAQs
What's the difference from preset-essentials?
preset-hybrid extends preset-essentials with sophisticated dual-module capabilities:
- Generates both CommonJS (.js) and ESM (.mjs) outputs
- Separate TypeScript configurations for each format
- Automatic import transformations and path resolution
- Perfect dual-module package.json structure
How are import extensions handled?
Automatically by tsc-esm-fix:
// Your TypeScript code
import { utils } from './utils';
// CommonJS output (no change needed)
const { utils } = require('./utils');
// ESM output (extensions added automatically)
import { utils } from './utils.js';What about dirname andfilename?
Automatically converted for ESM compatibility:
// Your TypeScript code
const configPath = path.join(__dirname, 'config.json');
// CommonJS output (unchanged)
const configPath = path.join(__dirname, 'config.json');
// ESM output (automatically converted)
const configPath = path.join(
path.dirname(fileURLToPath(import.meta.url)),
'config.json',
);Do I need to maintain two codebases?
No! Write TypeScript once, get both formats:
- Single TypeScript source in
src/ - Automatic dual compilation to
lib/index.jsandlib/index.mjs - Shared TypeScript declarations in
lib/index.d.ts
How do users consume my hybrid package?
Seamlessly with conditional exports:
// CommonJS users
const myLib = require('my-hybrid-lib');
// ESM users
import myLib from 'my-hybrid-lib';
// Both work automatically based on user's environment🤝 Contributing
We'd love your ideas and contributions! Submit issues or suggestions via GitHub Issues. See the Contribution Guide for more details.
📄 License
Released under the MIT License. © 2020, Alvis Tang.
