13 грудня 2021 р.

Динамічні імпорти

Інструкції експорту і імпорту, які ми розглядали в попередньому розділі, називаються “статичними”. Синтаксис у них дуже простий і строгий.

По-перше, ми не можемо динамічно задавати ніякі з параметрів import.

Шлях до модуля має бути строковим примітивом і не може бути викликом функції. Ось так працювати не буде:

import ... from getModuleName(); // Помилка, має бути рядок

По-друге, ми не можемо робити імпорт залежно від умов або в процесі виконання:

if(...) {
  import ...; // Помилка, заборонено
}

{
  import ...; // Помилка, ми не можемо ставити імпорт у блок
}

Усе це результат того, що мета директив import/export – задати кістяк структури коду. Завдяки ним вона може бути проаналізована, модулі можуть бути зібрані в один файл спеціальними інструментами, а невживані експорти видалені. Це можливо тільки завдяки тому, що все статично.

Але як ми можемо імпортувати модуль динамічно, за запитом?

Вираз import()

Вираз import(module) завантажує модуль і повертає проміс, результатом якого стає об’єкт модуля, що містить усі його експорти.

Ми можемо використати його у будь-якому місці коду, наприклад, так:

let modulePath = prompt("Який модуль завантажити?");

import(modulePath)
  .then(obj => <об’єкт модуля>)
  .catch(err => <помилка завантаження, наприклад якщо немає такого модуля>)

Чи якщо усередині асинхронної функції, то можна використати let module = await import(modulePath).

Наприклад, якщо у нас є такий модуль say.js:

// 📁 say.js
export function hi() {
  alert(`Привіт`);
}

export function bye() {
  alert(`Бувай`);
}

…То динамічний імпорт може виглядати так:

let {hi, bye} = await import('./say.js');

hi();
bye();

А якщо в say.js вказаний типовий експорт:

// 📁 say.js
export default function() {
  alert("Модуль завантажився (export default)!");
}

…То для доступу до нього нам слід узяти властивість default об’єкту модуля:

let obj = await import('./say.js');
let say = obj.default;
// або одним рядком: let {default: say} = await import('./say.js');

say();

Ось повний приклад:

Результат
say.js
index.html
export function hi() {
  alert(`Привіт`);
}

export function bye() {
  alert(`Бувай`);
}

export default function() {
  alert("Модуль завантажений (export default)!");
}
<!doctype html>
<script>
  async function load() {
    let say = await import('./say.js');
    say.hi(); // Привіт!
    say.bye(); // Бувай!
    say.default(); // Модуль завантажений (export default)!
  }
</script>
<button onclick="load()">Натисни мене</button>
Будь ласка, зверніть увагу:

Динамічний імпорт працює в звичайних скриптах, він не вимагає вказівки script type="module".

Будь ласка, зверніть увагу:

Хоча import() і виглядає схожим на виклик функції, насправді це спеціальний синтаксис, так само, як, наприклад, super().

Тому ми не можемо скопіювати import в іншу змінну або викликати за допомогою .call/apply. Це не функція.

Навчальна карта