Перейти к содержимому

62. Dynamic Import

## JavaScript: Мозги. Dynamic Import
![Иллюстрация к уроку](/lessons/javascript-dynamic-import.png)
В этом уроке мы изучим `dynamic import`, мощный инструмент JavaScript, позволяющий загружать модули по требованию, а не сразу при загрузке страницы. Это помогает оптимизировать производительность и уменьшить первоначальное время загрузки.
### Что такое Dynamic Import?
Обычный `import` (статический импорт) выполняется в начале скрипта и загружает все необходимые модули сразу. `Dynamic import`, с другой стороны, позволяет загружать модули асинхронно и только тогда, когда они действительно нужны. Он возвращает промис, который разрешается экспортированным значением модуля.
### Пример кода
Предположим, у нас есть модуль `module.js`:
```javascript
// module.js
export function greet(name) {
return `Привет, ${name}!`;
}

Теперь мы можем использовать dynamic import для загрузки этого модуля:

async function loadModule() {
try {
const module = await import('./module.js');
const message = module.greet('Мир');
console.log(message); // Выведет: Привет, Мир!
} catch (error) {
console.error('Ошибка загрузки модуля:', error);
}
}
loadModule();

В этом примере import('./module.js') возвращает промис, который разрешается экспортированными значениями module.js. Мы используем await для ожидания разрешения промиса и затем вызываем функцию greet из загруженного модуля. Важно обернуть все в try...catch для обработки возможных ошибок при загрузке модуля.

Dynamic import особенно полезен для условной загрузки модулей:

async function loadAnalytics() {
if (userIsAuthenticated()) { // Представим, что есть функция проверки аутентификации
try {
const analytics = await import('./analytics.js');
analytics.trackPageView(); // Предположим, что у модуля есть функция trackPageView
} catch (error) {
console.error('Ошибка загрузки аналитики:', error);
}
} else {
console.log('Пользователь не аутентифицирован, аналитика не загружается.');
}
}
loadAnalytics();

В этом примере модуль analytics.js загружается только в том случае, если пользователь аутентифицирован. Это позволяет избежать ненужной загрузки кода для неавторизованных пользователей.

Dynamic import широко используется в современных веб-приложениях и фреймворках:

  • Code Splitting (Разделение кода): Многие фреймворки, такие как React, Vue и Angular, используют dynamic import для разделения кода на небольшие чанки, которые загружаются по требованию. Это улучшает производительность, особенно для больших приложений. Например, в React можно использовать React.lazy и Suspense для ленивой загрузки компонентов.

  • Маршрутизация: Dynamic import позволяет загружать компоненты, связанные с определенными маршрутами, только тогда, когда пользователь переходит на этот маршрут.

  • Плагины и расширения: Приложения могут использовать dynamic import для загрузки плагинов или расширений по требованию, позволяя пользователям добавлять функциональность без необходимости перезагружать все приложение.

  • Dynamic import возвращает промис.
  • Позволяет загружать модули асинхронно и по требованию.
  • Идеально подходит для code splitting, условной загрузки и плагинов.
  • Улучшает производительность и уменьшает первоначальное время загрузки.
  • Не забывайте про обработку ошибок с помощью try...catch.
## Интерактивный пример
<Playground client:load
template="static"
files={{
"/index.html": `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<style>
body { background: #1e1e2e; color: #cdd6f4; font-family: sans-serif; padding: 20px; margin: 0; }
h2 { color: #89b4fa; margin-top: 20px; }
button { background: #89b4fa; color: #1e1e2e; border: none; padding: 8px 16px; border-radius: 6px; cursor: pointer; margin: 4px; font-size: 14px; }
button:hover { background: #74c7ec; }
button:disabled { background: #45475a; color: #6c7086; cursor: default; }
.box { background: #313244; border-radius: 8px; padding: 16px; margin: 10px 0; }
.output { background: #181825; border-radius: 6px; padding: 12px; font-family: monospace; font-size: 13px; min-height: 60px; color: #a6e3a1; white-space: pre-wrap; }
.loading { color: #f9e2af; }
.error { color: #f38ba8; }
.step { background: #45475a; border-radius: 4px; padding: 4px 8px; font-size: 12px; margin: 2px 0; display: block; }
.step.done { background: #1e3a2f; color: #a6e3a1; }
.step.active { background: #3a3000; color: #f9e2af; }
</style>
</head>
<body>
<h2>Симуляция динамического import()</h2>
<div class="box">
<p style="color:#bac2de;font-size:13px;margin:0 0 10px">
В браузере <code style="background:#181825;padding:2px 6px;border-radius:3px">import()</code>
возвращает Promise. Здесь мы симулируем загрузку модуля с задержкой.
</p>
<button id="loadBtn" onclick="loadModule()">📦 Загрузить модуль</button>
<button onclick="reset()">Сбросить</button>
<div id="steps" style="margin-top:10px"></div>
<div class="output" id="out" style="margin-top:8px">Нажмите кнопку для загрузки модуля</div>
</div>
<h2>Условная загрузка (по авторизации)</h2>
<div class="box">
<button onclick="loadAdmin(true)">Загрузить как admin</button>
<button onclick="loadAdmin(false)">Загрузить как гость</button>
<div class="output" id="adminOut">Нажмите кнопку</div>
</div>
<h2>import() с обработкой ошибок</h2>
<div class="box">
<button onclick="loadWithError()">Загрузить несуществующий модуль</button>
<div class="output" id="errorOut">Нажмите кнопку</div>
</div>
<script>
// Симуляция динамического импорта
function simulateDynamicImport(moduleName, delay = 800) {
return new Promise((resolve, reject) => {
const modules = {
'math': { add: (a,b) => a+b, multiply: (a,b) => a*b, PI: 3.14159 },
'utils': { formatDate: (d) => d.toLocaleDateString('ru'), capitalize: (s) => s[0].toUpperCase()+s.slice(1) },
'admin': { getUsers: () => ['Alice','Bob','Charlie'], deleteUser: (id) => 'Deleted: '+id },
};
setTimeout(() => {
if (modules[moduleName]) resolve(modules[moduleName]);
else reject(new Error('Module "' + moduleName + '" not found'));
}, delay);
});
}
function addStep(text, status) {
const s = document.createElement('span');
s.className = 'step ' + (status || '');
s.textContent = text;
document.getElementById('steps').appendChild(s);
return s;
}
async function loadModule() {
document.getElementById('loadBtn').disabled = true;
document.getElementById('steps').innerHTML = '';
document.getElementById('out').textContent = '';
const s1 = addStep('1. import("./math.js") → Promise {pending}', 'active');
document.getElementById('out').textContent = 'Загрузка модуля...';
await new Promise(r => setTimeout(r, 400));
s1.className = 'step done'; s1.textContent = '1. import("./math.js") → Promise {pending} ✓';
const s2 = addStep('2. Скачивание и парсинг модуля...', 'active');
const mod = await simulateDynamicImport('math');
s2.className = 'step done'; s2.textContent = '2. Модуль загружен ✓';
addStep('3. Promise {fulfilled} → модуль готов', 'done');
const lines = [
'const mod = await import("./math.js");',
'',
'mod.add(5, 3) => ' + mod.add(5, 3),
'mod.multiply(4, 7) => ' + mod.multiply(4, 7),
'mod.PI => ' + mod.PI,
'',
'// Модуль загружен только по запросу — не при старте!',
];
document.getElementById('out').textContent = lines.join('\\n');
}
async function loadAdmin(isAdmin) {
document.getElementById('adminOut').textContent = 'Проверяем права доступа...';
await new Promise(r => setTimeout(r, 300));
if (isAdmin) {
const mod = await simulateDynamicImport('admin', 500);
document.getElementById('adminOut').textContent =
'if (isAdmin) {\\n const { getUsers } = await import("./admin.js");\\n}\\n\\n' +
'getUsers() => [' + mod.getUsers().join(', ') + ']\\n' +
'deleteUser(1) => ' + mod.deleteUser(1);
} else {
document.getElementById('adminOut').textContent =
'Гость: admin-модуль НЕ загружен.\\n\\n// import("./admin.js") не вызван —\\n// код не скачан, bandwidth сохранён ✅';
}
}
async function loadWithError() {
document.getElementById('errorOut').textContent = 'Попытка загрузки...';
try {
await simulateDynamicImport('nonexistent', 400);
} catch(err) {
document.getElementById('errorOut').textContent =
'try {\\n await import("./nonexistent.js");\\n} catch(err) {\\n // Поймано!\\n}\\n\\n' +
'❌ Ошибка: ' + err.message + '\\n\\nВсегда оборачивайте import() в try/catch!';
document.getElementById('errorOut').style.color = '#f38ba8';
setTimeout(() => document.getElementById('errorOut').style.color = '', 2000);
}
}
function reset() {
document.getElementById('loadBtn').disabled = false;
document.getElementById('steps').innerHTML = '';
document.getElementById('out').textContent = 'Нажмите кнопку для загрузки модуля';
}
</script>
</body>
</html>`,
}}
/>