In this lesson, you will learn how to safely import CommonJS (CJS) modules into an ES Module (ESM) environment to ensure your application remains stable and free from unintended side effects.
As we discussed in our module evolution lessons, CJS and ESM handle code execution differently. CJS is synchronous and uses require, while ESM is asynchronous and uses import. When you are in an ESM project (where package.json has "type": "module"), importing a CJS file can sometimes trigger unexpected behaviors because CJS modules are evaluated in a specific, legacy scope.
The Strategy: Controlled Defaults
To bridge this gap without side effects, always use the Default Import pattern. Because CJS uses module.exports to define an object, ESM treats the entire export as a "default" member.
Imagine you have a legacy CJS utility file named math-tools.cjs:
// math-tools.cjs
module.exports = {
add: (a, b) => a + b
};
When importing this into your ESM file, instead of trying to destructure it during the import (which can fail depending on how the CJS file was authored), import it as a namespace or a default object:
// main.js
import mathTools from './math-tools.cjs';
// Use the object directly
console.log(mathTools.add(2, 3));
Why this matters
By importing the CJS module as a single object, you avoid "named export" confusion. ESM expects explicit named exports (e.g., export const add = ...), but CJS exports are dynamic. If you try to do import { add } from './math-tools.cjs', Node.js must perform a complex synthesis of your CJS module.exports object to find a named property called add.
By treating the CJS module as a single default import, you bypass this lookup mechanism, keeping your memory usage predictable and avoiding potential errors where the runtime fails to "guess" which exports exist inside the CJS wrapper. This approach ensures your ESM environment maintains clean module boundaries while interacting with legacy code.
Key Takeaway: When bridging modules, always import your CommonJS files as a default object to avoid static analysis errors and ensure stable cross-boundary execution.