Basic Importing

Once you have a module with exports, you can access the functionality in another module by using the import keyword. The two parts of an import statement are the identifiers you’re importing and the module from which those identifiers should be imported. This is the statement’s basic form:

  1. import { identifier1, identifier2 } from "./example.js";

The curly braces after import indicate the bindings to import from a given module. The keyword from indicates the module from which to import the given binding. The module is specified by a string representing the path to the module (called the module specifier). Browsers use the same path format you might pass to the <script> element, which means you must include a file extension. Node.js, on the other hand, follows its traditional convention of differentiating between local files and packages based on a filesystem prefix. For example, example would be a package and ./example.js would be a local file.

I> The list of bindings to import looks similar to a destructured object, but it isn’t one.

When importing a binding from a module, the binding acts as if it were defined using const. That means you can’t define another variable with the same name (including importing another binding of the same name), use the identifier before the import statement, or change its value.

Importing a Single Binding

Suppose that the first example in the “Basic Exporting” section is in a module with the filename example.js. You can import and use bindings from that module in a number of ways. For instance, you can just import one identifier:

  1. // import just one
  2. import { sum } from "./example.js";
  3. console.log(sum(1, 2)); // 3
  4. sum = 1; // error

Even though example.js exports more than just that one function this example imports only the sum() function. If you try to assign a new value to sum, the result is an error, as you can’t reassign imported bindings.

W> Make sure to include /, ./, or ../ at the beginning of the file you’re importing for best compatibility across browsers and Node.js.

Importing Multiple Bindings

If you want to import multiple bindings from the example module, you can explicitly list them out as follows:

  1. // import multiple
  2. import { sum, multiply, magicNumber } from "./example.js";
  3. console.log(sum(1, magicNumber)); // 8
  4. console.log(multiply(1, 2)); // 2

Here, three bindings are imported from the example module: sum, multiply, and magicNumber. They are then used as if they were locally defined.

Importing All of a Module

There’s also a special case that allows you to import the entire module as a single object. All of the exports are then available on that object as properties. For example:

  1. // import everything
  2. import * as example from "./example.js";
  3. console.log(example.sum(1,
  4. example.magicNumber)); // 8
  5. console.log(example.multiply(1, 2)); // 2

In this code, all exported bindings in example.js are loaded into an object called example. The named exports (the sum() function, the multiple() function, and magicNumber) are then accessible as properties on example. This import format is called a namespace import because the example object doesn’t exist inside of the example.js file and is instead created to be used as a namespace object for all of the exported members of example.js.

Keep in mind, however, that no matter how many times you use a module in import statements, the module will only be executed once. After the code to import the module executes, the instantiated module is kept in memory and reused whenever another import statement references it. Consider the following:

  1. import { sum } from "./example.js";
  2. import { multiply } from "./example.js";
  3. import { magicNumber } from "./example.js";

Even though there are three import statements in this module, example.js will only be executed once. If other modules in the same application were to import bindings from example.js, those modules would use the same module instance this code uses.

A> ### Module Syntax Limitations A> A> An important limitation of both export and import is that they must be used outside other statements and functions. For instance, this code will give a syntax error: A> A> js A> if (flag) { A> export flag; // syntax error A> } A> A>The export statement is inside an if statement, which isn’t allowed. Exports cannot be conditional or done dynamically in any way. One reason module syntax exists is to let the JavaScript engine staticly determine what will be exported. As such, you can only use export at the top-level of a module. A> A> Similarly, you can’t use import inside of a statement; you can only use it at the top-level. That means this code also gives a syntax error: A> A> js A> function tryImport() { A> import flag from "./example.js"; // syntax error A> } A> A> A> You can’t dynamically import bindings for the same reason you can’t dynamically export bindings. The export and import keywords are designed to be static so that tools like text editors can easily tell what information is available from a module.

A Subtle Quirk of Imported Bindings

ECMAScript 6’s import statements create read-only bindings to variables, functions, and classes rather than simply referencing the original bindings like normal variables. Even though the module that imports the binding can’t change its value, the module that exports that identifier can. For example, suppose you want to use this module:

  1. export var name = "Nicholas";
  2. export function setName(newName) {
  3. name = newName;
  4. }

When you import those two bindings, the setName() function can change the value of name:

  1. import { name, setName } from "./example.js";
  2. console.log(name); // "Nicholas"
  3. setName("Greg");
  4. console.log(name); // "Greg"
  5. name = "Nicholas"; // error

The call to setName("Greg") goes back into the module from which setName() was exported and executes there, setting name to "Greg" instead. Note this change is automatically reflected on the imported name binding. That’s because name is the local name for the exported name identifier. The name used in the code above and the name used in the module being imported from aren’t the same.