DEV Community

Max Core
Max Core

Posted on • Updated on

Базовый ультимативный гайд по Node.js (на примере SvelteKit)

dependencies? devDependencies? peerDependencies?
npm install? npm i? npm ci?
--save? --save-dev? --save-prod? -g?
package.json? package-lock.json? .npmrc?
"module": "^1.0.0"?
"module": "~1.0.0"?
"module": "1.0.0"?
Что из этого можно править руками?
Что из этого в Гит?
Какой командой в итоге билдить?

Актуально для: node -v — v20.5.0 и npm -v — 10.2.3

Что предлагает Svelte на старте?

(После npm create svelte@latest my-app)

  1. В package-lock.json только devDependencies (без prod-dependencies):

И все эти зависимости «из коробки» НЕ строгие, т.е. с ^:

```
"devDependencies": {
    "@sveltejs/adapter-auto": "^2.0.0",
    "@sveltejs/kit": "^1.20.4",
    "svelte": "^4.0.5",
    "vite": "^4.4.2"
}, 
```
Enter fullscreen mode Exit fullscreen mode
  1. .npmrc (NPM Run Command) с единственной строкой engine-strict=true.

P.S. package-lock.json нет вообще (и не надо).

Что рекомендуют разработчики Svelte?

Определиться с адаптером, вместо adapter-auto.

It's recommended to install the appropriate adapter to your devDependencies once you've settled on a target environment, since this will add the adapter to your lockfile and slightly improve install times on CI.
https://kit.svelte.dev/docs/adapter-auto

Мы люди простые, — возьмём универсальный adapter-node.
Итак,

Как установить новый модуль?

https://docs.npmjs.com/cli/v10/commands/npm-install

По умолчанию, npm install @sveltejs/adapter-node — запишет в prod-dependencies.
Потом, можно перекинуть в devDependencies путём:
npm install @sveltejs/adapter-auto --save-dev.
Но, снова вернуть в «prod» той же командой:
npm install @sveltejs/adapter-auto уже не получится.
Чтобы форсануть обратно в «prod»:
npm install @sveltejs/adapter-auto --save-prod

  • npm install @sveltejs/adapter-auto --save — такого просто нет (как минимум в npm v10)
  • Но нода не ругается на несуществующие параметры.
  • Более того, ей без разницы — что так: npm install module-name --param, что так: npm install --param module-name.
  • А вообще можно и просто npm i module-name.
  • Можно и npm i module_name -g, если модуль нужен не для проекта, а как консольная утилита для чего-нибудь.

Но это не то, что нам нужно.
Мы знаем, чем оборачивается «НЕ строгая версия».

Как установить строгую версию?

Есть 4 способа:

  1. npm config set save-exact true

    Сомнительно, т.к. делает запись где-то глобально, и только на локальной машине.

    P.S.
    Создаётся на самом деле 2-е переменные в npm config list:
    save-exact = true
    save-prefix = ""

    Если вернуть обратно (npm config set save-exact true), будет:
    save-exact = false
    save-prefix = "^"

  2. npm install @sveltejs/adapter-auto --save-exact

    Делает свою работу, но нам нужна защита от дурака, на всю команду.

  3. Добавить в .npmrc >> save-exact=true.

Т.е., теперь, когда мы делаем npm i module-name он автоматически будет устанавливать строгую версию, без ^.
(Тут, никаких save-prefix = "" не надо, это была внутренняя штука для глобального конфига.)

  1. Можно руками постирать все ^, но — тогда у нас окажутся устаревшие пакеты :D.

То, что было вначале пришло в devDependencies — не совсем свежее.
Но, пока мы ещё ничего не установили, у нас нет ни node_modules/, ни package-lock.json — нам ничего не мешает действительно всё прописать руками.

Итого:

  1. Идём по П.3. — добавляем в .npmrc >> save-exact=true.
  2. Для всего, что видим в devDependencies — заного поочерёдно делаем npm i module-name. (Они перелетят из devDependencies в prod-dependencies. Пока, тут это нам и нужно.)
  3. Ну и наконец-то устанавливаем наш adapter-auto:

npm i @sveltejs/adapter-auto.

P.S. Устанавливая один модуль — установятся все dependencies, как если бы мы делали всё это одним npm i.

А @sveltejs/adapter-auto можно удалить, чтоб не мешался.

Как удалить модуль?

https://docs.npmjs.com/cli/v10/commands/npm-uninstall

  1. npm uninstall @sveltejs/adapter-auto — удалит в каких бы *Dependencies они не находились.

    Раньше нужно было дописывать --save-dev и т.д., иначе удалится из node_modules/, но не из package.json и т.д.
    Сейчас этого нет. Только если прописать в .npmrc >> save=false, тогда нужно будет --save.
    Но не вижу во всём этом смысла для общих случаев.

  2. Можно удалить строку "@sveltejs/adapter-auto": "^2.0.0", руками.

    При этом удалить node_modules/ и package-lock.json.
    И сделать npm i всего проекта заново.
    Но, так делать не стоит, когда всё уже в бою.
    Это может навредить и серверной сборке, и коллегам, но об этом позже.

Как не захламить prod-dependencies?

Понятно, что в процессе разработки мы пробуем много разных модулей, которые чаще всего не пойдут на прод.
И, не хотелось бы, чтобы всё ложилось в prod-dependencies.

Тут выясняется, что возможен ещё какой-то peerDependencies,
где вообще — у каждого разработчика появляется свой личный блок с модулями:
npm i node_module --save-peer (--save-peer не задокументирован, но работает. https://stackoverflow.com/a/74549787/4117781)

Пропустил я эту команду, и получил что-то новое — звёздочку! в devDependencies:

"devDependencies": {
    "@sveltejs/adapter-auto": "*",
},
"peerDependencies": {
    "@sveltejs/adapter-auto": "2.1.1"
}
Enter fullscreen mode Exit fullscreen mode

Испугался, и решил не использовать.
Давайте лучше дружно хламить devDependencies, но уж лучше без этого.
Там ещё как-то должен был прописаться мой личный скоуп, на основе имени компа, но и этого автоматически не произошло.
Валим из этой идеи короче :D
Как минимум в этой статье.

Не будем тянуть.
Чтобы все модули по-умолчанию сваливались в devDependencies:
.npmrc >> save-dev=true

Теперь, чтобы вогнать что-то в prod-dependencies нужно будет npm i module-name --save-prod.
Такое делается не каждый день. Но — это ответственно.
Потому, вероятно — это должен делать кто-то один из команды.

Зачем нужен package-lock.json?

У модулей, которые у нас в зависимостях, — есть свои зависимости.
Каждая мажорная версия (1.0.0 —> 2.0.0) по конвенции допускает нарушение обратной совместимости.
https://docs.npmjs.com/about-semantic-versioning

Зафиксируем:
^1.0.0 — тут разработчик модуля говорит, что версия зависимости сойдёт вплоть до 2.0.0.
~1.0.0 — тут разработчик модуля говорит, что версия зависимости сойдёт вплоть до 1.1.0.

И — наши зависимости, зависимости модулей — могут между собой пересекаться.
А сама конечная зависимость — может быть только одна.
И, делая npm i — каждый новый раз — нода может выкачивать из своих репозиториев чутка разные набор зависимостей, более оптимальные на текущий момент, удовлетворяя при этом всем настройкам выше — и в наших зависимостях, и в зависимостях зависимостей.
Поэтому, несмотря на то, что package-lock.json такой страшный — к сожалению, его лучше всё же класть в git.
Чтобы не попасть в ситуацию, когда набор зависимостей на локале и на проде разный, а ты не можешь понять в чём ошибка.
А ошибка может быть в любой даже минорной версии с баг-фиксом в любой из зависимостей.
Хотя, это и бывает редко. Но бьёт больно.

Чтобы package-lock.json так не раздражал в git-e, парни предлагают:
.gitattributes —> package-lock.json binary
https://stackoverflow.com/a/50982431/4117781

Итого в .npmrc

https://docs.npmjs.com/cli/v10/configuring-npm/npmrc

engine-strict=true <— добрая рекомендация от Svelte
save-exact=true
save-dev=true

Что ещё интересного в package.json?

1. "private": true,
Consider also setting "private": true to prevent accidental publication.
https://docs.npmjs.com/cli/v10/configuring-npm/package-json
Не знаю как это возможно, но, если «accidental», то убирать не будем.

2. "type": module,
Это то, что позволяет нам делать модные import a from '/b.js, вместо старых a = require('b') и т.д.

3. Авторская рекомендация
Т.к. у нас кроме ноды есть строгое API со всякими проверками на всякие хосты, порты и заголовки,
стоит гарантировать на каком хосте/порте будет открываться dev-версия.
Заменить:
"dev": "vite dev",
На:
"dev": "vite dev --host 127.0.0.1 --port 3000",
А ещё добавить:
"host": "vite dev --host --port 3000",
чтобы просто npm run host, и проект можно посмотреть хоть на телефоне (если подключен к тому же wi-fi)

Во имя науки

  1. Можно руками закинуть модуль и в «prod» и в «dev». И даже ничего не сломается, но — не знаю зачем. При попытке обновить/форсировать установку ещё раз — одна из записей сотрётся, в зависимости от куда --save-x и т.д.
  2. optionalDependencies — когда допускаем, что модуль может не установиться, а он нам и не очень-то и нужен.
  3. bundleDependencies — когда пилим свой модуль.
  4. Постоянно встречаются упоминания npm-shrinkwrap.json — Это «freeze» package-lock.json для тех случаев когда это внезапно нужно:
    • Ноддерживает старые версии ноды (когда package-lock.json только с v5),
    • Необходим для публикации в качестве npm-модуля,
    • А также, если встряли с версиями зависимостей. Тут npm-shrinkwrap.json позволит управлять версиями вручную (https://nodejs.org/en/blog/npm/managing-node-js-dependencies-with-shrinkwrap/). В общем, не думая об этом — мы ничего не упускаем. Просто будем знать на всякий случай.

Как делать install на проде?

Принято делать вот так:

npm ci --[ТОЛЬКО PROD-DEPENDENCIES]

  1. [ТОЛЬКО PROD-DEPENDENCIES]

Можно встретить много рекомендаций, обещающих один и тот же результат:
--omit=dev, --include=prod, --only=prod, --production

По всей видимости это из-за долгой эволюции ноды.
Для последней 10-й версии в документации есть только --include=prod (https://docs.npmjs.com/cli/v10/commands/npm-ci).
Но, так — у меня устанавливаются всё — и «prod» и «dev».
В итоге, делаю так:
npm ci --omit=dev
Множественное «опущение» делается так:
npm ci --omit=dev --omit=peer

  1. Что за ci? https://docs.npmjs.com/cli/v10/commands/npm-ci

Собственно, это то, что выкачивает node_modules/ на основе package-lock.json, в обход package.json, гарантируя то, что на проде будет так, как было на локале.

Иии, внииимаааниииеее...

Спасибо, за внимание.

Top comments (0)