import

Оголошення статичного імпорту import застосовуються для імпорту незмінних живих зв'язувань, які, своєю чергою, експортуються іншим модулем. Імпортовані зв'язування називаються живими, оскільки вони оновлюються модулем, який їх експортує, проте їм не можуть бути присвоєні значення в модулі, що їх імпортує.

Для того, аби отримати змогу вжити оголошення import у файлі з вихідним кодом, цей файл повинен бути інтерпретований середовищем виконання як модуль. В HTML цього можна досягти шляхом додавання type="module" до тега <script>. Модулі автоматично інтерпретуються в суворому режимі.

Також існує подібний до функції динамічний import(), який не вимагає наявності атрибута type="module" на скриптах.

Синтаксис

import defaultExport from "module-name";
import * as name from "module-name";
import { export1 } from "module-name";
import { export1 as alias1 } from "module-name";
import { default as alias } from "module-name";
import { export1, export2 } from "module-name";
import { export1, export2 as alias2, /* … */ } from "module-name";
import { "string name" as alias } from "module-name";
import defaultExport, { export1, /* … */ } from "module-name";
import defaultExport, * as name from "module-name";
import "module-name";
defaultExport

Назва, що вказує на усталений експорт із модуля. Повинна бути дійсним ідентифікатором JavaScript.

module-name

Модуль, з якого відбувається імпорт. Дозволяються лише літерали в одинарних та подвійних лапках. Обчислення специфікатора залежить від середовища. Більшість середовищ узгоджені з браузерами та розв'язують специфікатори як URL, відносні щодо URL поточного модуля (дивіться import.meta.url). Node, збирачі та інші небраузерні середовища нерідко визначають власні додаткові можливості, тож слід шукати їхню власну документацію, аби дізнатися конкретні правила. Також більше інформації можна знайти в розділі розв'язання специфікаторів модулів.

name

Назва об'єкта модуля, яку під час звертання до імпортів буде вжито як свого роду простір імен. Повинна бути дійсним ідентифікатором JavaScript.

exportN

Назви експортованих значень до імпорту. Така назва може бути як ідентифікатором, так і рядковим літералом — залежить від того, що саме модуль module-name оголосив до експорту. Якщо це рядковий літерал — йому слід призначити псевдонім, який уже буде дійсним ідентифікатором.

aliasN

Псевдонім, що вказує на іменовані імпорти. Має бути дійсним ідентифікатором JavaScript.

Після "module-name" може писатися набір атрибутів імпорту, що починається з ключового слова with.

Опис

Оголошення import можуть знаходитися лише всередині модулів, і лише на найвищому рівні (тобто не всередині блоків, функцій тощо). Якщо оголошення import зустрічається у немодульному контексті (як от у скриптових файлах, eval, new Function, які після розбору мають оформитися у "скрипт" чи "функцію") — викидається помилка SyntaxError. Аби завантажити модуль у немодульному контексті — слід використовувати динамічний імпорт.

Жодне з імпортованих зв'язувань не може перебувати в тій же області видимості, що й будь-яке інше оголошення, включно з let, const, class, function, var і оголошенням import.

Оголошення import сконструйовані таким чином, щоб бути синтаксично жорсткими (зокрема: допускаються лише літеральні рядкові вказівники, і лише на верхньому рівні — адже всі зв'язування є ідентифікаторами). Це дає можливість статично аналізувати модулі та синхронно їх компонувати, іще до їхнього виконання. Це — ключова особливість, необхідна аби зробити модулі асинхронними за природою, що дає змогу працювати функціональності штибу await верхнього рівня.

Форми оголошень імпорту

Існує чотири форми оголошень import:

Нижче наведено приклади для пояснення синтаксису.

Іменований імпорт

Нехай дано певне значення під назвою myExport, експортоване з модуля my-module або явно за допомогою інструкції export, або неявно – як export * from "another.js". Такий код додасть значення myExport у поточну область.

import { myExport } from "/modules/my-module.js";

З одного й того ж модуля можна імпортувати декілька імен.

import { foo, bar } from "/modules/my-module.js";

Можна перейменувати експорт під час імпортування його значення. Наприклад, цей код додає значення shortName у поточну область видимості.

import { reallyReallyLongModuleExportName as shortName } from "/modules/my-module.js";

Модуль також може експортувати рядковий літерал, який не є дійсним ідентифікатором. В цьому випадку знадобиться звертатися до нього за псевдонімом, аби мати змогу користуватися ним у поточному модулі.

// /modules/my-module.js
const a = 1;
export { a as "a-b" };
import { "a-b" as a } from "/modules/my-module.js";

[!NOTE] Інструкція import { x, y } from "mod" не є еквівалентною до import defaultExport from "mod" і потім деструктурування x та y із defaultExport. Усталений та іменований імпорт — це різні синтаксичні конструкції модулів JavaScript.

Усталений імпорт

Усталений експорт слід імпортувати за допомогою відповідного йому синтаксису усталеного імпорту. Цей варіант безпосередньо імпортує усталене значення:

import myDefault from "/modules/my-module.js";

Оскільки усталений експорт не вказує імені явно, можна призначити ідентифікаторові будь-яке ім'я, за бажанням.

Також можливо вказувати усталений імпорт разом з імпортом простору імен чи іменованими імпортами. Проте в таких випадках усталений імпорт слід вказувати першим. Наприклад:

import myDefault, * as myModule from "/modules/my-module.js";
// myModule.default та myDefault вказують на одне й те ж зв'язування

...або ж:

import myDefault, { foo, bar } from "/modules/my-module.js";

Імпортування значення, яке називається default, має ефект, ідентичний усталеному імпортові. Йому необхідно призначати псевдонім, оскільки default — це зарезервоване слово.

import { default as myDefault } from "/modules/my-module.js";

Імпорт простору імен

Наступний код вставляє myModule, який містить всі значення, експортовані з модуля /modules/my-module.js, у поточну область.

import * as myModule from "/modules/my-module.js";

В цьому випадку myModule є об'єктом простору імен, який вміщує всі експортовані значення як звичайні властивості. Наприклад, якщо імпортований вище модуль містить експорт doAllTheAmazingThings(), його можна було б викликати таким чином:

myModule.doAllTheAmazingThings();

Значення myModule — це запечатаний об'єкт, чиїм прототипом є null. Усталений експорт доступний за ключем, що зветься default. Більше інформації про це – в розділі об'єкта простору імен модуля.

[!NOTE] JavaScript не підтримує довільні імпорти, як от import * from "module-name", через високу ймовірність конфліктів імен.

Імпортування модуля лише заради його побічних ефектів

Імпорт цілого модуля лише заради його побічних ефектів, без імпортування чогось конкретного. Це запускає глобальний код модуля, проте не імпортує жодних значень.

import "/modules/my-module.js";

Такий підхід часто використовується для поліфілів, які модифікують глобальні змінні.

Підняття

Оголошення імпорту – піднімаються. В цьому випадку це означає, що імпортовані значення доступні в коді модуля навіть до рядка, що їх оголошує, і що побічні ефекти імпортованого модуля виробляються до запуску решти коду поточного модуля.

myModule.doAllTheAmazingThings(); // myModule.doAllTheAmazingThings імпортується на наступному рядку

import * as myModule from "/modules/my-module.js";

Розв'язання специфікаторів модулів

Специфікація ECMAScript не визначає того, як специфікатори модулів повинні розв'язуватись, і покладає це на середовище виконання (наприклад, браузери, Node.js, Deno). Логіка браузерів задана специфікацією HTML, і вона стала де-факто базою для реалізацій у всіх інших середовищах. Поширене визнання специфікаторів трьох типів, реалізованих специфікацією HTML, Node та багатьма іншими:

  • Відносні специфікатори, що починаються з /, ./ або ../, розв'язуються відносно URL поточного модуля.
  • Абсолютні специфікатори, що є валідними URL, розв'язуються як є.
  • Голі (bare) специфікатори, що не належать до двох попередніх типів.

Найпомітніший підступ із відносними специфікаторами, особливо для тих, хто знайомий з поняттями CommonJS, полягає в тому, що браузери забороняють, аби один специфікатор неявно розв'язувався до кількох потенційних кандидатів. У CommonJS, якщо є main.js and utils/index.js, то всі три записи нижче імпортують "усталений експорт" з utils/index.js:

// main.js
const utils = require("./utils"); // Пропущено назву файлу "index.js"
const utils = require("./utils/index"); // Пропущено лише розширення ".js"
const utils = require("./utils/index.js"); // Найбільш явна форма

У вебі це затратно, тому що якщо написати import x from "./utils", то браузер повинен надіслати запити щодо utils, utils/index.js, utils.js і, можливо, багатьох інших URL, поки не знайде модуль, який можна імпортувати. Таким чином, у специфікації HTML специфікатор усталено може бути лише URL, що розв'язується відносно URL поточного модуля. Не можна пропустити розширення файлу чи ім'я файлу index.js. Така логіка була успадкована реалізацією ESM у Node, але не є частиною специфікації ECMAScript. Зверніть увагу, що це не означає, що import x from "./utils" у вебі ніколи не працює. Браузер все одно надсилає запит за URL, і якщо сервер може відповісти коректним вмістом, то такий імпорт спрацює. Це вимагає того, щоб сервер мав реалізацію певної власної логіки розв'язання, тому що зазвичай запити без розширень вважаються запитами на файли HTML. Абсолютні специфікатори можуть бути URL будь-якого роду, що розв'язуються до вихідного коду, який можна імпортувати. Перш за все:

  • URL HTTP завжди підтримуються у вебі, оскільки більшість сценаріїв одразу мають URL HTTP. Вони нативно підтримуються Deno (який із самого початку заснував усю свою систему модулів на URL HTTP), але мають лише експериментальну підтримку в Node – завдяки кастомним завантажувачам HTTPS.

  • URL file: підтримуються багатьма небраузерними середовищами виконання, наприклад, Node, оскільки сценарії зразу мають URL file:, але не підтримуються браузерами через безпекові міркування.

  • URL даних підтримуються багатьма середовищами виконання, серед яких браузери, Node, Deno тощо. Вони корисні для вбудовування маленьких модулів безпосередньо у вихідний код. Підтримуються типи MIME, що позначають вихідний код, який можна імпортувати, як-то text/javascript для JavaScript, application/json для модулів JSON, application/wasm для модулів WebAssembly тощо. (Вони можуть все ж потребувати атрибутів імпортування.)

    // URL HTTP
    import x from "https://example.com/x.js";
    // URL даних
    import x from "data:text/javascript,export default 42;";
    // URL даних для модулів JSON
    import x from 'data:application/json,{"foo":42}' with { type: "json" };
    

    URL даних text/javascript інтерпретуються як модулі, але не можуть скористатися відносними імпортами, оскільки схема URL data: не є ієрархічною. Тобто import x from "data:text/javascript,import y from './y.js';" викине помилку, адже відносний специфікатор './y.js' не може бути розв'язаний.

  • URL node: розв'язуються до вбудованих модулів Node.js. Вони підтримуються Node та іншими середовищами виконання, що заявляють про сумісність із Node, як-от Bun.

Голі специфікатори, популяризовані CommonJS, розв'язуються в директорії node_modules. Наприклад, якщо є import x from "foo", то середовище виконання шукає пакет foo всередині будь-якої з директорій node_modules у батьківських щодо поточного модуля директоріях. Така логіка може відтворюватися в браузерах за допомогою карт імпортування, які також дають змогу налаштувати розв'язання в інші способи. Алгоритм розв'язання модуля також можна виконати програмно, скориставшись функцією import.meta.resolve, визначеною специфікацією HTML.

Приклади

Стандартний імпорт

У цьому прикладі створюється модуль, придатний для повторного використання, який експортує функцію для отримання всіх простих чисел всередині вказаного діапазону.

// getPrimes.js
/**
 * Повертає список простих чисел, що менші за передане `max`.
 */
export function getPrimes(max) {
  const isPrime = Array.from({ length: max }, () => true);
  isPrime[0] = isPrime[1] = false;
  isPrime[2] = true;
  for (let i = 2; i * i < max; i++) {
    if (isPrime[i]) {
      for (let j = i ** 2; j < max; j += i) {
        isPrime[j] = false;
      }
    }
  }
  return [...isPrime.entries()]
    .filter(([, isPrime]) => isPrime)
    .map(([number]) => number);
}
import { getPrimes } from "/modules/getPrimes.js";

console.log(getPrimes(10)); // [2, 3, 5, 7]

Імпортовані значення можуть модифікуватися лише експортером

Імпортований ідентифікатор є живим зв'язуванням, оскільки модуль, що експортує його, може також присвоїти йому нове значення, і тоді імпортоване значення зміниться. Проте модуль, що імпортує його, присвоїти йому нове значення не може. Попри це, будь-який модуль, що користується імпортованим об'єктом, може змінювати його, і таке змінене значення буде спостерігатися в усіх модулях, що імпортують той самий об'єкт.

Крім цього, нове значення можна спостерігати крізь об'єкт простору імен модуля.

// my-module.js
export let myValue = 1;
setTimeout(() => {
  myValue = 2;
}, 500);
// main.js
import { myValue } from "/modules/my-module.js";
import * as myModule from "/modules/my-module.js";

console.log(myValue); // 1
console.log(myModule.myValue); // 1
setTimeout(() => {
  console.log(myValue); // 2; my-module оновив своє значення
  console.log(myModule.myValue); // 2
  myValue = 3; // TypeError: Assignment to constant variable.
  // Приймаючий модуль може лише читати значення, проте не перезаписувати його.
}, 1000);

Специфікації

Сумісність із браузерами

desktop mobile server
Chrome Edge Firefox Internet Explorer Opera Safari WebView Android Chrome Android Firefox for Android Opera Android Safari on iOS Samsung Internet Deno Node.js
import
Chrome Full support 61
Edge Full support 16
Firefox Full support 60
Internet Explorer No support Ні
Opera Full support 48
Safari Full support 10.1
WebView Android Full support 61
Chrome Android Full support 61
Firefox for Android Full support 60
Opera Android Full support 45
Safari on iOS Full support 10.3
Samsung Internet Full support 8.0
Deno Full support 1.0
Node.js Full support 13.2.0
footnote
Dynamic import
Chrome Full support 63
Edge Full support 79
Firefox Full support 67
Internet Explorer No support Ні
Opera Full support 50
Safari Full support 11.1
WebView Android Full support 63
Chrome Android Full support 63
Firefox for Android Full support 67
Opera Android Full support 46
Safari on iOS Full support 11.3
Samsung Internet Full support 8.0
Deno Full support 1.0
Node.js Full support 13.2.0
footnote
Available in workers
Chrome Full support 80
Edge Full support 80
Firefox No support Ні
footnote
Internet Explorer No support Ні
Opera No support Ні
Safari Full support 15
WebView Android Full support 80
Chrome Android Full support 80
Firefox for Android No support Ні
footnote
Opera Android No support Ні
Safari on iOS Full support 15
Samsung Internet Full support 13.0
Deno Full support 1.0
Node.js No support Ні

Дивіться також