Почему не надо коммитить папку node_modules
Tags: node, git
Published: 2022-04-04 06:01:52

[English](https://dev.to/grawl/why-you-dont-have-to-commit-nodemodules-folder-33nm) | Русский

Эта заметка — ответы на вопросы, которые могут возникнуть при чтении [статьи Джека Франклина в переводе For Web](https://forweb.dev/ru/blog/check-in-your-node-dependencies).

> # Нет нужды в npm install
>
> Если вы храните `node_modules` в git, вам не нужно отдельно устанавливать зависимости, чтобы запустить проект.

1. git плохо работает с большим количеством файлов в репозитории. Поиск фразой “git performance many files” показывает много полезной информации об этом. Например, вот: [Just as git does not scale well with large files, it can also become painful to work with when you have a large _number_ of files](https://git-annex.branchable.com/tips/Repositories_with_large_number_of_files/)

2. [Некоторые пакеты зависят от платформы](https://stackoverflow.com/questions/35238667/is-npm-dependent-on-the-os-of-a-computer). Например, инструменты для разработки, такие как [`dart-sass`](https://github.com/sass/dart-sass/releases).

3. Если вы закоммитили `node_modules`, это значит, что у любого разработчика теперь есть лёгкий способ что-либо исправить в любой из зависимостей (это называется “monkey patching”), и это определённо точно приведёт к тому, что после обновления этой “исправленной” зависимости прошлое исправление будет перетёрто, и с этой проблемой придётся как-то разбираться. Вы вообще не будете уверены в том, что зависимость определённой версии соответствует тому виду, в котором она была изначально получена.

> Это полезно не только для локальной разработки, но и для ботов на CI-платформах: они могут полностью пропустить шаг установки зависимостей.

CI обычно настраивают так, чтобы зависимости кешировались, и не устанавливались каждый раз с нуля. Гуглить фразой “ci node_modules cache”.

> # Гарантированно воспроизводимые сборки
> 
> Хранение `node_modules` в git гарантирует, что два разработчика запускают один и тот же код с одним и тем же набором зависимостей.

Эту проблему решает “лок-файл” — специальный файл, который нужно коммитить, в который пакетный менеджер (NPM/PNPM/Yarn) записывает о каждой скачанной зависимости все необходимые данные для гарантии воспроизводимой сборки.

Если заглянуть в `yarn.lock`, можно увидеть следующее:

```yml
"@apideck/better-ajv-errors@^0.2.4":
  version "0.2.5"
  resolved "https://registry.yarnpkg.com/@apideck/better-ajv-errors/-/better-ajv-errors-0.2.5.tgz#b9c0092b7f7f23c356a0a31600334f7b8958458b"
  integrity sha512-Pm1fAqCT8OEfBVLddU3fWZ/URWpGGhkvlsBIgn9Y2jJlcNumo0gNzPsQswDJTiA8HcKpCjOhWQOgkA9kXR4Ghg==
  dependencies:
    json-schema "^0.3.0"
    jsonpointer "^4.1.0"
    leven "^3.1.0"
```

Yarn заботливо записал, что он скачал пакет `@apideck/better-ajv-errors`:

- версии `0.2.5`
- по адресу `resolved` (прямая ссылка на `.tgz`)
- хеш-сумма файла была `sha512-Pm1fAqCT8OE...`
- и у этого пакета было 3 зависимости

И так для каждой зависимости, размещённой в папке `node_modules`. И в следующий раз, когда в папке проекта будет запущена команда `yarn install`, зависимости будут скачаны не тех версий, что указаны в `package.json`, а те, что записаны в `yarn.lock`. Следовательно, у всей команды и на CI независимо от платформы (Linux/macOS/Windows) будет один и тот же набор файлов, один и тот же код, с одними и теми же хеш-суммами.

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

Эту ошибку часто допускают, когда при развёртывании проекта у разработчика запускается `npm install`, который устанавливает пакеты по информации из `package.json`, а не `package-lock.json`. Чтобы установить пакеты из лок-файла, нужно запускать `npm ci`.

> # Лучшая осведомлённость о поставляемом коде
> 
> Я был удивлён тем, насколько я стал осведомлённее касательно зависимостей, когда стал видеть в диффе git весь код, попадающий в проект при их подключении. Это побудило нас поработать над инструментами, помогающими оценить влияние зависимостей на размер бандла и оптимизировать занимаемое на диске место.

При выборе зависимостей можно пользоваться специальными инструментами, а не просто читать километры кода.

- [Bundlephobia](https://bundlephobia.com)
   
    Покажет, сколько весит зависимость, сколько будет с GZIP, как долго она будет скачиваться по медленному 3G и по среднему 4G-интернету, покажет в процентном соотношении состав под-зависимостей, что зависимость экспортирует (если она написана на ES Modules), а также какие у неё есть альтернативы или соседние пакеты. Вот [пример](https://bundlephobia.com/package/@stylable/webpack-plugin@4.10.2).

- [bundlejs.com](https://bundlejs.com)
   
    Покажет, сколько точно в килобайтах будет добавлено кода при импорте вроде
    
    ```js
    import { map } from "nanostores"
    ```
    
    [Посмотрите это на примере `nanostores`](https://bundlejs.com/?q=nanostores&treeshake=[{+map+}]).
   
- [npm.anvaka.com](https://npm.anvaka.com)
   
    Покажет график всех зависимостей в виде 2D или 3D-графика. [Посмотрите на примере Vue 3](https://npm.anvaka.com/#/view/2d/vue)

> # Более вдумчивое добавление зависимостей, потому что они больше не скрыты
> 
> Я уже упоминал, что шум в диффах воспринимается людьми как недостаток хранения зависимостей в git, и я признаю, что это действительно может быть проблемой. Однако для меня этот шум оказался полезным знаком. Раньше я часто подключал новые зависимости, просто потому что не хотел сам писать несколько строчек кода. Теперь же я гораздо внимательнее отношусь к добавлению новых зависимостей, потому что я могу увидеть добавленный ими код и решить, стоит ли он того.

Вы можете прочитать код и до того, как добавить зависимость в проект. Например, зайдя в репозиторий на GitHub. Крайне советую хотя бы мельком смотреть зависимости, адекватность кода, количество открытых issue и дату последнего коммита.

> Примечание: это не означает, что у нас нет зависимостей! Бывают ситуации, когда подключить зависимость выгодно, но видимость кода в системе контроля версий сделала меня более осознанным — цена добавления зависимостей больше не скрыта.

Она никогда и не была скрыта.

> # C большими диффами можно справиться
> 
> Нельзя игнорировать тот факт, что добавление или обновление зависимостей может вызвать много шума в диффе. Одна из наших зависимостей — TypeScript, и каждое его обновление приносит огромный дифф в git, который, честно говоря, не стоит просмотра (за пределами CHANGELOG). Мы пришли к правилу, которое помогает в таких ситуациях: изменения в `node_modules` должны быть отделены от любых изменений в основной кодовой базе. Так что если я обновлю `node_modules/typescript` до свежей версии, наш инструментарий предупредит меня при обнаружении изменений за пределами `node_modules`.

Вы пришли к костылю.

> Это правило помогает нам в большинстве случаев, потому что любая задача, требующая добавления или обновления зависимостей, может быть разделена на две части:
> 
> 1. Обновление или добавление зависимости
> 2. Использование зависимости в коде
> 
> Иногда это не работает: например, обновление TypeScript может потребовать от нас исправления в коде ошибок, которые теперь обнаруживает новая версия TypeScript. В таких случаях мы отменяем правило.

И вот последствия хождения на костылях.

> # Защита от очередного left_pad
> 
> Печально известный [инцидент с left_pad](https://qz.com/646467/how-one-programmer-broke-the-internet-by-deleting-a-tiny-piece-of-code/) (популярный npm-пакет был внезапно удалён, ломая сборки проектов по всему миру) не затронул бы команду, которая хранит зависимости в git. В долгосрочной перспективе команде всё ещё пришлось бы разбираться с вопросом «что теперь делать с более не поддерживаемой зависимостью», но в краткосрочной перспективе их сборки бы не сломались, а релизы бы не были заблокированы.

Я помню тот день, когда `left_pad` был удалён с NPM. Я тогда работал в digital-агентстве на потоке сайтов, и, конечно же, во всех проектах, за которые я был ответственен, `left_pad` был под-зависимостью. Мы решили эту проблему тогда примерно за полчаса, когда CI показал 404 при попытке скачать пакет. Я уже не помню, что именно мы предприняли, но такая задача не должна быть вызовом и поводом городить костыли.

В конце концов, для защиты от именно таких проблем, вы можете поднять у себя прокси-реестр, например, с помощью [Verdaccio](https://verdaccio.org/). Он будет хранить у себя все копии всех скачанных пакетов.