Pull to refresh

Comments 19

Есть мнение, что зависимости которые требуются для сборки пакета необходимо ставить в секцию dependencies. Тогда при npm ci --production мы будем ставить зависимости, необходимые для того, чтобы проект заработал. Например на CI это просто сэкономит время сборки вашего приложения, особенно если у вас в dev-dependencies находятся какие-нибудь жирные библиотеки, качающие бинарники.

Такой подход неизбежно приведет к более сложной категоризации зависимостей и нарушит общепринятую семантику. Соответственно другим разработчикам будет сложно понять что происходит в проекте и по какому принципу добавляются зависимости. В итоге dev-зависимости перемешаются с зависимостями исходного кода и управлять ими будет гораздо сложнее. Кроме того, довольно сложно отделить зависимости, которые нужны только для сборки и зависимости, которые нужны для процесса разработки. Скорее всего выиграть на этом много не получится и игра просто не будет стоить свеч. Я бы не рекомендовал такой подход.

Оптимизировать сборку проекта в контексте CI/CD имеет смысл при помощи других инструментов, например, кэширования. Я постараюсь раскрыть этот вопрос в следующих постах.

Посыл в том, что ваш код не будет работать без тех самых сборщиков, которые установлены в devDependencies. Когда стоит придерживаться той семантики, которую вы описали? Когда вы разрабатываете библиотеку или backend под nodejs.
В той же документации на npmjs.org назначение devDependencies поля однозначно:


Packages that are only needed for local development and testing.

webpack => чтобы приложение работало
webpack-dev-server => для локальной разработки
Не холивара ради, а ради того, что есть и другие способы организации зависимостей.
Есть статья, которая возможно даст пищу для размышлений :) https://incubator.flaks.dev/devdependencies-mistake

Одна из главных мыслей, которую я хотел донести в статье, заключается в том, что данная система была разработана для Node.js проектов и идеально на front-end проекты она не может лечь. Не существует единственно верного канонического решения. Не стоит пытаться читать документацию к npm и интерпретировать ее буквально, т. к. впринципе этот инструмент не создавался для фронтенда. Здесь скорее вопрос в том, как адаптировать инструмент для эффективной работы конкретно в твоем проекте. Схема, предложенная мной, кажется наиболее понятной и удобной. А мой опыт говорит о том, что в разработке простота, читабельность и понятность кода или подходов намного важнее, чем оптимизация, которая в данном случае, кажется довольно сомнительной.
Посыл в том, что ваш код не будет работать без тех самых сборщиков, которые установлены в devDependencies.

А он и не должен, т. к. пакет фронтенд приложения не является полноценным npm-пакетом. npm используется исключительно для того, чтобы скачать зависимости и сложить их в node_modules.


Даже, если мы говорим о программе на Node.js, которая должна собираться перед своей работой (например через tsc), то распределение зависимостей может быть точно таким же. Разница лишь в том, что перед публикацией пакета в registry он будет автоматически скомпилирован (скрипт prepublishOnly) и уже готовый для выполнения код окажется в архиве пакета. Таким образом, при установке в проект всё будет работать, несмотря на отсутствие dev-зависимостей, которые нужны для сборки.

А он и не должен, т. к. пакет фронтенд приложения не является полноценным npm-пакетом. npm используется исключительно для того, чтобы скачать зависимости и сложить их в node_modules.

Зачем вы пишите код, который не работает без devDependencies ?) Мой комментарий касается только процесса написания конечного web клиента (не npm пакета) и уменьшения кол-ва скачиваемых зависимостей, необходимых для того чтобы ваше приложение собралось и запустилось в браузере

Зачем вы пишите код, который не работает без devDependencies ?)

Не совсем уловил суть вопроса.


Мой комментарий касается только процесса написания конечного web клиента (не npm пакета) и уменьшения кол-ва скачиваемых зависимостей, необходимых для того чтобы ваше приложение собралось и запустилось в браузере

Это я понял и все мои ответы в данной ветке как раз соответствуют данному контексту.

Если только для фронтенда, то действительно, разделение на прод-зависимости и дев-зависимости не несет значимого смысла, т.к. в ci после сборки не нужны node_modules, и в финальную папку можно копировать только папку build.
В изоморфной же схеме либо при сборке для ноды, когда зависимости не включаются в билд, а подтягиваются в рантайме (например, с помощью опции externals в Webpack), разделение на разного рода зависимости становится актуальным. После сборки в финальном образе с файлами ci в идеале должен выполнить npm i --production, чтобы очистить node_modules от лишнего и уменьшить размер. Эту оговорочку надо бы включить в статью

Да, это хорошее замечание. SSR по-сути представляет собой гибридный проект. Возможно стоит будет посвятить этой теме отдельный пост.

Есть мнение, что зависимости которые требуются для сборки пакета необходимо ставить в секцию dependencies

Потом вы решите использовать Docker. Поиграетесь и придете к multistage сборке. И для оптимизации придется опять переносить зависимости сборки в devDependencies. Хотя не спорю, для отдельных сценариев этот способ может иметь право на жизнь.

Существует ли возможность забандлить express js приложение в один толстый js файл со всеми зависимостями? И почему так никто не делает?
В среде Node.js (и других похожих технологиях) впринципе не принято бандлить зависимости. Идея бандлинга пришла из front-end проектов, потому что передавать огромное количество маленьких файлов по старому протоколу HTTP было неэффективно (новые версии протокола, начиная с версии 2 лишены этого недостатка). Кроме того, если ты забандлишь какую-то зависимость внутрь своего пакета, то в рамках родительского приложения она уже не сможет обновляться и переиспользоваться. Соответственно, чтобы обновить зависимости Express (как прямые, так и транзитивные) нужно будет постоянно выпускать новые версии Express, хотя в самом пакете ничего может не меняться при этом.

К примеру, Express, как и многие другие библиотеки, использует пакет «debug». Если каждая библиотека будет бандлить «debug» внутри себя, то в конечном приложении код «debug» разных версий будет встречаться десятки раз, что будет раздувать количество кода. А так npm при установке зависимостей автоматически выполняет дедубликацию и пакет «debug» устанавливается только один раз (об этом упомянуто в моём посте).

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

В будущих постах я подробнее расскажу о проблеме дублирующихся пакетов и почему это важно.
Меня интересует бандлинг своего приложения, а не общедоступного пакета. Проблема безопасности решается пересборкой с обновленным пакетом, ведь пакет все равно надо будет вручную обновить. Дедубликация выполняется бандлером (вебпаком например). Деплоить один js скрипт намного проще и безопаснее чем выполнять npm install на прод сервере.

Прошу прощения. Видимо я не совсем уловил суть вопроса. Однако, часть моего ответа все еще верна — бандлинг изобретение для фронтенда, чтобы повысить производительность при работе со старыми версиями протокола HTTP, т. к. передать один файл по сети проще, чем, скажем, тысячу.


При использовании Node.js такой проблемы впринципе не стоит, по этой причине ее и нет смысла решать, это только усложнило бы сборку приложения. Кроме того, это лишило бы приложение ряда интересных возможностей, например, загрузки модулей динамически: представьте, что у вас в приложении есть директория в которую вы можете класть JS-файлы, а приложение само их находит, загружает и выполняет по мере необходимости. Забандлить такую реализацию было бы очень проблематично.


Ну и делать npm install на продакшене явно не самая лучшая идея. Если вкратце, то лучше собирать артефакт приложения в среде CI/CD (на сборочной машине), а потом уже разворачивать готовый к выполнению код в продуктовой среде передавая нужные данные через окружение. Я постараюсь подробнее об этом написать в будущих постах.

Почему не делают? Делают. Погуглите “use webpack bundling for server side code”. Только вот смысла в этом не очень много: вебпак довольно-таки тормозной, а многие современные проекты пишут на TypeScript, который очень хорошо создает директорию типа dist из src и кладет в нее те же файлы, что были в src, но js, d.ts и map. И работает он гораздо быстрее костыльного ts-loader’а или awesome-ts-loader-а (причем все быстрее и быстрее, особенно с -b), плюс все фичи из tsconfig.json поддерживает. Зачем там еще webpack какой-то...

К слову, именно таким образом я лично и собираю проекты на Node.js, при помощи чистого tsc и ни какой магии.

Да… Магия возникает, когда хочется задействовать разные нетрадиционные вещи типа ttypescript или typescript-is. Или когда хочется билдить при помощи tsc -b —watch не только сервер, но и клиент (с реактом и tsx!), я потом на результат (js+map) натравливать уже webpack —watch (ибо так работает гораздо быстрее, чем ts-loader, когда монорепа с многими подпроектами). Или когда хочется jest гонять не по ts-файлам, а по все тем же сгенерированным js (ибо так тесты в 2-3 раза быстрее запускаются, чем чере ts-jest, но приходится доставать бубен для поддержки снапшотов в правильном месте). Или когда хочется нормальный watch или eslint сделать в монорепе (ужас-ужас). А уж про es6-модули для TS и node и говорить не приходится. Все это в 2020 году находится в страшно сыром и разрозненном состоянии, к сожалению, но процесс медленно идет.

лучше собирать артефакт приложения в среде CI/CD (на сборочной машине), а потом уже разворачивать готовый к выполнению код в продуктовой среде

Такой подход превращает «отладку на продакшене» в кромешный ад. Например, если у меня есть в продакшене невидимая для юзеров веб-морда, либо какой-то фоновый сервис, в случае аварийной ситуации (или для эксперимента) я смогу всегда там файлик руками подправить, перебилдить-перезапустить и посмотреть, что будет. А если мне артефакты собирает CI, то такой фокус уже ее пройдет, да и депллй начинает длиться долговато (возникает зависимость от CI, аварийные фиксы не прочеррипикать также быстро).


В общем, много удобств в том, чтобы собирать там, где ранаешь. Например, в WhatsApp весь сервер так собирался (там erlang), и это суперудобно в аварийных ситуациях или для экспериментов.

Sign up to leave a comment.