Базовый ультимативный гайд по Node.js (на примере SvelteKit)
Published: 2023-11-11 09:56:38

_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"
	}, 
	```  

2. `.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 = ""` не надо, это была внутренняя штука для глобального конфига.)

4. Можно руками постирать все `^`, но — тогда у нас окажутся устаревшие пакеты :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"
}
```
Испугался, и решил не использовать.
Давайте лучше дружно хламить `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`

2. Что за `ci`?
https://docs.npmjs.com/cli/v10/commands/npm-ci
   
Собственно, это то, что выкачивает `node_modules/` на основе `package-lock.json`, в обход `package.json`, гарантируя то, что на проде будет так, как было на локале.

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

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