javascriptimportmodulees6-moduleshoisting

Does importing a module mean embedding the code of the module at the line of the import statement?


From ReferenceError: can't access lexical declaration 'X' before initialization - JavaScript | MDN, there is an example of invalid import:

a.js (entry module):

import { b } from "./b.js";

export const a = 2;

b.js:

import { a } from "./a.js";

console.log(a); // ReferenceError: Cannot access 'a' before initialization
export const b = 1;

MDN explains:

In this example, the imported variable a is accessed but is uninitialized, because the evaluation of a.js is blocked by the evaluation of the current module b.js.

I understand this as importing a module means embedding the code of the module at the line of the import statement. That is, the a.js becomes this at compile time:

console.log(a); // ReferenceError: Cannot access 'a' before initialization
const b = 1;

const a = 2;

Is this understanding correct? I don't see it's explained in import - JavaScript | MDN. I don't know how to test this because reordering the lines in a.js doesn't change the result due to hoisting.


Solution

  • The code from an imported module is not simply embedded ("pasted"), but lives in a separate closure. Although this is surely an oversimplification, I compare the module with a function and the export statement with its return statement:

    function a_js() {
      var b = b_js(); // unnecessary line
      return 2;
    }
    
    function b_js() {
      var a = a_js();
      console.log(a);
      return 1;
    }
    <button onclick="a_js()">import a.js</button>
    <button onclick="b_js()">import b.js</button>

    Because the modules import each other, they cannot be loaded in any order: Pressing either button leads to a "Maximum call stack size exceeded" error.

    But if you remove the unnecessary line (which only fills a local variable that is then discarded), it works.