modfix
v1.0.1
Published
Automatically detect and configure the correct module system (ESM/CJS) to fix 'Cannot use import statement outside a module' and related errors
Downloads
21
Maintainers
Readme
modfix 🔧
Automatically detect and fix ESM/CommonJS module configuration issues
modfix solves the #1 most upvoted npm-related question on StackOverflow: "Cannot use import statement outside a module" (701 votes, 1.9M views).
The Problem
SyntaxError: Cannot use import statement outside a moduleThis error occurs when Node.js tries to parse ES modules (import/export) in a CommonJS context. It affects:
- TypeScript projects
- Babel setups
- Modern Node.js applications
- Projects migrating from CJS to ESM
modfix automatically detects your project's module system and fixes the configuration.
Installation
# Global installation (recommended for CLI)
npm install -g modfix
# Local installation
npm install modfix
# Run without installing
npx modfix analyzeQuick Start
# Analyze your project for issues
modfix analyze
# Auto-fix configuration (package.json, tsconfig.json)
modfix fix
# Convert code from CommonJS to ESM
modfix migrate --target esm
# Interactive setup
modfix initCLI Commands
modfix analyze [path]
Scan a project and report module configuration issues.
modfix analyze # Analyze current directory
modfix analyze ./my-project # Analyze specific path
modfix analyze --verbose # Show detailed file info
modfix analyze --json # Output as JSONExample output:
🔍 Analyzing project...
Project Analysis Report
──────────────────────────────────────────────────
Project: /path/to/my-project
Package.json: /path/to/my-project/package.json
Configured type: None
Recommended type: esm
Files analyzed:
Total: 15
ESM syntax: 12
CJS syntax: 3
Issues found:
2 error(s)
1 warning(s)
Errors:
✖ ESM syntax (import/export) used in CommonJS context
/path/to/my-project/src/index.js
→ Add "type": "module" to package.json or rename file to .mjs
Recommendation:
Run modfix fix --target esm to configure your project for ESMmodfix fix [path]
Auto-fix module configuration in package.json and tsconfig.json.
modfix fix # Fix with auto-detected target
modfix fix --target esm # Configure for ES Modules
modfix fix --target cjs # Configure for CommonJS
modfix fix --dry-run # Preview changes without writing
modfix fix --exports # Add "exports" field for dual package
modfix fix --interactive # Interactive mode with promptsWhat it does:
- Sets
"type": "module"or"type": "commonjs"inpackage.json - Updates
tsconfig.jsonwith correctmodule/moduleResolutionsettings - Optionally adds
"exports"field for dual ESM/CJS package support - Adds
"engines": {"node": ">=18.0.0"}
modfix migrate [path]
Convert source code between ESM and CommonJS syntax.
modfix migrate # Convert to ESM (default)
modfix migrate --target cjs # Convert to CommonJS
modfix migrate --dry-run # Preview transformations
modfix migrate --verbose # Show detailed changes
modfix migrate -f src/index.js # Migrate specific filesTransformations:
ESM → CJS:
// Before (ESM)
import fs from 'fs';
import { join } from 'path';
export default function hello() {}
export const name = 'world';
// After (CJS)
const fs = require('fs');
const { join } = require('path');
function hello() {}
const name = 'world';
module.exports = hello;
module.exports.name = name;CJS → ESM:
// Before (CJS)
const fs = require('fs');
const { join } = require('path');
module.exports = { hello, name };
// After (ESM)
import fs from 'fs';
import { join } from 'path';
export { hello, name };modfix init
Interactive setup wizard (alias for modfix fix --interactive).
modfix initProgrammatic API
Use modfix in your Node.js scripts:
import { analyze, fix, migrate, detect } from 'modfix';
// Analyze a project
const report = await analyze('./my-project');
console.log(report.issues); // Array of issues
console.log(report.recommendedType); // 'esm' | 'cjs' | 'mixed'
// Fix configuration
const results = await fix('./my-project', {
target: 'esm', // 'esm' | 'cjs'
dryRun: false, // Preview without changes
addExports: true // Add exports field
});
// Migrate code
const migration = await migrate('./my-project', {
target: 'esm',
dryRun: false,
files: ['src/index.js'] // Optional: specific files
});
// Detect single file
const fileInfo = await detect('./src/index.js');
console.log(fileInfo.effectiveType); // 'module' | 'commonjs'
console.log(fileInfo.usesESMSyntax); // boolean
console.log(fileInfo.usesCJSSyntax); // booleanAdvanced API
import {
// Analysis
analyzeProject,
analyzeFile,
generateSummary,
// Detection
detectSyntaxWithLexer,
getPackageType,
getModuleTypeFromExtension,
// Fixing
fixPackageJson,
fixTsConfig,
generateExportsField,
// Transformation
cjsToEsm,
esmToCjs,
transformFile,
previewTransform,
// Utilities
findNearestPackageJson,
getFilesRecursive
} from 'modfix';
// Transform code directly
const result = cjsToEsm(`
const fs = require('fs');
module.exports = { readFile: fs.readFile };
`);
console.log(result.code);
// import fs from 'fs';
// export { readFile: fs.readFile };How It Works
Detection Algorithm
- File Extension:
.mjs/.mts→ ESM,.cjs/.cts→ CJS - package.json: Walks up directory tree to find nearest
package.jsonand reads"type"field - Syntax Analysis: Uses es-module-lexer for fast parsing:
import/exportstatements → ESMrequire()/module.exports→ CJSimport.meta→ ESM__dirname/__filename→ CJS
Issue Detection
| Issue Code | Description |
|------------|-------------|
| ESM_IN_CJS | ESM syntax used in CommonJS context |
| CJS_IN_ESM | CommonJS syntax used in ESM context |
| MIXED_SYNTAX | File contains both ESM and CJS syntax |
| NO_PACKAGE_JSON | No package.json found |
| NO_TYPE_FIELD | Missing "type" field in package.json |
| TS_MODULE_MISMATCH | tsconfig.json module setting conflicts with package.json |
Configuration Generated
package.json (ESM)
{
"type": "module",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
}
},
"engines": {
"node": ">=18.0.0"
}
}tsconfig.json (ESM)
{
"compilerOptions": {
"module": "NodeNext",
"moduleResolution": "NodeNext",
"target": "ES2022",
"esModuleInterop": true,
"strict": true,
"outDir": "./dist",
"declaration": true
}
}Common Use Cases
Fixing "Cannot use import statement outside a module"
# Quick fix
modfix fix --target esmMigrating a CJS Project to ESM
# 1. Analyze current state
modfix analyze
# 2. Convert code
modfix migrate --target esm
# 3. Update configuration
modfix fix --target esm --exportsSetting Up a Dual ESM/CJS Package
modfix fix --target esm --exportsThen build both versions with your bundler (esbuild, rollup, etc.).
Checking a Project in CI
modfix analyze --json | jq '.issues | length'
# Returns number of issues, exit code 0 if no errorsRequirements
- Node.js: >= 18.0.0
- Supported files:
.js,.ts,.jsx,.tsx,.mjs,.cjs,.mts,.cts
Limitations
- Dynamic imports: Can't statically analyze dynamic
require()orimport()with variables - Monorepos: Currently analyzes single packages; workspace support planned for v2
- Bundler configs: Doesn't generate webpack/rollup/esbuild configs (use their respective init tools)
Roadmap
v1.x (Current)
- ✅ Project analysis and issue detection
- ✅ Auto-fix package.json and tsconfig.json
- ✅ Code transformation (CJS ↔ ESM)
- ✅ CLI with analyze, fix, migrate commands
- ✅ Programmatic API
v2.0 (Planned)
- ⬜ Monorepo/workspace support
- ⬜ Watch mode for continuous fixing
- ⬜ Babel config generation
- ⬜ VS Code extension
- ⬜ Git integration (auto-commit fixes)
v3.0 (Future)
- ⬜ AI-powered migration suggestions
- ⬜ Custom rule definitions
- ⬜ Plugin system
Contributing
Contributions are welcome! Please read our Contributing Guide first.
# Clone the repo
git clone https://github.com/yourusername/modfix.git
cd modfix
# Install dependencies
npm install
# Run tests
npm test
# Run CLI locally
node bin/modfix.js analyze ./test-projectLicense
MIT © Harshit
