Як працюють нативні JavaScript imports

Як працюють нативні JavaScript imports

  • 24 жовтня, 2023
  • читати 6 хв
Володимир Шайтан
Володимир Шайтан Lead Front-end Developer у One Source, Викладач Комп'ютерної школи Hillel.

Стандарт EcmaScript 2015 (ES6) ввів новий інструмент для роботи з модулями в JavaScript, відомий як import. Цей інструмент дозволяє легко імпортувати функції, класи, змінні та об'єкти з інших файлів, створюючи при цьому модульну структуру вашого коду.

В цій статті ми розглянемо, як працюють нативні JavaScript import-и. Важливо розуміти, що в цьому матеріалі ми не торкаємося теми того, як працюють імпорти в інструментах збірки, наприклад, webpack.

Навіщо потрібні import-и?

Модульний код допомагає розділити програму на логічні частини (модулі), що полегшує організацію, підтримку і розширення коду.

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

Як працюють import-и

Імпорти в JavaScript визначаються специфікацією EcmaScript (ES6 і пізніших версій). Під капотом механізм імпорту включає кілька ключових аспектів:

Розміщення файлів

Коли ви робите імпорт в JavaScript, ви вказуєте шлях до файлу, який потрібно імпортувати. Цей шлях може бути абсолютним або відносним, і браузер (або середовище виконання, якщо це Node.js) виконує запит до сервера для завантаження цього файлу.

HTTP-запит

Браузер (або середовище виконання) робить HTTP-запит на сервер для завантаження файлу, вказаного в імпорті. Це може бути GET-запитом або іншими HTTP-методами.

Завантаження файлу

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

Виконання коду

Після завантаження файлу, його вміст обробляється і виконується в середовищі виконання (браузер або Node.js). Якщо файл містить імпорти, то імпортовані модулі також завантажуються і виконуються.

Область видимості

Імпортовані змінні або функції стають доступними в області видимості файлу, який робить імпорт.

Розглянемо приклад:

// module.js
export function greet(name) {
  console.log(`Hello, ${name}!`);
}

// main.js
import { greet } from './module.js';

greet('Alice');

Під час виконання main.js браузер (або Node.js) виконає HTTP-запит до файлу module.js, завантажить його вміст, виконає функцію greet і виведе «Hello, Alice!».

Як V8 JavaScript Engine робить імпорти?

V8 — це віртуальна машина JavaScript, яка використовується в браузерах та Node.js для виконання JavaScript-коду. V8 взаємодіє з імпортами в JavaScript через стандартні механізми ECMAScript (ES6 і пізніших версій).

Розглянемо, як V8 оброблює імпорти:

Аналіз і парсинг:

Під час аналізу і парсингу імпортованих файлів, JavaScript-движок (у нашому випадку, V8) розбирає вхідний текст коду та ідентифікує ключові слова import та export. Він також розпізнає імена, які імпортуються та експортуються. Парсер розуміє синтаксис мови та визначає структуру модуля.

Розпізнавання шляхів:

Під час парсингу V8 розпізнає шляхи до файлів, які потрібно імпортувати. Він визначає, чи є ці шляхи абсолютними (починаються з `/`) або відносними (починаються з `./` або `../`).

Внутрішня логіка визначає, який файл потрібно завантажити.

Завантаження файлів:

V8 ініціює завантаження файлів, вказаних у імпортах.

У веббраузерах це може включати виконання HTTP-запиту до сервера за допомогою API, такого як `fetch`, для отримання вмісту файлу. У середовищі Node.js це може включати звернення до файлової системи для читання файлу з диска.

Кешування імпортів:

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

Виконання коду:

Після завантаження V8 обробляє вміст імпортованих файлів та виконує JavaScript-код.

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

Побудова залежностей:

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

Цей механізм дозволяє розробникам створювати структуру проєкту, що складається з різних модулів, і використовувати їх відповідно до потреб.

Події та обробка помилок:

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

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

Область видимості:

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

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

Ці механізми взаємодіють, щоб дозволити створювати структурований та модульний код в JavaScript.

Як використовувати import?

Імпорти в JavaScript (JS) і TypeScript (TS) — це спосіб включити функціональність з інших модулів (файлів) в ваш код. Це допомагає організувати проєкт та підтримувати чистоту коду, розділяючи його на менші, самостійні частини.

У JavaScript і TypeScript існує декілька способів імпорту. Розглянемо основні з найпростішими прикладами мовою JavaScript.

Default Imports (імпорт за замовчуванням):

У файлі module.js ми можемо мати функцію за замовчуванням, яку хочемо імпортувати.

// module.js
export default function greet(name) {
  console.log(`Hello, ${name}!`);
}

В іншому файлі ми можемо імпортувати цю функцію так:

// main.js
import greet from './module.js';
greet('Alice');

Named Imports (імпорт за іменем):

// module.js
export function greet(name) {
  console.log(`Hello, ${name}!`);
}

export function farewell(name) {
  console.log(`Goodbye, ${name}!`);
}

В іншому файлі ми імпортуємо їх так:

// main.js
import { greet, farewell } from './module.js';

greet('Charlie');
farewell('David');

Aliasing (імпорт із псевдонімами):

Ви можете створити псевдоніми для імпортованих модулів або об'єктів, щоб полегшити їх використання.

// module.js
export function greet(name) {
  console.log(`Hello, ${name}!`);
}

// main.js
import { greet as sayHello } from './module.js';

sayHello('Grace');

Це основні приклади імпортів в JS. Якщо вам потрібно імпортувати об'єкти чи інші речі, синтаксис залишається схожим, інколи інші конструкції можуть використовуватися для різних ситуацій.

Про динамічний імпорт

Динамічний імпорт — це механізм у JavaScript, який дозволяє завантажувати модулі не в момент компіляції, а під час виконання програми. Він надає більшу гнучкість та дозволяє оптимізувати завантаження модулів, особливо в великих додатках.

Синтаксис

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

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

const dynamicImport = import('./myModule.js');

Використання Promise:

Об'єкт, який повертається з import(), є обіцянкою (Promise). Ви можете використовувати метод .then() або ключове слово await для розв'язання цієї обіцянки та виконання коду, коли модуль завантажено.

Приклад використання .then():

dynamicImport
  .then((module) => {
    // Використання завантаженого модуля
  })
  .catch((error) => {
    // Обробка помилки завантаження
  });

Приклад використання await:

try {
  const module = await dynamicImport;
  // Використання завантаженого модуля
} catch (error) {
  // Обробка помилки завантаження
}

Ліниве завантаження

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

Залежності від змінних

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

const moduleName = 'myModule';
const dynamicImport = import(`./${moduleName}.js`);

Цікаве

Імпорт у формі export * from …

Ви можете імпортувати всі експорти з іншого модулю за допомогою export *.

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

export * from './myModule.js';

Зовнішній імпорт файлів

У деяких випадках, окрім завантаження модулів JavaScript, ви можете імпортувати інші типи файлів, такі як зображення або стилі CSS.

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

import myImage from './myImage.png';

Модульні імпорти в HTML

Ви можете використовувати імпорти модулів безпосередньо в HTML-файлах, щоб завантажувати та виконувати JavaScript-код під час завантаження сторінки.

<script type="module">
  import myModule from './myModule.js';
  // Ваш код
</script>

Динамічні ключі для імпортів

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

const moduleName = 'myModule';
import(`./${moduleName}.js`)
  .then((module) => {
    // Використання завантаженого модуля
  });

Висновок

Використання імпортів у JavaScript має численні переваги:

  • Модульність:

Імпорти дозволяють розділити ваш код на невеликі, самодостатні модулі, що полегшує розробку, розуміння та підтримку коду.

  • Відокремлення функціональності:

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

  • Збереження ресурсів:

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

  • Залежності та підтримка:

Імпорти допомагають керувати залежностями вашого проєкту та полегшують оновлення модулів.

  • Ліниве завантаження:

Деякі імпорти можуть бути завантажені «лінійно», тобто лише тоді, коли вони фактично потрібні, зменшуючи час завантаження сторінки.

  • Зручність та читабельність:

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

Всі ці переваги роблять імпорти важливим інструментом для розробки як невеликих, так і великих додатків у JavaScript.

Рекомендуємо курс по темі