EverProduct
Фронтенд

Этап 04 · Приложение

Инструментальная цепочка: модули, пакеты и сборка

Вчера проект был файлом и тегом 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.

Когда сборка ломается, сначала локализуй, потом чинь: установка, преобразование, бандлинг или отдача. Назвать стадию — бо́льшая часть работы.

Проверь себя

Закрой статью и ответь своими словами:

  1. Какие три проблемы решили модули по сравнению со страницей, полной тегов script?
  2. Чем dependencies отличаются от devDependencies и зачем коммитить файл блокировки?
  3. Назови четыре вопроса, которые стоит задать до добавления зависимости.
  4. Каковы пять работ сборки?
  5. Зачем в именах файлов хеш и какую проблему это решает?
  6. Что дают карты исходников?
  7. К каким четырём стадиям может относиться сбой инструментальной цепочки?

Коротко

  • Инструментальная цепочка решает четыре задачи: разбить код, переиспользовать чужой, перевести современный исходник для браузеров и дать быструю петлю во время работы.
  • Модули дают файлам приватность и явные зависимости; предпочитай именованные экспорты, одну ответственность на файл и подозревай цикл, когда что-то undefined на старте.
  • package.json объявляет зависимости и скрипты, semver объясняет расхождение версий, а файл блокировки делает установку воспроизводимой.
  • Зависимость — это код, который ты отдаёшь, которому доверяешь и который поддерживаешь: сначала проверь платформу, потом взвесь размер, поддержку и радиус поражения.
  • Сборка преобразует, бандлит, оптимизирует, ставит отпечаток и отдаёт; имена инструментов меняются каждые несколько лет, эти пять работ — нет.
  • Карты исходников позволяют инструментам разработчика показывать твой исходник вместо минифицированного вывода.
  • Node — среда, в которой работают твои инструменты, и половина загадочных ошибок сборки — это голос Node.
  • Знай дальний план конвейера наизусть, а детали конфигов смотри по месту: потерянные часы принадлежат тем, кто не может сказать, какая стадия сломалась.