Pull to refresh

Comments 33

Все это от того, что npm не создает lock-файл, как это делает bundler/carton/composer, файл который создавался бы после выполнения «nmp install» и фиксировал бы точные версии всех модулей, которые нужны(даже косвенно) для нашего приложения. Вот lock-файл и нужно добавлять в git.
Не верно, в package.json вполне можно фиксировать версии, и npm install --save так и делает, суть в другом, т.к. локальные модули имеют приоритет перед глобальными, наличие кода модулей в репе, гарантирует то, что они будут использоваться.
А lock для _всех_ модулей создать в npm нельзя, так как они имеют иерархическую структуру, и каждая зависимость имеет свой package.json на содержимое которого мы влиять не можем.
Почитайте внимательнее, все очень подробно расписано, в том числе и вопрос фиксации версий.
Нельзя только потому что это не предусмотрено. То есть приняли решение не фиксировать.
А как вы вообще представляете реализацию? Хранить версии для всего дерева? типа:

{
  'brfs': '0.0.5',
  'brfs/through': '2.2.7',
  'browserify': '2.22.0',
  'browserify/through': '2.3.4',
  'foo/bar/buzz': '123'
}


Ну может быть это и реализуется технически, но зачем? Ведь можно просто добавить в реп node_modules и не иметь проблем.
lock был актуален для плоской схемы, в которой нельзя было иметь 2 версии одной и той же библиотеки, а глобальные модули имели приоритет. С npm это проблемы просто нет.
Ну вот открыл тестовый проектик: в composer.json 1 пакет (phpunit/phpunit), в composr.lock — кроме него ещё 7-8 его зависимостей с зафиксированными версиями, при том что в composer.json версия phunit/phpunit указана достаточно свободно.
У композера не иерархическая структура модулей, в нем нельзя иметь две версии одной библиотеки в проекте, иерархическая система модулей в npm дает такую возможность.

Модуль A может зависеть от модуля C v1.7, а модуль B от C v2.0.11 при этом оба модуля А и В бесконфликтно установятся в проект и подтянут свои зависимости локально.

В моем комменте выше акцент был на том, что мы имеем 2 различные версии библиотеки through. Это кстати пример из реального проекта, с актуальными версиями.

Но суть даже не в том, что мы можем лочить или нет версии зависимых библиотек, суть в том, что этого вообще не требуется, т.к. можно безбоязненно коммитить node_modules в реп и быть уверенным в том, что проект получит одни и те же библиотеки на всех инсталляциях.
Отчасти пакетные менеджеры для того и создавались, чтобы не таскать с проектом все его зависимости. Не то чтобы одно или другое однозначно плохо или однозначно хорошо, но лучше иметь альтернативу, чем не иметь. Не согласны?
Отчасти может быть и да, но, на мой взгляд, от очень маленькой части, и вообще, это спорное утверждение. Основная цель пакетного менеджера — поддерживать рабочее окружение проекта. И бандлер справляется с этим хуже чем npm. Отсюда костыли в виде rvm.
В том же Bundler, который и был предтечей всех современных систем контроля зависимостей, Gemfile.lock именно и предназначен для полной фиксации всех зависимостей приложений, на всю глубину дерева зависимостей.

Типичный пример:

Из
source "https://rubygems.org"

group :jekyll do
  gem "jekyll"
  gem "json"
  gem "nokogiri"
  gem "redcarpet"
  gem "textpow", git: 'https://github.com/regru/textpow.git'
  gem "ultraviolet", git: 'https://github.com/regru/ultraviolet.git'
end


Получаем
➜  bem-guide git:(master) cat Gemfile.lock
GIT
  remote: https://github.com/regru/textpow.git
  revision: ff81fdebcc72baeed82d80dffd09a58d6aa9802f
  specs:
    textpow (1.3.1)
      plist (>= 3.0.1)

GIT
  remote: https://github.com/regru/ultraviolet.git
  revision: a507f9e422b4b870d3437751b8d2f054165e97de
  specs:
    ultraviolet (1.0.1)

GEM
  remote: https://rubygems.org/
  specs:
    classifier (1.3.3)
      fast-stemmer (>= 1.0.0)
    colorator (0.1)
    commander (4.1.3)
      highline (~> 1.6.11)
    directory_watcher (1.4.1)
    fast-stemmer (1.0.2)
    highline (1.6.19)
    jekyll (1.0.3)
      classifier (~> 1.3)
      colorator (~> 0.1)
      commander (~> 4.1.3)
      directory_watcher (~> 1.4.1)
      kramdown (~> 1.0.2)
      liquid (~> 2.3)
      maruku (~> 0.5)
      pygments.rb (~> 0.5.0)
      safe_yaml (~> 0.7.0)
    json (1.8.0)
    kramdown (1.0.2)
    liquid (2.5.0)
    maruku (0.6.1)
      syntax (>= 1.0.0)
    mini_portile (0.5.0)
    nokogiri (1.6.0)
      mini_portile (~> 0.5.0)
    plist (3.1.0)
    posix-spawn (0.3.6)
    pygments.rb (0.5.1)
      posix-spawn (~> 0.3.6)
      yajl-ruby (~> 1.1.0)
    redcarpet (2.3.0)
    safe_yaml (0.7.1)
    syntax (1.0.0)
    yajl-ruby (1.1.0)

PLATFORMS
  ruby

DEPENDENCIES
  jekyll
  json
  nokogiri
  redcarpet
  textpow!
  ultraviolet!
Bundler позволяет иметь в одном окружении 2 версии одной и той же библиотеки?
То есть там фактически и нет дерева зависимостей. Список библиотек — плоский, по одной штуке каждой версии. А если требуется поставить 2 зависимости, которые в свою очередь зависят от одной библиотеки, но разных версий, получим конфликты. npm принципиально отличается в этом плане, проблема в том, что люди тащат свои старые привычки в новую среду, не понимая особенностей инструмента, с которым работают. lock был нужен, так как не было гарантии, что приложение будет использовать код из репозитория в новой среде, даже если мы закоммитим зависимости. Теперь он просто не нужен, хотя возможность его сделать есть, как указали в комментах npm shrinkwrap. Но в любом случае, необходимость отпала, нужно просто переступить через свои старые привычки :)
UFO just landed and posted this here
Вот же… Вся статья как раз о том, что в npm нет проблем, которые есть в bundler/composer, таких как конфликты версий, например.

Вы можете представить себе 2 больших сложных проекта на рельсах на одной машине и при этом не в сэндбоксе? Это же просто фантастика и сказки. А на ноде, раз плюнуть.
Хм:

$ npm shrinkwrap    
npm WARN package.json dateformat@1.0.2-1.2.3 No repository field.
npm WARN package.json vows@0.7.0 No repository field.
npm WARN package.json growl@1.7.0 No repository field.
npm WARN package.json ms@0.3.0 No repository field.
npm WARN package.json eyes@0.1.8 No repository field.
wrote npm-shrinkwrap.json

$ cat npm-shrinkwrap.json 
{
  "name": "kantaina",
  "version": "0.1.5",
  "dependencies": {
    "lodash": {
      "version": "1.2.1",
      "from": "lodash@~1.2"
    },
    "when": {
      "version": "2.1.1",
      "from": "when@~2.1"
    },
    "dep-graph": {
      "version": "1.1.0",
      "from": "dep-graph@~1.1",
      "dependencies": {
        "underscore": {
          "version": "1.2.1",
          "from": "underscore@1.2.1"
        }
      }
    },
    "chai": {
      "version": "1.6.1",
      "from": "chai@~1.6"
    },
    "chai-as-promised": {
      "version": "3.3.1",
      "from": "chai-as-promised@~3.3"
    },
    "sinon-chai": {
      "version": "2.4.0",
      "from": "sinon-chai@~2.4"
    },
    "sinon": {
      "version": "1.7.3",
      "from": "sinon@~1.7",
      "dependencies": {
        "buster-format": {
          "version": "0.5.5",
          "from": "buster-format@~0.5",
          "dependencies": {
            "buster-core": {
              "version": "0.6.4",
              "from": "buster-core@>=0.6.2"
            }
          }
        }
      }
    },
...
Ухты, не знал :)

Но тем не менее, в описании shrinkwrap есть:

If you wish to lock down the specific bytes included in a package, for example to have 100% confidence in being able to reproduce a deployment or build, then you ought to check your dependencies into source control, or pursue some other mechanism that can verify contents rather than versions.
npm shrinkwrap уже посоветовали, и это хорошо. Однако не защищает от ситуации, когда мейнтейнер случайно или по злому умыслу публикует новую версию под тем же номером. И у вас что-то ломается.

Мы решили эту проблему так — вместе с проектом идёт небольшой кеширующий веб-сервер, который подменяет собой официальный NPM-репозитарий, и кэширует все версии всех пакетов, которые загружаются во время npm install. Кэш потом чекинится в репозиторий нашего проекта, и мы имеем железобетонную гарантию, что ни один байт зависимых пакетов не поменяется.

Если хотим сделать апгрейд, частично или полностью сносим кэш и делаем npm install заново. Кэш наполняется актуальными версиями пакетов.
А в чем причина отказа от хранения node_modules в репе?
Как минимум, потому что там встречаются бинарные модули.
Мне кажется, это не проблема. npm rebuild при деплое их соберёт. Я понимаю, что процесс деплоя уже может быть давно отлажен, хорошо работает и нет смысла что-то менять. Просто ищу аргументы против node_modules в репе. Пока внятных не нашел :)
Теоретически, build-скрипт может что-то поменять в сорцах, и эти изменения будут попадать в репозиторий при последующих коммитах. Практически, может такого никогда и не встретится. Я не стал рисковать в этом плане.
Хм… Не сталкивался с такими build-скриптами, и имею привычку смотреть git status и git diff перед коммитом. Может быть какой-то смысл в этом есть, но тут уже ответственность полностью на разработчике, и всегда есть возможность откатиться. Ну а от всего не застрахуешься, по-пьяни можно и rm -rf /* на сервере набрать.
UFO just landed and posted this here
Насколько я могу судить, на сáмом-то деле всё именно так и устроено.

Автор пакета не может опубликовать новую версию под старым номером совершенно случайно, потому что никто не наберёт случайно «npm publish --force» вместо простого «npm publish». (Это была бы не такая случайность, когда подбрасываешь монету и выпадает решка — а такая случайность, когда подбрасываешь монетку, а выпадает полуторатонный вилочный штабелеукладчик.) Если автор пакета вдруг команду «npm publish --force» набрал и запустил, то можно быть совершенно уверенным в том, что именно это он и имел в виду, то есть руководствовался некоторыми соображениями — скорее всего, совершенно разумными.

Поэтому вызывает понятное удивление то обстоятельство, что Роджерс сперва пишет «Как сообщество, мы должны распределять между собой часть интеграционного тестирования», а затем теми же руками сомневается в том, что автор пакета знает, что он делает. И в том же aml сомневается.

Впрочем, надо признаться, что в известной мере я сужу здесь по себе. Последний раз я «npm publish --force» запускал буквально сегодня утром — и хорошо знаю о себе, что у меня была на то веская причина: я изменил текст README, а больше ничего. И так как кто угодно (и когда угодно) может зайти на Гитхаб и прочесть там свежайший текст README, то не великá беда, если у кого-то в пакете README останется старым из-за того, что «npm update имяМодуля» не сработает без роста номера версии. С другой стороны, новым потребителям пакета (которые только-только ещё «npm install имяМодуля» набирают) неплохо бы иметь новую версию README в нём. Так что в этаком случае уместно подменить последнюю версию пакета, а не выпускать новую версию, которая только реестр npm засоряет.

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

Пакет A зависит от пакета B версии 1.
Пакет C тоже зависит от пакета B, но версии 2.
Не проверял их на конфликтность но на моем текущем проекте есть по 3 версии commander, methods и mkdirp, и по 2 версии bson, cookie, esprima, formidable, lru-cache, mime, minimatch, mongodb, pause, pkginfo, sigmund, sliced, uglify-js, underscore. Такое происходит, когда разные библиотеки, которые ты зарекваирил тянут за собой в зависимостях разные версии одной и той же библиотеки. Ситуация вполне типичная для большого проекта.
Одна из рекомендаций для разработиков — как можно более явно указывать версию зависимости, несмотря на возможность указать любой диапазон или вообще "*" в значении «любая последняя версия». npm install --save тоже дописывает конкретную версию пакета. В результате, отслеживание и обновление зависимостей ложится на маинтейнеров пакетов. Если вы посмотрите, к примеру, дерево зависимостей cloud9 IDE — обязательно наткнетесь на несколько разных версий. Для отслеживания обновления зависимостей удобно пользоваться gemnasium.com — он присылает письмо, если какая-то из зависимостей обновлена.
Мне хочется подметить, и подмечу, что за полтора года, прошедших со времени публикации Роджерсом этой блогозаписи, я так и не мог совершенно понять, возобладала ли его точка зрения или не особенно возобладала.

А всё потому, что за это время я повидал много-много репозиториев с кодами модулей и библиотек для Node (которые массово «node_modules» добавляют у себя в .gitignore да в .npmignore, но которым это и Роджерс рекомендует). Зато повидал очень мало репозиториев с кодами приложений и сайтов. (Может быть, и ни одного не повидал такого.)

Это у совета Роджерса невеликá аудитория, или это у меня невелик кругозор?

Может ли кто-нибудь из тутошних читателей назвать с полдесятка (а не то и с десяток) наиболее популярных приложений и сайтов с открытым исходным кодом для Node — чтобы можно было посмотреть в их репозиториях списки игнорируемых файлов да сделать вывод о том, насколько Роджерс был услышан, насколько методика его пошла в массы?
Я совсем никакой не разработчик, но мне кажется, это как-то неправильно, помещать в репозиторий с кодом проекта на может пару сотен, ну тысяч строк динамично обновляющуюся папку, которая может содержать десятки тысяч изменяющегося кода. С каждым коммитом помимо истории непосредственно изменений проекта будут кучи diff'ов файлов, до которых по сути нет никакого дела.

Если и хранить где-нибудь node_modules, то в отдельном репозитории, связанном субмодулем с основным. Но всё равно как-то не особо правильно. Было бы здорово, если бы был .lock файл, что выше говорили. Чтобы можно было написать npm lock, оно вернуло json с текущими зависимостями с конкретными версиями (которые сейчас локально), а потом или npm install менял своё поведение при наличии такого файла (при этом ругаясь, если он расходится с зависимостями в package.json), или отдельная команда, которая бы подтягивала конкретные зависимости. Хотя вариант с install лучше, мне кажется. Всякие там heroku бы правильно подтягивали бы зависимости, без каких-либо модификаций.
Папка node_modules не обновляется сама по себе, если по какой-то причине разработчик решил обновить версии модулей, это изменение конечно же пойдет отдельным коммитом с соответствующим commit message. Для аналога lock в npm, как уже указали, есть npm shrinkwrap.
Хранить всё в репозитории это конечно хорошо. Потому что деплоить мы будем ровно то, что тестировали. И при этом не зависим от всяких сторонних тормозных сервисов типа npm.org. Более того, по аналогии с heroku в репозиторий стоит заливать и образ системы на которой всё это будет крутиться. Ой, как-то дофига весить оно будет ;-)

На самом деле всё проще: фиксируем версии или нет — не важно. деплоим на тестовый сервер, прогоняем тесты, если всё хорошо — клонируем тестовый сервер на боевой. не надо на каждом продакшен сервере запускать по деплою с его выкачиванием исходников, выкачиванием модулей, сборкой скриптов/стилей/спрайтов, компиляцией в байт-код… недавно ж даже битторрент выпустил приблуду для п2п синхронизации директорий.

а вот насчёт «офигенной фичи» иметь несколько модулей разных версий — тут я бы поспорил. Что есть в новой версии какого-то модуля пофиксили дыру в безопасности? Или существенно увеличили скорость? Да и вообще, иметь несколько версий одного модуля — лишнее потребление памяти. особенно, если модуль держит внутри себя кэш. Нафиг такое счастье. Как разработчик приложения я хочу чтобы все библиотеки были последней версии. И было бы классно, чтобы разработчики библиотек тоже шли в ногу со временем. А если не поспевают — им помогут те, кому больше всех надо. А поощрять использование устаревших версий — это плохо. В конце концом, если вылезла какая-то совсем большая несовместимость, которую самому не поправить — ну что ж, не будем обновлять этот злополучный модуль (а остальные будем) или найдём ему замену.
Security фиксы — это одно, а изменение api — другое. Библиотеки обычно привязываются к мажорной ветке зависимости, например mkdirp: '0.3.x'. В рассчете на то, что в 'x' как раз будут исправления безопасности, и в то же время можно рассчитывать на стабильный api.
Sign up to leave a comment.

Articles