Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Динамічний імпорт». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Динамічний імпорт, це виклик функції `import()`, який повертає Promise і завантажує модуль під час виконання програми (runtime), а не на етапі збірки.** Статичний `import` підтягує модуль одразу під час старту, тому весь код потрапляє в початковий бандл. Динамічний `import()` вантажить файл лише тоді, коли він справді потрібен: за кліком користувача, за умовою, за фіча-флагом. Promise резолвиться об'єктом модуля з усіма його експортами, включно з `default`. Складальники (Webpack, Vite, Rollup, esbuild) бачать вираз `import()` і роблять code splitting, тобто виносять модуль в окремий чанк, який довантажується по мережі. Це базовий механізм lazy loading, локалізації, плагінів та відкладеного підключення важких бібліотек. ```javascript button.addEventListener('click', async () => { const { openModal } = await import('./modal.js'); openModal(); }); ``` **Ключове:** `import()` повертає Promise з об'єктом модуля і вантажить код на вимогу, що дає code splitting і швидший старт застосунку.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Динамічний імпорт, це виклик функції `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')`.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.