[Notes] Modules in JavaScript


My notes after reading: https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/

Original JavaScript had no concept of modules. Instead it ran on scope only, where global scope was accessible to the whole process without any restrictions. Naturally the dependency management and code maintenance was hard this way and not to mention the security implications.

Eventually modules systems were designed for JS:

  • CommonJS (CJS)
  • ES Modules (ESM)

ESM or EcmaScript Modules is part of JavaScript specification. All browsers have native support for this module system so I’ll focus my attention to ESM only. Also, ESM is the only compatible module system right now which supports cyclic dependencies as well.

Imports are basically a reference view into another module. Changes made in the parent module will reflect in the variables imported by child modules. The dependence is, as always, represented as a tree but ESM allows cyclic dependencies as well.

ES module system runs in three phases asynchronously

  • Construction: all loaded modules are parsed and converted to module records. These module records contains the information of imports, exports, variables and functions
  • Instantiation: all modules are instantiated and allocated working memory. The instance contains the instruction and space for variables and data structures.
  • Evaluation: The code is executed to determine the initial state.

These steps are part of ECMA spec but they always execute under a loader and in case of browsers that loader is HTML spec. which loads the module files and then calls parse, instantiate and evaluate.

In short all we need is to provide the entry point of our application code and the loader will parse and load all the required modules and create the dependency tree.

Multiple modules can depend on the same module. So they share the module instance. There can only be one instance per module.

Loaded and parsed modules are maintained in a module map. given a module location get the status (loading or loaded) if loaded get the module record.

Imports are reference to exported items memory location. Changes reflect everywhere. Once the dependency graph is ready the instantiation process traverses it in a depth-first manner and allocates the memory. This way all imports have a reference for the exported items.

Evaluation step executes the top level code (the global code) first and only once in the application lifetime. Cyclic dependencies are handled using timeout and tricks.

Browsers differentiate between module and non-module JS import using type attribute