Introduction to modules
export
and import
statements
Modern JavaScript provides a way to split up a (long) JavaScript program into separate modules that can be imported when needed. This way the loading of code can be made more efficient and faster. For example, you can load only a few modules with the specific functionality you need for your application, rather than loading an entire JavaScript library with all available functionality.
Each module is a JavaScript, stored in a separate file (.js
extension).
A module can export functions, variables (and constants) and classes, by using an export
statement.
These exported features can be made available to another module by importing them into that other module by using an import
statement.
// module greeting.js
export const someConst = "Some value";
export function welcome(user) {
console.log(`Welcome back, ${user}!`);
}
// module main.js
import {welcome} from './greeting.js';
welcome("Casey");
<!-- index.html -->
…
<script type="module" src="modules/main.js"></script> <!-- see below for the type="module" attribute -->
<!-- logs: "Welcome back, Casey!" -->
…
You can also use a single export statement at the end of your module file, including a comma-separated list of the features you want to export, wrapped in curly brackets:
// module greeting.js
const someConst = "Some value";
function welcome(user) {
console.log(`Welcome back, ${user}!`);
}
export { welcome, someConst };
Exported functions, variables, constants, and classes need to be top-level items.
You cannot use export
inside a block statement, such as in a function or in an if
statement.
The same thing holds for imports: it is not possible to import conditionally or via a function,
unless you use dynamic imports (see later).
Import declarations are hoisted to the top of the module's scope.
You can also import modules directly from a script in an HTML file.
The next example replaces main.js
in the the example above.
<!-- index.html -->
…
<script type="module"> // see below for the type="module" attribute
import {welcome} from './modules/greeting.js';
welcome("Casey"); // logs: "Welcome back, Casey!"
</script>
…
Note the string in the import statement after from
that starts with ./
.
This string is the relative path to the module file and is required to start with ./
, ../
, or /
.
/
starts a path relative to the site root, but it is generally considered better practice to
use paths relative to the current location. ./
means "the current location" and
../
means one level up from the current location. Use multiple ../
in a sequence to go multiple levels up.
The string can also be an absolute URL, although this is not recommended when the site or the directory arrangement of the site may change in the future.
The evaluation of the string after from
is environment specific.
For example, some environments require .js
module file extensions,
while other environments prefer .mjs
extensions.
Node.js allows modules to be imported using bare module names (no path or extension at all),
while modern browsers need import maps for this.
Import maps
An import map is defined using a JSON object
within a <script>
element with a type="importmap"
attribute.
There must be no more than one import map in the document, and it must be included before any <script>
elements that import modules.
<script type="importmap">
{
"imports": {
"greeting": "./modules/greeting.js"
}
}
</script>
<script type="module">
import {welcome} from "greeting";
welcome("Casey"); // logs: "Welcome back, Casey!"
</script>
Import maps allow you to use bare module names, as shown in the example above, allow remapping module paths, allow you to simulate importing modules from packages and allow multiple module versions in an application while referring to them using the same module specifier. See import maps for detailed information.
Differences between modules and standard scripts
An HTML <script>
element that loads a module file or directly contains code that includes an import
statement requires a
type="module"
attribute, as shown in the examples above.
Directly embedded module script within a <script>
element basically acts as a top-level module,
although these "embedded modules" typically do not export features; they only import features from separate module files.
HTML <script>
elements do not need the
defer
attribute when used to load a module.
Modules load and execute deferred automatically, even when the module is directly embedded within the <script>
element.
You can override this default behavior by adding the async
attribute to the <script>
element.
Modules are automatically and always interpreted in strict mode, even without a "use strict";
statement present.
Modules and scope
Module scripts have their own top-level scope. A variable declared in a module is unavailable in other scripts, unless it is exported and imported.
<script type="module">
let myVariable = "Hello";
</script>
<script>
console.log(myVariable); // logs: ReferenceError: myVariable is not defined
</script>
Imported features can only be accessed in the script they are imported into. They aren't available in the global scope.
<script type="module">
import {someConst} from './modules/greeting.js';
console.log(someConst); // logs: "Some value"
</script>
<script>
console.log(someConst); // logs: ReferenceError: someConst is not defined
</script>
Conversely, globally defined variables are available within modules:
<script>
const user = "Jane Doe"
</script>
<script type="module">
import {welcome} from './modules/greeting.js';
welcome(user); // logs: "Welcome back, Jane Doe!"
</script>
Keyword this
used in the global execution context (outside any function, directly in global scope) of a module
refers to undefined
. This contrasts with this
used in the global scope of a non-module script,
where it refers to the global object.
Variables that have been imported cannot be re-assigned a value. They act in a similar way as const
variables.
Variables can only be re-assigned a value by the exporting module.
Like with const
variables, you can still modify properties of object values.
Modules are only executed once
Modules are only executed once, at the first import when there are imports in multiple <script>
elements.
This means, for example, that a function executed in a module will only execute once, even when multiple
scripts import the same object from a module.
<script type="module" src="modules/main.js"></script> <!-- logs: "Welcome back, Casey!" -->
<script type="module" src="modules/main.js"></script> <!-- logs: -->
You can import the same function in multiple modules:
<script type="module">
import {welcome} from './modules/greeting.js';
welcome("Casey"); // logs: "Welcome back, Casey!"
</script>
<script type="module">
import {welcome} from './modules/greeting.js';
welcome("Max"); // logs: "Welcome back, Max!"
</script>
Importing the same object in multiple modules:
// module myObj.js
export const someObj = {someProp: "Some value"};
<script type="module">
import {someObj} from './modules/myObj.js';
someObj.someProp = "Altered value"
</script>
<script type="module">
import {someObj} from './modules/myObj.js';
console.log(someObj.someProp); // logs: "Altered value"
</script>