Give files explicit exports and imports
ES modules let one file publish selected bindings and another import them. Module code has its own scope and runs in strict mode. In a browser, load the entry script as a module and use paths the browser can resolve.
math.js
export function percentage(earned, maximum) {
if (maximum <= 0) throw new RangeError('Maximum must be positive');
return earned / maximum * 100;
}
export const passMark = 40;app.js
import { percentage, passMark } from './math.js';
console.log(percentage(42, 50)); // 84
console.log(passMark); // 40index.html
Serve the directory through a local HTTP server. Browser modules apply loading and origin rules that can make file:// testing misleading. In native browser imports, an npm package name is not automatically a URL; a bundler or import map may resolve it.
Named versus default exports
Named imports use exported names, with optional aliases such as import { passMark as minimum } .... A default export is imported without braces and can use a local name. Avoid mixing the two forms by guessing; inspect the exporting file's contract.
Live bindings and side effects
An imported binding reflects the exporting module's binding, but the importer cannot reassign it. Imports are not a universal deep-copy mechanism. Modules may perform side effects when evaluated; keep initialization deliberate so importing a utility does not unexpectedly start timers or submit requests.
Cycles and dynamic import
Circular dependencies can expose initialization-order problems. Prefer clear dependency direction. Dynamic import returns a promise and is useful for optional code, but it still needs loading/error handling.
Practice
- Move a formatting helper into its own file with a named export.
- Rename an import locally without editing the original export.
- Break the relative path and inspect the module-loading error.
- Explain why a module's top-level variable is not automatically a window property.