Module is just a file <script type="module"></script> Script is treated as a module, by using the attribute type="module" Modules work only via HTTP(s), not locally. Use http (live) server to work with modules. Modules "use strict" by default Modules have its own scope To make a global variable we can assign it to a window property window.user = "John" Module script can import other modules Import is evaluated ones // πŸ“ hi.js alert("hi!") // πŸ“ main.js import './hi.js' // hi! import './hi.js' // (shows nothing) Scripts duplicates are ignored // 'my.js' is fetched and executed only once <script type="module" src="my.js"></script> <script type="module" src="my.js"></script> External scripts + CORS CORS headers are needed for external imports another-site.com must supply 'Access-Control-Allow-Origin' response header <script type="module" src="http://another-site.com/their.js"></script> Global variable To make a global variable we can assign it to a window property window.user = "John" , but it is not recommended. Defer, async Module scripts are deferred by default and wait until the HTML document is fully ready Module doesn’t block HTML processing, they load in parallel with other resources. Possible to use the async attribute in <script type="module" async> <script type="module"> alert(typeof button); // object: the script can 'see' the button below // as modules are deferred, the script runs after the whole page is loaded </script> Compare to regular script below: <script> alert(typeof button); // button is undefined, the script can't see elements below // regular scripts run immediately, before the rest of the page is processed </script> <button id="button">Button</button> this in module this on top level in module is undefined this in non-module javascript file on top level is window object Relative or absolute path import {sayHi} from 'sayHi.js' - not working in browser import {sayHi} from './sayHi.js' - works Node.js or bundle tools allow bare modules, w/o any path Different exports/imports whole file // πŸ“ from.js alert("hi!") // πŸ“ to.js import './hi.js' // hi! specific variables, functions, classes // πŸ“ from.js export let months = ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; export const MODULES_BECAME_STANDARD_YEAR = 2015; export class User { constructor(name) { this.name = name; } } export function sayHi(user) { alert(`Hello, ${user}!`); } // no ';' at the end // πŸ“ to.js import {months, MODULES_BECAME_STANDARD_YEAR, User, sayHi} from './from.js'; sayHi('John'); // Hello, John! export apart from declarations // πŸ“ from.js let arr = [1, 2, 3]; function func() { alert('I am function') } export {arr, func}; // πŸ“ to.js import {arr, func} from './from.js'; func(); import * all Bad for bundle optimizers such as webpack's β€œtree-shaking” // πŸ“ from.js export let arr = [1, 2, 3]; export function func() { alert('I am function') } // πŸ“ to.js import * as importObj from './from.js'; importObj.arr; // [1, 2, 3] importObj.func(); // 'I am function' export as // πŸ“ from.js let arr = [1, 2, 3]; function func() { alert('I am function') } export {func as exportedFunc, arr as exportedArr}; // πŸ“ to.js import * as importObj from './from.js'; importObj.exportedArr; // [1, 2, 3] importObj.exportedFunc(); // 'I am function' import as // πŸ“ from.js export let arr = [1, 2, 3]; export function func() { alert('I am function') } // πŸ“ to.js import {func as importedFunc, arr as importedArr} from './from.js'; importedArr; // [1, 2, 3] importedFunc(); // 'I am function' export default syntax makes the β€œone thing per module” way look better no need for curly braces there’s a rule that imported variables should correspond to file names import LoginForm from "./loginForm.js" // πŸ“ from.js export default function func() { alert('I am function') } // πŸ“ to.js import func from './from.js'; func(); // 'I am function' Mix of default & named exports // πŸ“ from.js export default class User { constructor(name) { this.name = name; } } export function sayHi(user) { alert(`Hello, ${user}!`); } // πŸ“ to.js import {default as User, sayHi} from './from.js'; new User('John'); // πŸ“ from.js export default class User { constructor(name) { this.name = name; } } export function sayHi(user) { alert(`Hello, ${user}!`); } // πŸ“ to.js import * as user from './from.js'; let User = user.default; // the default export new User('John'); Re -export We may import things and immediately export them import {login, logout} from './helpers.js'; export {login, logout}; // or shorter export {login, logout} from './helpers.js'; // default export needs separate handling when re-exporting // πŸ“ user.js export default class User { } export * from './user.js'; // to re-export named exports export {default} from './user.js'; // to re-export the default export Dynamic imports Dynamic imports work in regular scripts, they don’t require script type="module" import() looks like a function call, but it is not, it’s a special syntax let modulePath = prompt("Which module to load?"); import(modulePath) .then(obj => {}) .catch(err => {}) // πŸ“ say.js export function hi() { alert('Hello') } export function bye() { alert('Bye') } let {hi, bye} = await import('./say.js'); hi(); bye(); // πŸ“ say.js export default function() { alert("Module loaded (export default)!"); } let obj = await import('./say.js'); let say = obj.default; // or, in one line: let {default: say} = await import('./say.js'); say();