Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save AmrHalim/edd64765a4edee7da544f2df8792f1dd to your computer and use it in GitHub Desktop.
Save AmrHalim/edd64765a4edee7da544f2df8792f1dd to your computer and use it in GitHub Desktop.
Navigating Interoperability: Exploring the differences between default importing CommonJS and ECMAScript Modules in TypeScript

Navigating Interoperability: Exploring the differences between default importing CommonJS vs ECMAScript modules in TypeScript

Introduction

  • CommonJS (CJS) modules lack default exports.
  • Despite that, we can still export one function only from a CJS module using module.exports syntax.
  • When ECMAScript 6 (ES6) introduced default exports, TypeScript had to devise a way to enable interoperability between CJS and ESM modules.
  • This means that if a module imports functionality from two other modules, one being a true CJS module and the other a transpiled ESM module, they should integrate seamlessly.
import func1 from 'cjsmodule';
import func2 from 'esmmodule';

func1();
func2();
  • Why's that even an issue? Let's witness in practice!

Exporting only one function from a true CJS module

Let's examine a CJS module that exports only one function:

// add.js
module.exports = function (a, b) {
  return a + b;
}

When importing this function into an ESM module, here's how we'd do it:

// index.ts
import add from './add';
add(1, 2);

When this code gets transpiled into CJS, this is how it will look like:

// index.js
var add = require('./add');
add(1, 2);

This all looks and works just fine!

Now let's explore doing the same thing, but this time in an ESM module.

Transpile a default export ESM module

Imagine writing the same example but this time using a default export in an ESM module.

// add.ts
export default function add (a: number, b: number) {
  return a + b;
}

Upon transpilation into CJS, this code will kinda resemble the following:

// add.js
function add(a, b) {
  return a + b;
}
exports.default = add;

Since CJS lack default exports, the compiler will simply add to the exports object a property called default and assign add function to this property.

Now, let's utilize this exported function in another ESM module:

// index.ts
import add from './add';
add(1, 2);

We'd expect that this code transpile to:

// index.js
var add = require("./add");
add(1, 2);

Looks familiar? This is the same transpiled code we got when we transpiled the ESM module into CJS above.

Unfortunately, this would yield an error since the transpiled code of the module we're importing from is essentially an object with a default property representing the function we're exporting. Consequently, the default export is accessed incorrectly in this snippet.

For this code to execute correctly, it needs to be transpiled into:

// index.js
var add = require('./add');
add.default(1, 2);

We can already see the confusion! Default importing from two modules - one is CJS and the other is ESM - simply works differently.

Interoperability between CJS and ESM

To address this, when TypeScript compiles an ESM module, it appends a special extra field to the output:

// index.js
+ Object.defineProperty(exports, "__esModule", { value: true });
function add(a, b) {
  return a + b;
}
exports.default = add;

This enables transpilers to conditionally generate different syntax for true CJS modules versus transpiled ESM modules like so:

const _mod = require('./module');
const mod = _mod.__esModule? _mod.default: _mod;
mod();

Conclusion

  • In conclusion, while default exports in ECMAScript modules (ESM) offer convenience, they can complicate interoperability with CommonJS (CJS) modules.
  • Given that CJS modules lack native support for default exports, relying on them in ESM modules may introduce challenges when integrating with existing CJS-based code.
  • Our exploration highlights the necessity for transpilers to handle default exports specially to ensure compatibility with CJS modules. However, this complexity underscores the importance of carefully considering the use of default exports in ESM modules.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment