Mutable difference between ES6 named and default exports

Viewed 1286

When importing/exporting data from ES6 modules, mutability of that data appears to be different between named imports and exports. Is there a reason for this or some fundamental difference I'm not understanding?

// counter.js
export let count = 0;

export const incrementCount = () => count += 1;

export default count;
// main-default.js
import count, { incrementCount } from './counter';

console.log(count); // 0

incrementCount();
incrementCount();

console.log(count); // 0
// main-named.js
import { count, incrementCount } from './counter';

console.log(count); // 0

incrementCount();
incrementCount();

console.log(count); // 2

In both scenarios, I would expect count to increment. However, this only happens when using named exports.

2 Answers

The problem is that you used export default count;, which does not export the count binding (allowing aliasing the mutable variable by an import) but actually creates a new, hidden variable that gets the initial value assigned but is never changed afterwards.

export default count;

desugars to

let *default* = count; // where *default* is a guaranteed collision-free identifier
export { *default* as default }

What you want instead is

// counter.js
let count = 0;
export const incrementCount = () => count += 1;
export { count as default }

// main-default.js
import countA, { default as countB, incrementCount } from './counter';

console.log(countA, countB); // 0, 0
incrementCount();
console.log(countA, countB); // 1, 1

See also How can I alias a default import in Javascript?.

It is because count is number not object. By importing default you are assigning value of count to new variable. By named import you have readonly copy like object. Consider this:

// counter.js
export let count = {a:0};
export const incrementCount = () => count.a += 1;
export default (function(){ return count.a})();

When you run:

// main-default.js
import countdefault, { count, incrementCount } from './counter.mjs';
console.log(countdefault, count);
incrementCount();
incrementCount();
console.log(countdefault, count);

you get:

0 { a: 0 }

0 { a: 2 }

but when you change counter export to object:

// counter.js
export let count = {a:0};
export const incrementCount = () => count.a += 1;
export default (function(){ return count})();

you get:

{ a: 0 } { a: 0 }

{ a: 2 } { a: 2 }

Related