Skip to main content

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

Динамічний імпорт, це виклик функції import(), який повертає Promise і дозволяє завантажувати модулі під час виконання програми, а не під час збірки. Завдяки цьому в початковий бандл потрапляє лише те, що потрібно одразу, а решта коду довантажується на вимогу.

Теорія

TL;DR

  • import() це функціоподібний вираз, який повертає Promise з об'єктом модуля.
  • Статичний import виконується на етапі збірки і завжди потрапляє в бандл; динамічний виконується в runtime.
  • Шлях до модуля може бути змінною або шаблонним рядком, чого статичний import не вміє.
  • Складальники використовують import() як точку розрізу для code splitting.
  • Типові сценарії: lazy loading, локалізація, плагіни, важкі залежності, A/B тести.
  • Помилку завантаження ловлять через try/catch або .catch(), бо це звичайний Promise.

Швидкий приклад

javascript
// Dynamic import: the module is fetched only when this line runs const module = await import('./utils.js'); module.sayHello(); // Static import for comparison: resolved at build time, always in the bundle // import { sayHello } from './utils.js';

Синтаксис і що повертає import()

import() можна використовувати як з .then(), так і з await:

javascript
import(modulePath) .then(module => { // the module is loaded module.default(); }) .catch(err => console.error(err));
javascript
const module = await import('./math.js'); const { sum } = module; console.log(sum(2, 3));

Promise резолвиться об'єктом модуля, у якого є всі іменовані експорти та властивість default:

javascript
// math.js export function add(a, b) { return a + b; } export const PI = 3.14;
javascript
const math = await import('./math.js'); console.log(math.add(2, 3)); // 5 console.log(math.PI); // 3.14

Оскільки це звичайний Promise, помилки обробляються стандартно:

javascript
try { const module = await import('./missing.js'); } catch (err) { console.error('Module loading failed:', err); }

Умовне завантаження і динамічні шляхи

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

javascript
if (navigator.language.startsWith('fr')) { const messages = await import('./lang/fr.js'); console.log(messages.default.hello); } else { const messages = await import('./lang/en.js'); console.log(messages.default.hello); }

Шлях може бути змінною або шаблонним рядком, що зі статичним import неможливо:

javascript
const theme = 'dark'; const module = await import(`./themes/${theme}.js`); module.applyTheme();

Важливо: складальник (наприклад, Webpack) все одно має вгадати шаблон шляху, інакше він не включить потрібні файли у збірку. Тому частина шляху має бути статичною, як ./themes/ у прикладі вище.

Класичний випадок, завантаження за дією користувача:

javascript
button.addEventListener('click', async () => { const { openModal } = await import('./modal.js'); openModal(); });

Модуль modal.js завантажиться лише при першому кліку, а не на старті сайту, що економить трафік і пришвидшує запуск.

Коли використовувати динамічний імпорт

СценарійЧому корисно
Lazy loading компонентівЗавантажуємо компонент тільки при переході на потрібну сторінку
Переклади та локалізаціяІмпортуємо лише потрібну мову
Важкі залежностіВідкладене завантаження великих бібліотек (chart.js, moment, three.js)
Плагіни та модульна архітектураПідключаємо функціональність на запит
SSR і code splittingУ Next.js, Vite, Webpack автоматичний розріз бандлів
Feature flags та A/B тестиВантажимо різні модулі залежно від прапорця

Як це працює під капотом

Складальники (Webpack, Rollup, Vite, esbuild) роблять три речі:

  1. Аналізують вирази import('./path') у коді.
  2. Виконують code splitting, тобто розбивають проєкт на окремі чанки.
  3. Під час виклику import() довантажують потрібний чанк по мережі.

Приблизний вигляд результату збірки:

javascript
main.js // the main bundle vendors~chart.js // the shared library chunk chart.js // fetched on click

У React і Next.js це загорнуто в готові хелпери:

javascript
import dynamic from 'next/dynamic'; const Chart = dynamic(() => import('../components/Chart'), { ssr: false, loading: () => <p>Loading...</p>, }); export default function Page() { return ( <div> <h1>Data</h1> <Chart /> </div> ); }

Компонент <Chart> підвантажиться лише у браузері і лише коли користувач відкриє сторінку.

Відмінності від require()

Критерійimport()require()
ТипАсинхронний (Promise)Синхронний
Працює вESM (браузер, Node.js)CommonJS (Node.js)
Динамічний шляхТакОбмежено
Підтримує tree-shakingТакНі
Code splitting у складальникахТакНі

Підсумкова таблиця по самому import():

ПитанняВідповідь
Що робитьЗавантажує модулі асинхронно, на вимогу
Що повертаєPromise з об'єктом модуля
Де працюєES Modules (Node.js, браузер)
Для чогоLazy loading, code splitting, A/B тести, динамічна локалізація
Синтаксисconst module = await import('./file.js')

Типові помилки

  • Забувають, що це Promise. const m = import('./a.js'); m.foo() не спрацює, потрібен await або .then().
  • Повністю динамічний шлях. import(userInput) складальник не зможе проаналізувати, і чанк не потрапить у збірку. Залишайте статичний префікс каталогу.
  • Немає обробки помилок. Мережа може впасти, і відхилений Promise без catch перетворюється на unhandled rejection.
  • Динамічний імпорт усюди. Якщо модуль потрібен одразу на старті, import() лише додає зайвий раунд-тріп по мережі. Дрібні утиліти краще імпортувати статично.
  • Виклик у циклі без кешу. Повторний import() того самого шляху бере модуль з кешу модулів, але зайвий await у гарячому циклі все одно сповільнює код.
  • Очікують, що import() дасть іменований експорт напряму. Він повертає весь об'єкт модуля, тому потрібна деструктуризація: const { sum } = await import('./math.js').

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.