Как работают нативные JavaScript imports

Как работают нативные JavaScript imports

  • 24 октября, 2023
  • читать 6 мин
Владимир Шайтан
Владимир Шайтан Technical Lead в Zoot, Преподаватель Компьютерной школы 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.

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