Resolving Type Module Conflicts in Legacy Projects
Fix ERR_REQUIRE_ESM errors in legacy Node.js projects. Override tsconfig module resolution, patch package.json conditional exports, and validate dual-format outputs in CI pipelines.
Diagnosing ERR_REQUIRE_ESM in Legacy Node.js Loaders
Legacy tooling triggers ERR_REQUIRE_ESM: require() of ES Module not supported when synchronous require() calls intersect with modern ESM boundaries. Isolate and resolve the fault by:
- Tracing Stack Traces: Pinpoint dynamic
require()invocations in pre-ESM loaders. Use--trace-sync-ioornode --inspectto halt execution at the exact resolution boundary. - Differentiating Loader Failures: Distinguish native Node.js ESM loader rejections from third-party bundler fallbacks. Native failures throw synchronously at startup; bundler fallbacks typically manifest during tree-shaking or runtime chunk evaluation.
- Mapping Incompatible Dependencies: Execute
npx depcruise --include-only "^src" --output-type json src/ | jq '.[] | select(.moduleSystem == "esm" and .dependencyType == "require")'to map legacy dependency trees against ESM-incompatible packages.
Overriding tsconfig.json Module Resolution for Mixed Codebases
Bridge legacy CommonJS outputs with modern ESM type declarations without breaking downstream consumers. Address TS2307: Cannot find module or its corresponding type declarations by:
- Enforcing Strict Resolution: Set
moduleResolutiontonode16ornodenext. This forces the compiler to evaluatepackage.jsonexportsandtypesfields accurately, preventing implicit.d.tsresolution failures. - Preventing Default Import Mismatches: Enable
esModuleInterop: trueandallowSyntheticDefaultImports: trueto align legacy transpilation output with modern import syntax. - Isolating Ambient Declarations: Override
typeRootsto point exclusively to your project’snode_modules/@typesand localtypes/directory. This bypasses ambient conflicts from globally installed or hoisted legacy type definitions.
Patching package.json Exports for Browser/Node Divergence
Runtime resolution collisions occur when legacy monorepos lack explicit conditional exports. Misconfigured fallbacks frequently trigger ERR_UNSUPPORTED_DIR_IMPORT: Directory import is not supported. Resolve this by:
- Defining Explicit Conditions: Map
importandrequirekeys directly to their respective build artifacts. This bypasses the Browser vs Node.js Module Resolution divergence that causes legacy bundlers to misinterpret Node-specific conditional exports. - Removing Ambiguous Fallbacks: Delete top-level
mainandmodulekeys. Modern resolvers prioritizeexports; retaining legacy fallbacks forces older toolchains into unpredictable resolution paths. - Aligning Export Paths: Ensure
exportspaths match exact build output directories (e.g.,./dist/esm/,./dist/cjs/). Path aliasing failures occur when virtual paths diverge from physical artifact locations. Apply Module System Fundamentals & Dual-Package Resolution principles to prevent module duplication when CJS and ESM entry points coexist in shared workspaces.
CI/CD Pipeline & DevOps Cache Invalidation for Dual Outputs
Stale CJS/ESM artifacts cause environment-specific type conflicts during deployment, manifesting as MODULE_NOT_FOUND: Cannot resolve package.json exports condition. Enforce deterministic builds with:
- Strict Pre-Build Cleanup: Execute
rm -rf dist/ .tscache/ node_modules/.cache/before compilation to eliminate cross-pollinated artifacts from previous pipeline runs. - Environment-Specific Resolution Flags: Inject
NODE_OPTIONS="--experimental-vm-modules --no-warnings"orNODE_OPTIONS="--conditions=node"into legacy CI runners to force explicit module resolution paths. - Artifact Integrity Validation: Generate SHA-256 checksums for
dist/esm/anddist/cjs/outputs post-build. Compare against deployment manifests to verify artifact parity before publishing.
Step-by-Step Resolution Workflow
- Isolate legacy entry points with explicit extensions
mv legacy-entry.js legacy-entry.cjs && sed -i 's/require("\.\/legacy-entry")/require("\.\/legacy-entry.cjs")/g' src/bootstrap.js
- Configure strict conditional exports in package.json
{
"exports": {
".": {
"node": {
"import": "./dist/esm/index.mjs",
"require": "./dist/cjs/index.cjs"
},
"browser": "./dist/browser/index.js",
"types": "./dist/types/index.d.ts"
}
}
}
- Apply TypeScript module resolution override
{
"compilerOptions": {
"module": "NodeNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"verbatimModuleSyntax": true,
"resolveJsonModule": true
}
}
- Validate dual-package integrity without emitting
tsc --noEmit --project tsconfig.json && node --check dist/esm/index.mjs && node --check dist/cjs/index.cjs