| Previous | Notes index | Next |
|---|---|---|
| Previous: Asynchronous JavaScript | Notes index | Next: HTTP requests with Fetch |
Modules let a program split related code across files. Each module has its own scope, so top-level names do not automatically become global variables. Modules make it clear which values a file provides and which values it needs.
Use a named export for values that another module can import by name. The imported name must match an exported name unless it is renamed.
// math.js
export function add(left, right) {
return left + right;
}
export const taxRate = 0.1;// app.js
import { add, taxRate } from "./math.js";
console.log(add(20, 5));
console.log(taxRate);The import path is relative to the importing module. In a browser, include the file extension in relative module paths.
A module can have one default export. The importing file chooses the local name for it.
// greeting.js
export default function greet(name) {
return "Hello, " + name;
}// app.js
import greet from "./greeting.js";
console.log(greet("Mira"));Named exports work well when a module provides related operations. A default export is useful when a module has one primary value. Keep one style consistent within a project.
Use a script element with type="module". Module scripts are deferred by default, run in strict mode, and support import and export syntax.
<script type="module" src="./app.js"></script>Browsers load modules over HTTP or HTTPS. Some browser security rules prevent module imports from working when an HTML file is opened directly from the file system. Use a local development server when the browser blocks local imports.
The as keyword can rename an imported or exported binding. A module can also re-export values from another module to create a focused public entry point.
import { taxRate as defaultTaxRate } from "./math.js";
console.log(defaultTaxRate);export { add, taxRate } from "./math.js";An import is a live binding to the exported value. If an exporting module updates an exported let binding, importers observe the current value. An importer cannot assign to an imported binding.
Dynamic import returns a promise that fulfills with the module namespace object. It can keep optional code out of the initial path until a person asks for it.
async function openAdvancedPanel() {
const panelModule = await import("./advanced-panel.js");
panelModule.showPanel();
}
openAdvancedPanel();Dynamic import is asynchronous, so handle possible loading errors at the call site.
async function loadPanel() {
try {
const panelModule = await import("./advanced-panel.js");
panelModule.showPanel();
} catch (error) {
console.error("The panel could not be loaded", error);
}
}A module should group a small set of related responsibilities. Avoid circular dependencies where two modules require each other's initialization. Move shared values to a third module when that makes the relationship simpler.
Use explicit exports instead of attaching application values to window. Explicit module boundaries make dependencies easier to find and code easier to test.
- Modules split code across files and keep top-level names scoped.
- Named imports use the name provided by the exporting module.
- A default import can choose its local name.
- Browser modules use script type="module" and load over HTTP or HTTPS.
- Dynamic import returns a promise.
- Imported bindings are read-only to the importing module.
- What problem do modules help solve?
- How does a named import identify the exported value?
- How many default exports can a module have?
- What does script type="module" enable in a browser?
- How are relative import paths resolved?
- What does dynamic import return?
- Can an importing module assign to an imported binding?
- What can help simplify a circular dependency?