Инструментальная цепочка: модули, пакеты и сборка
Вчера проект был файлом и тегом script, сегодня это манифест пакетов, четыреста папок зависимостей и конфиг, который никто не читает. Каждая часть решает свою задачу — выучи задачи, и произвол исчезнет.
Всё до сих пор работало с одним HTML-файлом и тегом script. Потом ты открываешь настоящий проект и находишь package.json, папку node_modules с четырьмя сотнями каталогов внутри, файл блокировки, конфиг сборки и команду, которую надо выполнить, чтобы хоть что-то появилось.
Обычная реакция — воспринимать это как полосу препятствий: копируй команды, не спрашивай. Работает, пока внутри что-нибудь не сломается, а ломается примерно ежемесячно, и тогда модели нет вообще никакой.
Задач здесь всего четыре, и каждый инструмент существует ради одной из них: разбить код на файлы, переиспользовать чужой код, превратить современный исходник в то, что умеет браузер, и делать всё это быстро, пока ты работаешь.
Любая часть инструментальной цепочки решает задачу, которая была у браузера. Выучи задачи — и инструменты перестанут быть произволом.
Модули: файлы, объявляющие, что им нужно
Изначально все скрипты делили одну глобальную область. Два файла с переменной config затирали друг друга. Порядок загрузки задавался руками и был хрупким, а ни один файл не сообщал, от чего он зависит, — это выяснялось удалением тега script и наблюдением за обрушением.
Модули чинят всё это двумя словами. export говорит, что файл предлагает; import — что ему нужно. Всё неэкспортированное приватно для файла, и это первая настоящая инкапсуляция за историю языка.
В браузерах они теперь родные — тег script с type="module", — и приносят с собой полезное поведение: модули по умолчанию отложены, то есть выполняются после разбора документа, и всегда работают в строгом режиме.
Две привычки стоит завести сразу. Предпочитай именованные экспорты экспорту по умолчанию: они находятся поиском, переименование безопаснее, а редактор нормально их подсказывает. И один модуль — одна ответственность: файл с именем utils — это место, куда несвязанные вещи уходят, чтобы стать ненаходимыми.
Следи за циклическими импортами, когда два файла импортируют друг друга. Обычно оно даже запускается и обычно даёт ошеломляющий undefined на старте. Если что-то undefined во время импорта — проверяй цикл раньше всего остального.
npm: чужой код
npm — это две вещи: команда, которая ставит пакеты, и реестр, откуда она их ставит, — крупнейшее собрание переиспользуемого кода в природе.
package.json — манифест проекта. Важнее всего два поля. dependencies — то, что нужно приложению во время работы; devDependencies — то, что нужно только тебе для сборки и тестов. А scripts — место, где живут настоящие команды проекта: dev, build, test. Поэтому это первый файл, который стоит прочитать в незнакомом репозитории: он рассказывает, как это запускается.
Версии записаны по semver: мажор, минор, патч. Крышечка разрешает минорные и патчевые обновления, тильда — только патчевые. Поэтому два человека могут поставить «одни и те же» зависимости и получить разный код.
Файл блокировки существует ровно для того, чтобы этого не было. Он фиксирует точные версии всего, включая зависимости зависимостей. Коммить его всегда — это разница между «у меня работает» как загадкой и как невозможностью.
node_modules — установленный результат. Он огромен, его никогда не коммитят, и он восстанавливается из манифеста и файла блокировки одной командой.
Цена зависимости
Установка пакета занимает три секунды, а последствия тянутся дольше, чем кажется.
Каждая зависимость — это код, который ты отдаёшь пользователям, код, которому ты теперь доверяешь, и код, который кто-то может перестать поддерживать. Пакеты тянут свои зависимости, поэтому двенадцать прямых превращаются в четыреста фактических, и каждая из них может выполнить код при установке и работает внутри твоего приложения.
Ничто из этого не означает избегать пакетов: писать собственную библиотеку дат — не добродетель. Это означает задать четыре вопроса перед добавлением: не умеет ли это платформа сама? (по-настоящему большая доля мелких утилитарных пакетов старше того браузерного API, который теперь существует). Сколько это весит в том, что уедет пользователю? Поддерживается ли это и сколькими людьми? Что сломается, если оно исчезнет или окажется вредоносным?
Для маленького помощника — двадцать строк, которые ты можешь написать и понять, — написать самому за пять лет часто дешевле, чем владеть зависимостью. Для всего криптографического, всего связанного с датами и часовыми поясами и всего критичного к доступности верно ровно обратное: бери проверенное.
Зачем вообще нужна сборка
Браузеры исполняют HTML, CSS и JavaScript. Современная разработка производит то, чем не является ни одно из трёх: TypeScript, JSX, Sass, импорт CSS из JavaScript-файла. Кто-то должен перевести. Это и есть сборка.
Она делает пять работ, и знать их достаточно:
Преобразование. TypeScript в JavaScript, JSX в вызовы функций, современный синтаксис в тот, который поддерживают целевые браузеры.
Сборка в бандл. Объединение модулей в меньшее число файлов. Это было чудовищно важно, когда каждый импорт означал отдельный запрос; современные протоколы снизили штраф, но для крупных приложений бандлинг по-прежнему выигрывает, а по-настоящему важной частью стало разделение кода — отдавать только то, что нужно этой странице.
Оптимизация. Минификация (убрать пробелы, укоротить имена), tree-shaking (выбросить экспортированный код, который никто не импортировал), сжатие ресурсов.
Отпечаток. Хеш содержимого в имени файла, чтобы файлы кэшировались навсегда, а новый деплой означал новое имя. Это ответ на «почему пользователи видят старую версию».
Отдача во время работы. Дев-сервер — та половина, с которой ты взаимодействуешь ежечасно: он пересобирает по сохранению и заменяет модули на лету, так что страница обновляется без перезагрузки и без потери состояния. Это не роскошь: такая петля обратной связи во многом и делает фронтенд быстрым занятием.
Инструменты меняют имена каждые несколько лет — webpack, Rollup, esbuild, Vite, Parcel, — а пять работ не меняются. Учи работы; конкретный инструмент считай деталью, которую придётся переучить дважды за десятилетие.
Одну вещь стоит настроить рано и потом радоваться: карты исходников, которые сопоставляют минифицированный преобразованный код твоим оригинальным файлам, чтобы инструменты разработчика показывали написанное тобой, а не одну строку в сорок тысяч символов.
Коротко про Node
Всё это работает на Node — JavaScript вне браузера. Тот же язык в другой среде: у Node есть файлы и процессы и нет DOM; у браузера есть DOM и нет файловой системы.
Учить Node, чтобы быть фронтендером, не обязательно, но знать, что на нём работают твои инструменты, необходимо: половина непонятных ошибок сборки — это Node, который тебе что-то говорит: несовпадение версий, отсутствующий модуль, несуществующий путь.
Понимать конвейер, а не конфиг
Здесь есть отдельная ловушка, стоящая людям месяцев. Столкнувшись с инструментальной цепочкой, большинство либо заучивает команды без модели, либо пытается освоить все опции конфигов до того, как напишет приложение. Проваливаются оба, в противоположные стороны.
Правило масштаба из сферы «Как учиться» ложится сюда точно: понимать — значит уметь менять масштаб на одном и том же объекте. На дальнем плане конвейер — это четыре фразы: мой исходник становится исполняемым, пакеты приезжают из реестра, сборка преобразует и оптимизирует, дев-сервер пересобирает по сохранению. Вот этот уровень стоит знать наизусть, потому что он позволяет локализовать сбой: это проблема установки, преобразования, сборки или отдачи? На ближнем плане отдельные опции конфига — справочный материал, который смотрят по необходимости, и заучивать его нет ни малейшего смысла.
Почти каждый час, потерянный на инструментах, потерян тем, кто не построил дальний план и потому не может сказать, какая из четырёх стадий сломалась.
На практике
Сначала читай раздел scripts в любом проекте, который открываешь. Быстрейшая доступная ориентировка.
Один раз собери проект с пустой папки руками. npm init, установка сборщика, написание конфига, добавление скриптов. Занимает вечер и навсегда убирает ощущение, что настройка проекта — это магия, которую делает кто-то другой.
Перед установкой пакета проверь платформу. Форматирование даты, копирование объекта, генерация идентификатора, debounce функции — часть этого уже не требует зависимости.
Посмотри, что ты реально отдаёшь. Большинство сборщиков умеют показать разбивку по размеру; первый взгляд обычно удивляет и часто находит одну огромную библиотеку, использованную ради одной функции.
Коммить файл блокировки. Не коммить node_modules.
Когда сборка ломается, сначала локализуй, потом чинь: установка, преобразование, бандлинг или отдача. Назвать стадию — бо́льшая часть работы.
Проверь себя
Закрой статью и ответь своими словами:
- Какие три проблемы решили модули по сравнению со страницей, полной тегов script?
- Чем
dependenciesотличаются отdevDependenciesи зачем коммитить файл блокировки? - Назови четыре вопроса, которые стоит задать до добавления зависимости.
- Каковы пять работ сборки?
- Зачем в именах файлов хеш и какую проблему это решает?
- Что дают карты исходников?
- К каким четырём стадиям может относиться сбой инструментальной цепочки?
Коротко
- Инструментальная цепочка решает четыре задачи: разбить код, переиспользовать чужой, перевести современный исходник для браузеров и дать быструю петлю во время работы.
- Модули дают файлам приватность и явные зависимости; предпочитай именованные экспорты, одну ответственность на файл и подозревай цикл, когда что-то
undefinedна старте. package.jsonобъявляет зависимости и скрипты, semver объясняет расхождение версий, а файл блокировки делает установку воспроизводимой.- Зависимость — это код, который ты отдаёшь, которому доверяешь и который поддерживаешь: сначала проверь платформу, потом взвесь размер, поддержку и радиус поражения.
- Сборка преобразует, бандлит, оптимизирует, ставит отпечаток и отдаёт; имена инструментов меняются каждые несколько лет, эти пять работ — нет.
- Карты исходников позволяют инструментам разработчика показывать твой исходник вместо минифицированного вывода.
- Node — среда, в которой работают твои инструменты, и половина загадочных ошибок сборки — это голос Node.
- Знай дальний план конвейера наизусть, а детали конфигов смотри по месту: потерянные часы принадлежат тем, кто не может сказать, какая стадия сломалась.