搜索

PythonReactSEOPDF
GitHub Skill

esm-cjs-interop

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.

orient-bot☆ 6 仓库 Stars0 仓库 Forks

安装与使用

请根据来源仓库和你的 AI 工具说明安装此技能。先阅读技能文件和依赖要求,再添加到工作流。

查看来源 ↗使用指南

技能说明

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
  1. Build fails with cryptic ESM error - often circular deps
  2. Import works in tests but fails at runtime - module loading order differs
  3. "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:

  1. Is the import target an ESM or CJS module?
  2. Are you using export * with a CJS module?
  3. 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

  1. Use explicit .js extensions in all import paths
  2. Avoid export * with modules that might be CJS
  3. Import from specific packages rather than re-export barrels
  4. Follow dependency hierarchy to prevent circular deps
  5. Test imports at runtime not just in vitest (different resolution)

使用示例

查看来源仓库中的使用示例与配置说明。

查看来源 ↗

来源历史

收录时间
2026-01-20
索引更新时间
2026-01-26
Branch
main

收录与同步日期属于本站记录。源码修改历史请查看来源仓库。

GitHub ↗

chat Comments (0)

chat_bubble_outline

No comments yet. Be the first to share your thoughts!