17

I am following a tutorial and it says

ES modules uses live bindings. It means a feature to support cyclical dependencies.

But I don't clearly understand this concept. What does this mean?

mkubilayk
  • 2,477
  • 3
  • 20
  • 27
Ishan Patel
  • 5,571
  • 12
  • 47
  • 68

2 Answers2

21

Live bindings is a concept introduced in ES modules. It means that when the exporting module changes a value, the change will be visible from the importer side. This is not the case for CommonJS modules. Module exports are copied in CommonJS. Hence importing modules cannot see changes happened on the exporter side.


ESM

counter.mjs

export let count = 1;
export function increment() {
    ++count;
}

index.mjs

import { count, increment } from './counter.mjs';
console.log(count);
increment();
console.log(count);

Output

$ node --experimental-modules index.mjs
1
2

CJS

counter.js

let count = 1;
function increment() {
    ++count;
}

exports.count = count;
exports.increment = increment;

index.js

const { count, increment } = require('./counter.js');
console.log(count);
increment();
console.log(count);

Output

$ node index.js
1
1

More resources on the topic:

mkubilayk
  • 2,477
  • 3
  • 20
  • 27
  • 7
    Worth noting that Live Bindings provide a common memory address for both exporter & importer, i.e. both are pointing to the same memory location. – Jack Sep 02 '19 at 03:42
  • 1
    This answer is actually wrong. In commonJS, the change done by the exporter is actually reflected everywhere. Your `increment` function in the CJS example should mutate exports.count instead. And then the change is reflected everywhere. – Antoine Weber Oct 30 '21 at 10:42
  • 1
    `Module exports are copied in CommonJS` is wrong. The object called "exports" is exported to every file that use require(), and any change made on that object is reflected in every file that used require() – Antoine Weber Oct 30 '21 at 10:45
  • 1
    the function should be `function increment() { export.count += 1 }` – Antoine Weber Oct 30 '21 at 11:10
0

in CJS, require("./foo") gets you a reference to the exports object of foo.js.

  1. let's set exports.x to 0 in foo.js
exports.x = 0
  1. In bar.js we get a reference to foo's exports and mutate it:
const fooExports = require("./foo")
fooExports.x += 1

Now we put everything in action in main.js

const fooExports = require("./foo") // getting a reference to exports
console.log(fooExports.x) // x is indeed 0
require("./bar") // we execute bar.js that mutate exports
console.log(fooExports.x) // x is now 1, because fooExports is a reference to exports

TLDR: What we receive from require is not a copy. It's a reference to the exports object.

Antoine Weber
  • 1,741
  • 1
  • 14
  • 14