Модули и инструменты
Что это такое
Модули делят JavaScript на файлы с явными импортами и экспортами, так что каждый файл сам заявляет, что ему нужно и что он предлагает. Инструменты сборки превращают эти файлы вместе с зависимостями и не-JavaScript-ресурсами во что-то, что браузер загрузит эффективно. Изучают их обычно вместе, потому что осмысленными инструменты делает именно наличие модулей.
Почему это важно
Один файл скрипта не масштабируется дальше нескольких сотен строк: каждое имя глобально, порядок загрузки решает, будет ли код корректен, а переиспользовать что-либо без копирования не получится. Модули дают коду границу и граф зависимостей — без них тестирование, tree-shaking и отложенная загрузка попросту невозможны. Инструменты нужны потому, что браузеры всего этого сами не умеют — по крайней мере, с теми же гарантиями, — и потому, что альтернатива, то есть доставка каждого файла и каждой зависимости каждому посетителю, чаще всего и делает первую загрузку медленной.
Ключевые идеи
Именованные экспорты, явные импорты
ES-модуль объявляет, что он предлагает и что берёт. Именованные экспорты предпочтительнее экспорта по умолчанию: при импорте их переименовывают осознанно, опечатка в имени сразу заметна, а сам символ становится понятнее. Импорты поднимаются и являются живыми привязками: модуль, который их импортировал, увидит более поздние изменения экспортированного let, — в отличие от скопированного значения, и это иногда удивляет.
// format.js
export function formatMoney(cents) { /* … */ }
export const CURRENCY = "GBP";
// invoice.js
import { formatMoney } from "./format.js";
Модуль выполняется один раз
Выполнение модуля происходит один раз на URL, сколько бы мест его ни импортировало, а порядок задаёт граф, а не теги <script>. Поэтому операторы верхнего уровня выполняются при первом импорте; если такой код делает работу, момент её запуска никто не выбирал.
Динамический импорт разбивает граф
import() возвращает промис и загружает модуль по требованию — так маршрут или тяжёлый редактор остаются вне начального бандла. Однако статический import так не умеет: сборщику придётся включить цель в бандл точки входа.
async function openEditor() {
const { Editor } = await import("./editor.js");
return new Editor();
}
Сборщик существует, чтобы разрешать, преобразовывать и удалять
Сборщик разрешает спецификаторы модулей, преобразует синтаксис, которого не понимают целевые браузеры, встраивает ресурсы и удаляет код, до которого дело не доходит. Vite — распространённый современный выбор: в разработке он отдаёт нативные ES-модули, а для продакшна собирает бандл, поэтому цикл «правка — обновление» остаётся быстрым даже на больших проектах.
Доставляйте меньше, измеряя
Инструменты без измерений растут. Смотрите, что в итоге попадает в собранный вывод, держите самые большие зависимости осознанными и предпочитайте возможности платформы библиотеке, когда платформа уже делает работу.
Практика
Разбейте скрипт на модули
Возьмите один файл с несколькими несвязанными функциями и разбейте его на три модуля с именованными экспортами: один из них импортирует два других.
Готово, когда: ни у одного модуля нет больше одной причины меняться, а файл точки входа читается как короткий список импортов и один вызов.
Лениво загрузите одну тяжёлую функцию
Найдите редко используемую, но крупную функцию — редактор форматированного текста, библиотеку графиков — и загружайте её динамическим import(), запускаемым тем взаимодействием, которому она нужна.
Готово, когда: панель сети показывает, что при первой загрузке чанк не запрашивается, а функция всё равно работает при первом вызове.
Частые ошибки
- Смешивание синтаксиса CommonJS и ES-модулей —
requireв модуле падает, аimportнельзя использовать условно; исправить: выбирайте ES-модули для нового кода и переводите границы осознанно. - Импорт ради побочного эффекта без пояснения — зависимость невидима, и её могут удалить как «неиспользуемую»; исправить: импортируйте используемую привязку или поясните комментарием, зачем нужен голый импорт.
- Работа на верхнем уровне модуля — она выполняется в непредсказуемый момент, и её нельзя ни отменить, ни повторить; исправить: экспортируйте функцию и вызывайте её из точки входа.
- Добавление зависимости ради нескольких строк — бандл растёт у всех, а цепочка поставок ширится; исправить: используйте API платформы или добавьте небольшую функцию к себе.
- Отказ от осмотра продакшн-бандла — фреймворк или файл локали удваивает передачу, и никто не замечает; исправить: проверяйте собранный вывод после добавления зависимости.
Что читать дальше
- Асинхронный JavaScript (02-frontend/06-javascript/03-asynchronous-javascript) — динамические импорты возвращают промисы, и обработка ошибок здесь та же.
- Vite (02-frontend/09-vite) — инструмент сборки, на который этот урок лишь намекает, во всех подробностях.
- Управление пакетами (npm) (02-frontend/04-package-management-npm) — как зависимости, которые импортируют ваши модули, вообще попадают на диск.