
esm-cjs-interop
by orient-bot
Orient - AI Agent Platform
SKILL.md
name: esm-cjs-interop description: Handle ESM/CJS module interoperability in the Orient monorepo. Use when encountering import errors, circular dependencies, module resolution issues, or "does not provide an export named" errors. Covers import patterns, re-export strategies, and avoiding circular dependency chains.
ESM/CJS Interoperability
Quick Reference
# Check if package is ESM or CJS
grep '"type"' packages/*/package.json
# Find circular dependency issues
npx madge --circular packages/*/src/index.ts
Module System in This Monorepo
This monorepo uses ESM ("type": "module") for all packages:
// packages/*/package.json
{
"type": "module",
"module": "NodeNext",
"moduleResolution": "NodeNext"
}
However, some legacy code in src/ may still use CJS patterns, creating interop issues.
Required: .js Extensions for ESM
ESM requires explicit .js extensions in import paths, even for TypeScript files:
// ✅ Correct - explicit .js extension
import { loadConfig } from './config/index.js';
import { createLogger } from '@orient/core';
// ❌ Wrong - missing extension (works in CJS, fails in ESM)
import { loadConfig } from './config/index';
Common Error: "Does not provide an export named"
This error occurs when CJS modules are re-exported incorrectly in ESM:
Error: The requested module '@orient/package' does not provide
an export named 'SomeClass'
Solution: Use Default Import + Re-export
// ❌ Wrong - doesn't work with CJS modules
export * from './some-cjs-module.js';
export { SomeClass } from './some-cjs-module.js';
// ✅ Correct - use default import first
import SomeCjsModule from './some-cjs-module.js';
export const { SomeClass, someFunction } = SomeCjsModule;
// Or re-export the whole module
export default SomeCjsModule;
Circular Dependency Prevention
Circular dependencies cause runtime failures, especially when ESM and CJS mix.
Pattern: Circular Dependency Chain
@orient/mcp-tools
└── imports @orient/agents (to get PromptService)
└── imports @orient/mcp-tools (for tool definitions)
└── CIRCULAR!
Solution: Import Lower-Level Package
Instead of importing a high-level package that re-exports, import the specific lower-level package:
// ❌ Creates circular dependency
import { PromptService } from '@orient/agents';
// ✅ Import the actual implementation package
import { MessageDatabase } from '@orient/database-services';
// Use MessageDatabase.setSystemPrompt directly
Identifying Circular Dependencies
- Build fails with cryptic ESM error - often circular deps
- Import works in tests but fails at runtime - module loading order differs
- "Cannot access before initialization" - circular import timing issue
Debug Command
# Check for circular dependencies
npx madge --circular --extensions ts packages/*/src/index.ts
# Visualize dependency graph
npx madge --image deps.svg packages/mcp-tools/src/index.ts
Package Dependency Direction
Follow this dependency direction to avoid cycles:
┌─────────────────────────────────────────────────┐
│ Allowed Import Direction │
│ │
│ @orient/core │
│ ↓ │
│ @orient/database │
│ ↓ │
│ @orient/database-services │
│ ↓ │
│ @orient/integrations │
│ ↓ │
│ @orient/agents │
│ ↓ │
│ @orient/mcp-tools │
│ ↓ │
│ @orient/bot-whatsapp | @orient/bot-slack │
└─────────────────────────────────────────────────┘
Packages can only import from packages above them in this hierarchy.
Re-Export Patterns
Barrel File (index.ts) Pattern
// packages/*/src/index.ts
// Re-export types (always safe)
export type { ConfigOptions, LogLevel } from './types/index.js';
// Re-export ESM modules
export { loadConfig } from './config/index.js';
export { createLogger } from './logger/index.js';
// Re-export default exports
export { default as SomeClass } from './SomeClass.js';
Conditional ESM/CJS Exports
Use package.json exports field for dual support:
{
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs",
"default": "./dist/index.js"
}
}
}
Troubleshooting
Import Works in Tests, Fails in Runtime
Tests may use different module resolution (esbuild/vite transforms). Check:
- Is the import target an ESM or CJS module?
- Are you using
export *with a CJS module? - Is there a circular dependency in the runtime load order?
"Module not found" After Build
# Ensure dist/ exists
ls packages/*/dist/
# Rebuild the package
pnpm --filter @orient/package-name build
# Check exports in package.json match dist/ structure
Dynamic Import for CJS Modules
When importing CJS modules dynamically:
// ESM dynamic import of CJS module
const cjsModule = await import('./cjs-module.cjs');
const { someExport } = cjsModule.default || cjsModule;
Best Practices
- Use explicit
.jsextensions in all import paths - Avoid
export *with modules that might be CJS - Import from specific packages rather than re-export barrels
- Follow dependency hierarchy to prevent circular deps
- Test imports at runtime not just in vitest (different resolution)
Score
Total Score
Based on repository quality metrics
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 100以上
3ヶ月以内に更新がある
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
Reviews
Reviews coming soon