Consider the following example:
// module.mjs
export let member = "initial"
setTimeout(() => {
member = "mutated"
}, 2000)
// index.mjs
import { member } from "./module.mjs"
setInterval(() => {
console.log(member)
}, 300);
If you run index.mjs it prints "initial" a few times and after the 2-second timeout starts printing "mutated".
This is surprising to me. I expected it to just keep printing "initial" since the reference to member has changed. I wouldn't be surprised if my index.mjs looked like this
// index.mjs
import * as allExports from "./module.mjs"
setInterval(() => {
console.log(allExports.member)
}, 300);
The reference to allExports hasn't changed. So by accessing member as a property I have access to the new reference. And fair enough, this produces the same result.
But why does it also work in the first case? Are import references updated every time the context for a function call is constructed? Well, then it should also work with default-exports. However, after adjusting the code like this:
// module.mjs
let member = "initial"
export default member
setTimeout(() => {
member = "mutated"
}, 2000)
// index.mjs
import member from "./module.mjs"
setInterval(() => {
console.log(member)
}, 300);
index.mjs just keeps printing "initial" according to my intuition. Hmm, maybe there is a fundamental difference between default-exports and named-exports. What if I do this:
// module.mjs
export let member = "initial"
setTimeout(() => {
member = "mutated"
}, 2000)
// index.mjs
import * as allExports from "./module.mjs"
let { member } = allExports
setInterval(() => {
console.log(member)
}, 300);
Wow, it keeps printing "initial". This is where I got really confused. I always assumed that
import { member } from "./module.mjs"
is just short for importing all named-exports as an object and then doing destructuring.
So my question is: what underlying principle justifies this unique behavior of named-exports?
The reason for this is because technically ES6 modules export and import bindings and not values. When you do:
export let test = 0
You need to think that you are exporting test not 0. That means any change on the variable will be reflected on their imports.
The reason this keeps printing initial:
// index.mjs
import * as allExports from "./module.mjs"
let { member } = allExports
setInterval(() => {
console.log(member)
}, 300);
Is because allExports is a special object. You are creating a new binding here and the value gets copied but you are losing the imported binding.