Какво са модули?

Modules са JavaScript файлове, които се зареждат в специален режим (за разлика от scripts, които се зареждат по оригинален начин в JavaScript).Този различен режим е необходим, защото модулите имат много различна семантика от scripts:

  1. Модул кода автоматично минава в строг режим и няма начин да се откажете от strict mode.
  2. Променливи, създадени в най-горното ниво на модула не се добавят автоматично към споделения глобален обхват. Те съществуват само в рамките на обхвата на модула на най-високо ниво.
  3. Стойността на this в най-горното ниво на модула е undefined.
  4. Модулите не позволяват HTML-стил на коментарите в рамките на кода (останала особеност от ранните дни на браузъра).
  5. Модулите трябва да export (изнасят) всичко, което трябва да бъде на разположение на кода извън модула.
  6. Модули могат да import (внасят) обвързване от други модули.

Тези различия може да изглеждат дребни на пръв поглед, но те представляват значителна промяна в начина, по който JavaScript кода се зарежа и оценява, което ще обсъдим в тази глава. Истинската сила на модулите е способноста да изнасят и внасят само обвързванията, които ви трябват, а не всичко от един файл. Доброто разбиране на export и import е основно за разбирането на това, по което модулите се различават от scripts.

Basic Exporting

Ключовата дума export се използва да изложи части от публикуван код към други модули. В най-простия случай, можете да поставите export пред всяка променлива, функция или клас декларация за да ги изнесете от модула. Например:

// export data
export var color = "red";
export let name = "Nicholas";
export const magicNumber = 7;

// export function
export function sum(num1, num2) {
    return num1 + num1;
}

// export class
export class Rectangle {
    constructor(length, width) {
        this.length = length;
        this.width = width;
    }
}

// тази функция е частна за модула
function subtract(num1, num2) {
    return num1 - num2;
}

// дефиниране на функция....
function multiply(num1, num2) {
    return num1 * num2;
}

// ... и изнасянето и по-късно
export { multiply };
	 		

Има няколко неща, които могат да се забележат в този пример. На първо място, всяка декларация е точно същата, както би била в противен случай без ключовата дума export. Всяка изнесена функция или клас също има име, това е така, защото изнесените декларации на функции и класове изискват име. Вие не можете да изнасяте анонимни функции или класове, използващи този синтаксис (освен ако не се използва ключовата дума default, обсъдена по-подробно в секцията "Default стойности в модули").

На следващо място, помислете за multiply() функцията, която не е предназначена за експорт, когато е дефинирана. Това работи, защото не е необходимо винаги да се изнася декларация. Можете също да изнасяте референции. И накрая обърнете внимание, че този пример не изнася subtract() функцията. Тази функция няма да бъде достъпна извън този модул, защото всички променливи, функции или класове, които не са изрично изнесени остават скрити в модула.

Basic Importing

След като имаме модул с export, имаме достъп до функционалноста в друг модул с помощта на ключовата дума import. Двете части на import декларацията са идентификатори, които се импортират и модула, от който трябва да се внесат тези идентификатори. Това е основната форма на отчета:

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

Фигурните скоби след import показват обвързването на вноса от даден модул. Ключовата дума from показва модула, от която се внася дадено обвързване. Модула се определя от string, представляващ пътя към модула (наречен module specifier). Браузърите използват същия път формат, който може да се подаде към <script> елемент, което означава, че трябва да включва файлово разширение. Node.js, от друга страна, следва своята традиционна конвенция за разграничаване между локални файлове и пакети на базата на префикс за файловата система. На пример example ще бъде пакет, a ./example.js ще бъде локален файл.

info
Списъкът на обвързване за внос изглежда подобно на destructured обект, но не е само това..

При внос на обвързване от модул, обвързването действа все едно, че е дефинирано посредством const. Това означава, че не можете да дефинирате друга променлива със същото име (включително внос на друго обвързване със същото име), да използвате идентификатор преди import изявлението или промяна на стойността му.

Importing a Single Binding

Да предположим, че първият пример в раздела "Basic Експорт" е в един модул с името на файла "example.js". Можете да внесете и използвате идентификатори от този модул по редица начини. Например, можете просто да внесете един идентификатор:

// import just one
import { sum } from "./example.js";

console.log(sum(1, 2));     // 3

sum = 1;        // error
	 		

Въпреки че, example.js изнася повече от една функция, този пример внася само sum() функцията. Ако се опитате да присвоите нова стойност на sum, резултата е грешка, тъй като не можете да презаписвате импортираните идентификатори.

worning
Уверете се, че сте включили   / ,  ./ , или  .../  в началото на файла, който внасяте за по-добра съвместимост между браузъра и Node.js.

Внос на множество обвързвания

Ако искате да внесете множество обвързвания от "example" модула, трябва изрично да ги изброите, както следва:

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

Тука, три идентификатора се внасят от example модула: sum, multiply и magicNumber. След това се използват, все едно са дефинирани на място.

Внос на всичко от модула

Има също така и специален случай, който позволява да внесем целия модул, като обект. Всичко от износа след това е на разположение в този обект, като свойства. Например:

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

В този код, всички изнесени обвързвания от example.js са заредени в обект, наречен example. Изнесените имена sum(), multiple() и magicNumber след това са достъпни, като свойства на example. Този формат на внос се нарича namespace import, тъй като example обектът не съществува вътре в файла example.js и вместо това е създаден, за да бъде използван, като namespace обект за всички изнесени членовете example.js.

Имайте предвид, че няма значение колко пъти използвате модула в import изявление, модулът ще бъде изпълнен само веднъж. След, като се изпълни кода за внос на модула, инстанцията на модула се съхранява в паметта и се използва повторно всеки път, когато друго import изявление има препратки към него. Помислете върху следното:

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

Въпреки, че има три import изявления в този модул, example.js ще се изпълни само веднъж. Ако други модули в същото приложение внасят обвързвания от example.js, тези модули ще използват същата модул инстанция, който този код използва.

Ограничения на модул синтаксиса

Важно ограничение на export и import е, че те трябва да бъдат използвани извън други изявления и функции. Например, този код ще направи синтактична грешка:

if (flag) {
export flag;    // syntax error
}
	 			

Декларацията за export е вътре в if изявление, което не е позволено. Износът не може да бъде условен или направен динамично по никакъв начин. Една от причините да съществува модул синтаксис е да позволи на JavaScript машината, staticly да определи, какво да бъде изнесено. Като такъв, можете да използвате export само на най-високото ниво в модула.

По същия начин не можете да използвате import във вътрешността на декларация. Можете да го използвате само на най-високото ниво. Това означава, че този код също дава синтактична грешка:

function tryImport() {
import flag from "./example.js";    // syntax error
}
	 			

Не можете да внасяте динамично обвързвания по същата причина, поради която не можете да изнасяте динамично обвързвания. Ключовите думи export и import са предназначени да бъдат статични, така че инструменти, като текстови редактори да могат лесно да кажат каква информация е на разположение от модула.

A Subtle Quirk of Imported Bindings

В ECMAScript 6 import изявленията създават обвързвания само за четене на променливи, функции и класове, а не просто да се позовават на първоначалното обвързване, като нормални променливи. Въпреки, че модула, който внася обвързването не може да променя стойността си, модула, който изнася идентификатор може. Да предположим например, че искате да използвате този модул:

export var name = "Nicholas";
export function setName(newName) {
    name = newName;
}
	 			

Когато импортирате тези две обвързвания, функцията setName() може да промени стойността на name:

import { name, setName } from "./example.js";

console.log(name);       // "Nicholas"
setName("Greg");
console.log(name);       // "Greg"

name = "Nicholas";       // error
	 			

Извикването на setName("Greg") се връща обратно в модула, от който setName() се изнася и изпълнява там настройването на name към "Greg". Имайте в предвид, че тази промяна се отразява автоматично върху вноса на name обвързването. Това е защото name е локално име за изнасяния name идентификатор. The name използван в кода по-горе и name, използван в модула се внасят. но не са едни и същи.

Преименуване на износа и вноса

Понякога може да не искате да използвате оригиналното име на променлива, функция или клас внесени от един модул. За щастие, можете да промените името на износа, както по време на изнасянето, така и по време на внасянето.

В първия случай, да предположим, че имате функция, която искате да изнeсете с различно име. Можете да използвате, ключовата дума as за да зададете името на функцията, което трябва да се знае извън модула:

function sum(num1, num2) {
    return num1 + num2;
}

export { sum as add };
	 			

Тука, функцията sum() (sum е локално име) се изнася, като add() (add е exported name). Това означава, че ако друг модул иска да внася тази функция, ще трябва да използва името add:

import { add } from "./example.js";	 				
	 			

Ако внасящия модул на функцията иска да използва друго име, той също може да използва as:

import { add as sum } from "./example.js";
console.log(typeof add);            // "undefined"
console.log(sum(1, 2));             // 3
	 			

Този код внася add() функцията използвайки import name и я преименува на sum() (local name). Това означава, че няма идентификатор с име add в този модул.

Стойности по подразбиране в модули

Модул синтаксиса е наистина оптимизиран за изнасяне и внасяне на стойности по подразбиране от модули, като този модел е често срещан и в други модулни системи, като CommonJS (друга спецификация за използване на JavaScript извън браузъра). Стойността по подразбиране за един модул е променлива, функция или клас, които са посочени с ключовата дума default и можете да зададете само един износ по подразбиране за модул. Използването на ключовата дума default за няколко изнасяния е синтактична грешка.

Exporting Default Values

Ето един прост пример, който използва ключовата дума default:

export default function(num1, num2) {
    return num1 + num2;
}
	 			

Този модул изнася функция, като негова стойност по подразбиране. Ключовата дума default показва, че това е износ по подразбиране. Функцията не изисква име, защото самия модул представлява функцията.

Можете също да зададете идентификатор по подразбиране за износ, като го поставите след export default, като например:

function sum(num1, num2) {
    return num1 + num2;
}

export default sum;
	 			

Тука sum() функцията първо се дефинира и по-късно се изнася, като стойност по подразбиране на модула. Вие може да искате да изберете този подход, ако стойността по подразбиране трябва да бъде изчислена.

Третият начин да зададете идентификатор, като износ по подразбиране е с помощта на преименуване на синтаксиса, както следва:

function sum(num1, num2) {
    return num1 + num2;
}

export { sum as default };
	 			

Идентификаторът default има специално значение в преименуването на износа и показва стойността, която трябва да бъде по подразбиране за модула. Понеже default е ключова дума в JavaScript, тя не може да се използва за променлива, функция или име на клас (тя може да се използва, като име на свойство). Така че използването на default за преименуване на износ е специален случай, който създава съгласуваност с това, как се дефинира non-default износ. Този синтаксис е полезен, ако искате да използвате една export декларация за определите няколко изнасяния, включително и по подразбиране, наведнъж.

Importing Default Values

Можете да внесете стойност по подразбиране от модул, като използвате следния синтаксис:

// внос по подразбиране
import sum from "./example.js";

console.log(sum(1, 2));     // 3
	 			

Това import изявление внася по подразбиране от модула example.js. Обърнете внимание, че не се използват фигурни скоби, за разлика от non-default import. Локалното име sum се използва за представяне на функцията, като default износ на модула. Този синтаксис е най-чист и създателите на ECMAScript 6 очакват той да бъде доминиращ вид на внос в интернет, което ви позволява да използвате вече съществуващ обект.

За модули, които изнасят default и един или повече non-default, можете да внесете всички изнесени обвързвания с една декларация. Например да предположим, че имате този модул:

export let color = "red";

export default function(num1, num2) {
    return num1 + num2;
}
	 			

Можете да внесете color и функцията по подразбиране с помощта на следната import декларация:

import sum, { color } from "./example.js";

console.log(sum(1, 2));     // 3
console.log(color);         // "red"
	 			

Запетаята разделя локалното име по подразбиране от не по подразбиране, което е заобиколено от фигурни скоби. Имайте предвид, че по подразбиране трябва да бъде преди не-по подразбиране в import изявлението

Както при изнасяне по подразбиране, така и при внасяне по подразбиране може да импортирате default с помощта на синтаксиса за преименуване.

// еквивалентно на предишния пример
import { default as sum, color } from "example";

console.log(sum(1, 2));     // 3
console.log(color);         // "red"
	 			

В този код износа по подразбиране (default) е преименуван на sum и допълнителния износ на color също се внася. Този пример е еквивалентен на предишния

Re-exporting a Binding

Възможно е някой път да искате да реекспортирате нещо, което модула е внесъл (например, ако се създаде библиотека от няколко по-малки модули). Можете да изнесете повторно внесената стойност с моделите, които вече разгледаме в тази глава, както следва:

import { sum } from "./example.js";
export { sum }
	 				
	 			

Това работи, но има една декларация, която може да направи същото нещо:

export { sum } from "./example.js";
	 			

Тази форма на export се оглежда в указания модул за декларация на sum и след това я изнася. Разбира се можете да изберете различно име за изнасяне на едно и също нещо.

export { sum as add } from "./example.js";
	 			

Тука, sum се внася от "./example.js" и след това се изнася, като add.

Ако искате да изнасяте всичко от друг модул, можете да използвате модела със звезда * :

export * from "./example.js";	 			
	 		

Чрез изнасяне на всичко включва по подразбиране, както и всяко име за износ, което може да повлияе на това, което може да изнесете от вашия модул. Например, ако "./example.js" има износ по подразбиране, вие можете да дефинирате нов износ по подразбиране, когато използвате този синтаксис.

Внос без обвързване

Някои модули не могат да изнасят нищо и вместо това, правят само модификации на обектите в глобалния обхват. Въпреки, че най-високото ниво променливи, функции и класове вътре в модула, не попадат автоматично в глобалния обхват, това не означава, че модулите не могат да получат достъп до глобалния обхват. Общите дефиниции на вградени обекти, като Array и Object са достъпни в един модул и промени в тези обекти ще бъдат отразени в други модули.

Например, ако искате да добавите метод за всички масиви, наречен pushAll(), можете да дефинирате модул, като този:

// код на модул без износ и внос
Array.prototype.pushAll = function(items) {

    // елементите тябва да бъдат масив
    if (!Array.isArray(items)) {
        throw new TypeError("Argument must be an array.");
    }

     // използване на вграден push() и оператор spread
    return this.push(...items);
};
	 			

Това е валиден модул, въпреки, че няма износ и внос. Този код може да се използва, както от модул, така и от скрипт. Тъй като не изнася нищо, можете да използвате опростен внос за изпълнение на кода в модул, без импортни обвързвания:

import "./example.js";

let colors = ["red", "green", "blue"];
let items = [];

items.pushAll(colors);
	 			

Този код внася и изпълнява модула, съдържащ pushAll() метода, така че pushAll() се добавя към прототипа на масиви. Това означава, че pushAll() вече е на разположение за използване от всички масиви във вътрешността на този модул.

info
Внос без обвързвания най-вероятно ще бъде използван за създаване на polyfills и shims.

Зареждане на модули

Докато ECMAScript 6 определя синтаксиса за модули, той не определя как да ги заредите. Това е част от сложността на дадена спецификация, която е би трябвало да бъде последователна към средите за изпълнение. Вместо да се опитва да създаде единна спецификация, която да работи във всички JavaScript среди, ECMAScript 6 определя само синтаксиса и контекста на механизъма за зареждане на не определена вътрешна операция, наречена HostResolveImportedModule. Уеб браузърите и Node.js са оставени да решат, по какъв начин да се приложи HostResolveImportedModule, което да има смисъл за съответните среди.

Използване на модули в уеб браузъри

Дори преди ECMAScript 6 уеб браузърите имат множество начини за включване на JavaScript в уеб приложение. Тези script опции за зареждане са:

  1. Зареждане на JavaScript код на файлове с помощта на <script> елементa с атрибут src, уточняващ място от което да се зареди кода.
  2. Включване на JavaScript код inline използвайки <script> елементa без src атрибут.
  3. Зареждане на JavaScript код на файлове за изпълнение, като workers (също като web worker или service worker).

За пълната поддръжка на модули, уеб браузърите трябваше да актуализират всеки един от тези механизми. Тези данни са определени в HTML спецификацията и ще ги обобщим в този раздел.

Използване на модули със <script>

Поведението по подразбиране на <script> елемента е да зареди JavaScript файлове, като скриптове (не модули). Това се случва, когато type атрибутът липсва или когато type атрибутът съдържа тип JavaScript съдържание (като "text/javascript"). Елементът <script> тогава може да изпълни inline код или зареди файл, указан в src. В подкрепа на модули, "module" стойността е добавенa, като type опция. Определянето на type на "module", казва на браузъра да зареди целия inline код или код, съдържащи се във файл, указан от src, като модул вместо скрипт. Ето един прост пример:

<!-- load a module JavaScript file -->
<script type="module" src="module.js"> </script>

<!-- include a module inline -->
<script type="module">
import { sum } from "./example.js";

let result = sum(1, 2);
</script>
	 			

Първият <script> елемент в този пример зарежда файл от външен модул, с помощта на src атрибута. Единствената разлика от зареждане със скрипт е, че "module" е даден, като type. Вторият <script> елемент съдържа модул, който е включен директно в уеб страницата. Променливата result не е изложена глобално, защото тя съществува само в рамките на модула (както е определен от <script> елемента) и следователно не се добавя към window, като свойство.

Както можете да видите, включването на модули в уеб страници е сравнително просто и подобно на включването на скриптове. Въпреки това има някои разлики в това, как са зареждат модули.

info
Може би сте забелязали, че "module" не е вид съдържание, например, както "text/javascript". Модул JavaScript, сервира файлове със същия вид съдържание, както скрипта на JavaScript файловете, така че не е възможно да се разграничат единствено на базата на вида съдържание. Също така, браузърите игнорират <script> елементи, когато type е непознат, така браузъри, които не поддържат модули автоматично ще игнорират <script type="module"> линията, за осигуряване на по-добра обратна съвместимост.

Поредност на зареждане на модули в Уеб Браузъри

Модулите са уникални с това, за разлика от скриптовете те могат да използват import за да уточнят, че и други файлове трябва да бъдат заредени, за да се изпълняват правилно. В подкрепа на тази функционалност, <script type="module"> винаги действа все едно, че се прилага атрибутът defer.

Атрибута defer е по избор при зареждане на скриптови файлове, но винаги се прилага при зареждане на модул файлове. Модул файловете започват изтеглянето веднага, след като HTML анализатора срещне <script type="module"> със src атрибут, но не ги изпълняват, докато документът не е напълно анализиран. Модулите също се изпълняват в реда, в който се появяват в HTML файла. Това означава, че първият <script type="module"> винаги гарантирано се изпълнява преди втория, дори ако един модул съдържа inline код вместо да го уточните със src. Например:

<!-- този ще бъде изпълнен първи -->
<script type="module" src="module1.js"> </script>

<!-- този ще бъде изпълнен втори  -->
<script type="module">
import { sum } from "./example.js";

let result = sum(1, 2);
</script>

<!-- този ще бъде изпълнен трети -->
<script type="module" src="module2.js"> </script>
	 			

Тези три <script> елемента се изпълняват в реда, по който са определени, така че module1.js гарантирано ще се изпълни преди inline модула и inline модула гарантирано ще се изпълни преди module2.js.

Всеки модул може да внася от един или повече други модули, което усложнява материята. Ето защо модулите първо се анализират напълно, за да се идентифицират всички import декларации. Всяка import декларация след това започва донасяне (от мрежата или от кеша), но модула не се изпълнява, докато всички import ресурси първо не са заредени и изпълнени.

Всички модули, както тези, които изрично включват използване на <script type="module"> и тези, които мълчаливо включват използване на import се зареждат и изпълняват по ред. В предишния пример, последователноста на пълното зареждане е:

  1. Изтегляне и анализиране на module1.js.
  2. Рекурсивно изтегляне и анализиране на import ресурсите в module1.js..
  3. Анализиране на inline модула.
  4. Рекурсивно изтегляне и анализиране на import ресурсите в inline модула..
  5. Изтегляне и анализиране на module2.js.
  6. Рекурсивно изтегляне и анализиране на import ресурсите в module2.js..

След като зареждането завърши, нищо не се изпълнява докато документът не е напълно анализиран. След като анализирането на документа завърши, се случват следните действия:

  1. Рекурсивно изпълнение на import ресурсите за module1.js.
  2. Изпълнение на module1.js..
  3. Рекурсивно изпълнение на import ресурсите за inline модула.
  4. Изпълнение на inline модула..
  5. Рекурсивно изпълнение на import ресурсите за module2.js.
  6. Изпълнение на module2.js..

Забележете, че inline модула действа, като другите два модула с изключение на това, че кодът не трябва да бъде изтеглен първо. Иначе, последователноста на зареждането на import ресурси и изпълнението на модули е точно същото.

info
Атрибутът defer се игнорира в <script type="module">, защото той вече се държи, все едно, че defer се прилага.

Асинхронно зареждане на модули в уеб браузъри

Вие може вече да сте запознати с атрибута async на <script> елементa. Когато се използва със скриптове, async предизвиква скрипт файла да се изпълни веднага, след като файлът е напълно изтеглен и анализиран. Реда на async скриптовете в документа не се отразява на реда, по който се изпълняват скриптовете, все пак. Скриптовете винаги се изпълняват веднага, след като приключи изтеглянето, без да чакат документа да завърши анализирането.

Async атрибута може да бъде приложен на модули. Използване на async в <script type="module"> предизвиква модула да се изпълни по начин, подобен на скрипт. Единствената разлика е, че всички import ресурси за модула са изтеглени преди самия модул да се изпълни. Това гарантира, че всички ресурси за модула, от които се нуждае за да функционира, ще бъдат изтеглени преди изпълнението на модула; просто не може да се гарантира, кога модулът ще се изпълни. Да разгледаме за следния код:

<!-- няма гаранция, кой от тези ще се изпълни първи -->
<script type="module" async src="module1.js"> </script>
<script type="module" async src="module2.js"> </script>
	 			

В този пример има два модул файла заредени асинхронно. Не е възможно да се каже, кой модул ще се изпълни първи, просто като се гледа този код. Ако module1.js завърши първи изтеглянето (включително всички свои import ресурси), тогава той ще се изпълни първи. Ако module2.js завърши първи изтеглянето, тогава този модул ще се изпълни първи.

Зареждане на модули, като Workers

Workers, като web workers и service workers, изпълняват JavaScript код извън контекста на уеб страницата. Създаването на нов worker включва създаването на нова инстанция worker (или друг клас) и подаване на местоположението на JavaScript файл. По подразбиране, механизъма за зареждане е да зареди файловете, като скриптове, като този:

// зареждане на script.js, като скрипт
let worker = new Worker("script.js");
	 			

В подкрепа на зареждането на модули, програмистите на стандарта HTML, добавят втори аргумент към този конструктор. Вторият аргумент е обект с type свойство със стойност по подразбиране "script". Можете да зададете на type "module" за да зареди модул файлове:

// зареждане на module.js, като модул
let worker = new Worker("module.js", { type: "module" });
	 			

Този пример зарежда module.js, като модул вместо скрипт, като подава втори аргумент с "module", като стойност на type свойството. (Свойство type има за цел да имитира това, как type атрибутът на <script> дефинира модули и скриптове). Вторият аргумент се поддържа за всички видове worker в браузъра.

Worker модулите обикновено са същите, като worker скриптове, но има и няколко изключения. Първо worker скриптовете са ограничени да бъдат заредени от същия произход, като уеб страницата, в която те са посочени, но worker модулите не са толкова ограничени. Въпреки, че worker модулите имат същото ограничение по подразбиране, те също така могат да заредят файлове, които имат подходящи Cross-Origin Resource Sharing (CORS) заглавия да позволяват достъп. Второ докато worker скрипта може да използва self.importScripts() метода за зареждане на допълнителни скриптове в worker, self.importScripts() винаги се проваля на worker модули, защото вие трябва да използвате import вместо него.

Browser Module Specifier Resolution

Всички примери в тази глава до този момент използват относителен модул спецификатор на пътя, като ". / example.js'. Браузъри изискват модул спецификаторите да бъдат един от следните формати:

  • Започва с  /  за разрешаване от главната директория
  • Започва с  ./  за разрешаване от текущата директория
  • Започва с  ../  за разрешаване от родителската директория
  • URL формат

Например, да предположим, че имате модул файл, намиращ се на https://www.example.com/modules/module.js, който съдържа следния код:

// imports from https://www.example.com/modules/example1.js
import { first } from "./example1.js";

// imports from https://www.example.com/example2.js
import { second } from "../example2.js";

// imports from https://www.example.com/example3.js
import { third } from "/example3.js";

// imports from https://www2.example.com/example4.js
import { fourth } from "https://www2.example.com/example4.js";
	 			

Всеки от спецификаторите на модула в този пример е валиден за използване в браузър, включително пълният URL адрес в последния ред (трябва да се уверете, че www2.example.com правилно e конфигурирал Cross-Origin Resource Sharing (CORS) заглавията, за да позволи кръстосано домейн зареждане). Това са единствените модул спецификатор формати, които браузърите могат да разрешат по подразбиране (въпреки че, спецификацията за зареждането не е още завършена, модул ще предоставят начини за разрешаване на други формати). Това означава, че някои нормално изглеждащи модул спецификатори, всъщност са невалидни в браузърите и ще доведе до грешка, като например:

// invalid - doesn't begin with /, ./, or ../
import { first } from "example.js";

// invalid - doesn't begin with /, ./, or ../
import { second } from "example/index.js";
	 			

Всеки един от тези модул спецификатори, не може да се зареди от браузъра. Двата модул спецификатора са в невалиден формат (липсват правилните начални характери), въпреки че и двата ще работят, когато се използват, като стойност на src в <script> tag. Това е умишлена разлика в поведението между <script> и import.