What a module actually is
ESM exports live bindings, not values, and is statically analysable. Both of those facts have consequences you meet the first time you publish.
An ES module exports a binding, not a value. If the module reassigns an exported variable later, every importer sees the new value, because the import is a view onto the module scope rather than a copy taken at import time. CommonJS copies at require time, which is why the same pattern gives you a stale value there. This is also why you cannot reassign an import: it is not your binding.
Imports are hoisted and statically determined, which is what makes tree-shaking possible at all: the bundler can know the whole dependency graph without running anything. It is also why import specifiers cannot be computed, and why dynamic import exists as an expression that returns a promise for the cases where the decision genuinely is at runtime.
Modules are evaluated once per specifier and cached, which makes a module scope a singleton. That is a convenient place to put a connection pool and a terrible place to put request state on a server, where one module instance is shared by every user. Circular imports resolve to a partially initialised module rather than an error, which is why the symptom is undefined rather than a helpful message.
You should now be able to
- Explain the difference between a live binding and a copied value
- Say why import cannot be conditional and require can
Loading…