Last Updated:
Importing ESM Modules in CommonJS TypeScript
In the JavaScript ecosystem, we have two major module systems: CommonJS and ECMAScript Modules (ESM). CommonJS has been around for a long time and is the traditional module system used in Node.js. On the other hand, ESM is the standard module system introduced in ECMAScript 2015 and is widely adopted in modern JavaScript development. TypeScript, as a superset of JavaScript, supports both module systems. However, when working with CommonJS TypeScript projects, there are specific considerations and techniques for importing ESM modules. This blog will guide you through the fundamental concepts, usage methods, common practices, and best practices for importing ESM modules in CommonJS TypeScript.
Table of Contents#
Fundamental Concepts#
CommonJS Modules#
CommonJS modules use the require function to import modules and the module.exports or exports object to export values. In a CommonJS TypeScript file, it might look like this:
// math.ts (CommonJS)
function add(a: number, b: number) {
return a + b;
}
module.exports = {
add
};// main.ts (CommonJS)
const { add } = require('./math');
console.log(add(1, 2)); ECMAScript Modules (ESM)#
ESM uses the import and export keywords. An ESM TypeScript file could be:
// math-esm.ts (ESM)
export function add(a: number, b: number) {
return a + b;
}// main-esm.ts (ESM)
import { add } from './math-esm';
console.log(add(1, 2)); Compatibility Issues#
CommonJS and ESM have different semantics and loading mechanisms. CommonJS is synchronous and uses the require statement, while ESM is static and supports top-level await. When trying to import an ESM module in a CommonJS TypeScript project, we need to handle these differences carefully.
Usage Methods#
Using Dynamic Imports#
In a CommonJS TypeScript project, you can use dynamic import() to import ESM modules. Dynamic imports return a promise, so you need to handle it accordingly.
// main.ts (CommonJS)
async function main() {
const { add } = await import('./math-esm.mjs');
console.log(add(1, 2));
}
main().catch((error) => {
console.error(error);
});Common Practices#
File Extensions#
When working with ESM and CommonJS in the same project, it's important to use the correct file extensions. Use .mjs for ESM files and .cjs for CommonJS files. In TypeScript, you can use .ts for both, but make sure the tsconfig.json is configured correctly.
Configuration in tsconfig.json#
You need to configure your tsconfig.json to support both module systems. Here is an example:
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"esModuleInterop": true,
"skipLibCheck": true,
"strict": true
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules"]
}Best Practices#
Error Handling#
When using dynamic imports, always handle errors properly. Use a try...catch block.
async function main() {
try {
const { add } = await import('./math-esm.mjs');
console.log(add(1, 2));
} catch (error) {
console.error('Error importing ESM module:', error);
}
}
main();Code Organization#
Keep your ESM and CommonJS code organized. Group ESM modules together and CommonJS modules together. This makes the codebase more maintainable and easier to understand.
Conclusion#
Importing ESM modules in a CommonJS TypeScript project requires an understanding of the differences between the two module systems. By using dynamic imports, and following best practices such as proper error handling and code organization, you can successfully integrate ESM modules into your CommonJS TypeScript projects.