What happens when a JS module is imported twice?

Let's start this article with a question. The ES2015 module incrementcontains the following code:

// increment.js
let counter = 0;
counter++;

export default counter;

In another module, which we will call consumer, the above module is imported 2 times:

// consumer.js
import counter1 from './increment';
import counter2 from './increment';

counter1; // => ???
counter2; // => ???

And now, actually, a question. What gets in the variables counter1and counter2after the module consumer? In order to answer this question, you need to understand how JavaScript executes modules, and how they are imported.





Module execution


In order to understand how the internal JavaScript mechanisms work, it is useful to look into the specification .

According to the specification, each JavaScript module is associated with a Module Record entity . This entry has a method Evaluate()that the module executes:

If this module has already been successfully executed, return undefined; [...]. Otherwise, transitively execute all the dependencies of this module and then execute this module.
As a result, it turns out that the same module is executed only once.

Unfortunately, what you need to know to answer our question is not limited to this. How to make sure that the instruction callimport using the same paths will return the same module?

Allow import commands


The abstract operation HostResolveImportedModule () is responsible for associating the path to the module (specifier) ​​with a specific module . The module import code looks like this:

import module from 'path';

Here is what the spec says about it:

An implementation of HostResolveImportedModule must meet the following requirements:

  • The normal return value should be an instance of a particular subclass of Module Record.
  • If the Module Record entity corresponding to the pair referencingScriptOrModule, specifier does not exist, or such an entity cannot be created, an exception shall be thrown.
  • Each time this operation is called with passing to it as arguments a specific pair of referencingScriptOrModule, specifier, it should, in the case of its usual execution, return the same instance of the Module Record entity.

Now consider this in a more understandable way.

HostResolveImportedModule(referencingScriptOrModule, specifier)- an abstract operation that returns the module corresponding to a pair of parameters referencingScriptOrModule, specifier:

  • The parameter referencingScriptOrModuleis the current module, that is, the module that imports.
  • A parameter specifieris a string that matches the module path in the instruction import.

At the end of the description, HostResolveImportedModule()it is said that when importing modules that correspond to the same path, the same module is imported:

import moduleA from 'path';
import moduleB from 'path';
import moduleC from 'path';

// moduleA, moduleB  moduleC -    

moduleA === moduleB; // => true
moduleB === moduleC; // => true

Interestingly, the specification indicates that the host (browser, Node.js environment, in general - anything trying to execute JavaScript code) should provide a specific implementation HostResolveImportedModule().

Answer


After a thoughtful reading of the specification, it becomes clear that JavaScript modules are executed only once during import. And when importing a module using the same path, the same instance of the module is returned.

Let's get back to our question.

A module is incrementalways executed only once:

// increment.js
let counter = 0;
counter++;

export default counter;

Regardless of how many times a module has been imported increment, an expression is counter++evaluated only once. A variable counterexported using the default export mechanism matters 1.

Now take a look at the module consumer:

// consumer.js
import counter1 from './increment';
import counter2 from './increment';

counter1; // => 1
counter2; // => 1

The commands import counter1 from './increment'and import counter2 from './increment'use the same path - './increment'. As a result, it turns out that the same instance of the module is imported.

It turns out that the same value 1 is written to the variables counter1and counter2to consumer.

Summary


Having examined a simple question, we were able to find out more about how JavaScript modules are executed and imported.

The rules used when importing modules are quite simple: the same module is executed only once. In other words, what is in the scope of the module level is performed only once. If a module that has already been executed once is imported again, then it will not be executed again. When importing a module, it uses what was obtained as a result of a previous session to find out what exactly it exports.

If a module is imported several times, but the module specifier (the path to it) remains the same, then the JavaScript specification ensures that the same instance of the module is imported.

Dear readers! How often do you resort to reading the JavaScript specification, figuring out the features of the functioning of certain language constructs?


All Articles