Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Що таке package.json і як працює npm?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**package.json** - файл конфігурації Node.js проекту, що містить залежності, скрипти та метадані. npm читає його щоб встановити пакети і виконати команди. ```bash npm install express # додає express до dependencies в package.json npm start # запускає скрипт "start" визначений у package.json ``` **Ключове:** завжди комітьте `package-lock.json` разом з `package.json` - це гарантує однакові версії на всіх машинах.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**package.json** - це файл конфігурації в корені кожного Node.js проекту. Він містить залежності, визначає скрипти і зберігає метадані. **npm** читає його, щоб завантажити пакети з публічного реєстру і виконати визначені команди. ## Теорія ### TL;DR - package.json - список покупок проекту, npm - служба доставки що все це привозить - `dependencies` потрібні в продакшені; `devDependencies` - тільки для збірки та тестів - `package-lock.json` фіксує точні версії, щоб кожен `npm install` давав однаковий результат - Ніколи не комітьте `node_modules/`; завжди комітьте `package-lock.json` - У CI/CD пайплайнах використовуй `npm ci` замість `npm install` ### Швидкий приклад ```bash mkdir my-app && cd my-app npm init -y # генерує package.json миттєво npm install express # додає до dependencies npm install --save-dev jest # додає до devDependencies npm start # виконує скрипт "start" ``` ```json { "name": "my-app", "version": "1.0.0", "scripts": { "start": "node index.js" }, "dependencies": { "express": "^4.19.2" }, "devDependencies": { "jest": "^29.0.0" } } ``` Команда `npm start` запускає запис `"start"` зі секції scripts. Ось і вся ідея. ### Основні поля | Поле | Що робить | |---|---| | `name` | Назва пакету, унікальна якщо публікуєте в реєстр npm | | `version` | SemVer: major.minor.patch | | `main` | Файл, який Node завантажує при `require('your-package')` | | `scripts` | Команди доступні через `npm run <назва>` | | `dependencies` | Пакети для роботи в продакшені | | `devDependencies` | Пакети тільки для розробки, виключаються при `--production` | | `peerDependencies` | Пакети, які споживач вашої бібліотеки має встановити сам | | `engines` | Мінімальна версія Node.js для вашого коду | ### Версії та SemVer ```json "express": "4.19.2" // точна версія, ніколи не оновлюється "express": "^4.19.2" // сумісна: >=4.19.2 <5.0.0 (npm за замовчуванням) "express": "~4.19.2" // тільки патчі: >=4.19.2 <4.20.0 "express": "*" // будь-яка версія - уникайте в реальних проектах ``` Префікс `^` npm записує за замовчуванням при `npm install`. Він дозволяє minor та patch оновлення, які за правилами SemVer мають бути зворотньо сумісними. Префікс `~` строгіший: тільки патчі. Точне закріплення версії звучить надійніше, але означає що ви пропустите патчі безпеки якщо не оновлюєте вручну. ### Як працює npm install Коли запускаєш `npm install`, npm робить три речі по черзі. Спочатку читає package.json щоб дізнатись потрібні діапазони версій. Потім перевіряє `package-lock.json` на точні версії з попередньої установки. І нарешті завантажує архіви з `registry.npmjs.org` та розпаковує їх у `node_modules/`. Якщо локфайлу ще немає, npm вирішує останні відповідні версії і створює його. `npm ci` пропускає крок вирішення повністю. Він читає локфайл напряму і завершується з помилкою якщо той не відповідає package.json. Тому CI/CD пайплайни і використовують саме цю команду: результат передбачуваний, встановлення швидше. Я бачив як проекти ламались на стейджингу після того як хтось видалив `package-lock.json` для чистоти - і minor версії пакетів тихо змінили поведінку. Локфайл існує не просто так. ### npm скрипти та lifecycle хуки ```json { "scripts": { "start": "node dist/index.js", "dev": "nodemon src/index.ts", "build": "tsc", "test": "jest --coverage", "prestart": "npm run build", "posttest": "echo 'Tests done'" } } ``` Префікси `pre` та `post` - це lifecycle хуки. `prestart` запускається перед `start` автоматично, без окремого виклику. Скрипти запускають через `npm run build`, але `npm start` та `npm test` мають вбудовані скорочення без ключового слова `run`. ### Коли що використовувати - Новий проект з нуля: `npm init -y` - Додати бібліотеку для рантайму: `npm install express` - Додати інструмент для розробки: `npm install --save-dev typescript jest` - Поділитись проектом: закоміть `package.json` + `package-lock.json`, колеги запускають `npm install` - Деплой або CI/CD: `npm ci` замість `npm install` - Перевірити відомі вразливості: `npm audit` ### Типові помилки **Не комітити package-lock.json.** Без нього `npm install` завантажує найновішу версію в межах діапазону. Твій `^4.19.2` через місяць може встановити `4.20.1` для колеги якщо вийшла нова minor версія. Речі ламаються. ```bash # Неправильно echo "package-lock.json" >> .gitignore git commit -m "cleanup" # колеги отримають різні версії пакетів # Правильно git add package-lock.json # завжди комітьте локфайл ``` **Комітити node_modules.** Ця папка на звичайному проекті важить 200MB+. npm відновить її за кілька секунд з локфайлу. Немає сенсу тримати її в git. **Класти dev-інструменти у dependencies.** TypeScript, Jest, nodemon не запускають застосунок в продакшені. Вони мають бути в `devDependencies`. Якщо потраплять у `dependencies`, їх встановлять на кожен продакшен деплой без потреби. **Зірочка у полі engines.** `"node": "*"` означає що код може потрапити на Node 12, де немає top-level `await` та інших нових API. Вказуйте мінімум: `"node": ">=20.0.0"`. **`npm install` у CI пайплайнах.** Ця команда може змінити локфайл якщо знайде розбіжності. Використовуйте `npm ci` - вона завершується з помилкою при невідповідності, що виявляє проблему замість того щоб приховати її. ### Де зустрічається у реальних проектах - Express: `"start": "node server.js"` у scripts, `express@^4.19.2` у dependencies - Create React App: `react`, `react-dom`, `react-scripts` у dependencies; `npm start` запускає webpack dev server - Next.js: `"build": "next build"`, `"start": "next start"` для SSR у продакшені - NestJS: `@nestjs/core`, `@nestjs/common` у dependencies, `"node": ">=18"` у engines - Для одного застосунку npm підходить добре. Монорепозиторії з кількома пакетами часто переходять на pnpm для кращої підтримки workspace і меншого використання диска. ### Питання на співбесіді **Q:** Яка різниця між `dependencies` та `devDependencies`? **A:** `dependencies` встановлюються в продакшені. `devDependencies` пропускаються при `npm install --production`. Jest, TypeScript та nodemon мають бути в `devDependencies`, бо в рантаймі вони не потрібні. **Q:** Що означає `"^4.19.2"` у термінах SemVer? **A:** `^` дозволяє minor та patch оновлення: будь-яка версія `>=4.19.2 <5.0.0`. `~` строгіший: тільки патчі, `>=4.19.2 <4.20.0`. Major версія ніколи не оновлюється автоматично, SemVer вважає їх breaking changes. **Q:** Що станеться якщо немає package-lock.json? **A:** npm вирішить останню версію що відповідає кожному діапазону і створить новий локфайл. Два окремих `npm install` в різний час можуть встановити різні версії, звідси класична проблема "у мене працює, у тебе ні". **Q:** Яка різниця між `npm install` та `npm ci`? **A:** `npm install` вирішує версії і може оновити локфайл як побічний ефект. `npm ci` читає локфайл точно як він є та завершується з помилкою при невідповідності з package.json. Для автоматизованих пайплайнів підходить другий варіант. **Q:** (Senior) Як npm workspaces керують спільними залежностями в монорепозиторії? **A:** Поле `"workspaces": ["packages/*"]` у кореневому package.json каже npm підіймати спільні залежності до кореневого `node_modules`. Якщо три пакети залежать від React, npm встановить його один раз. Поле `overrides` дозволяє примусово встановити конкретну версію для всіх суб-пакетів коли виникають конфлікти peer dependencies. ## Приклади ### Базовий: Express сервер з npm start ```json // package.json { "name": "basic-api", "version": "1.0.0", "main": "index.js", "scripts": { "start": "node index.js" }, "dependencies": { "express": "^4.19.2" } } ``` ```js // index.js const express = require('express'); const app = express(); app.get('/', (req, res) => res.send('Hello World')); app.listen(3000, () => console.log('Server on port 3000')); ``` Запускаєш `npm install` щоб отримати Express, потім `npm start`. У терміналі з'явиться "Server on port 3000". GET до `localhost:3000` поверне "Hello World". З цього мінімального налаштування починається кожен Express проект. ### Середній: TypeScript з build і watch скриптами ```json // package.json { "name": "ts-api", "version": "1.0.0", "scripts": { "start": "node dist/index.js", "dev": "nodemon src/index.ts", "build": "tsc", "prestart": "npm run build" // компілює перед запуском }, "dependencies": { "express": "^4.19.2", "dotenv": "^16.4.5" }, "devDependencies": { "nodemon": "^3.1.4", "typescript": "^5.6.2", "@types/express": "^4.17.21" }, "engines": { "node": ">=20.0.0" } } ``` ```ts // src/index.ts import express from 'express'; import dotenv from 'dotenv'; dotenv.config(); const app = express(); const port = process.env.PORT || 3000; app.get('/', (req, res) => res.send(`Running on port ${port}`)); app.listen(port); ``` `npm run dev` запускає nodemon, який стежить за TypeScript файлами і перезапускає сервер при змінах. `npm start` спочатку виконає `prestart` - компілює TypeScript у `dist/`, потім запускає скомпільований код. Ключовий патерн: nodemon, typescript та @types у `devDependencies`, express та dotenv у `dependencies`. ### Просунутий: Конфлікт peer dependencies та виправлення через overrides ```json // packages/ui-lib/package.json (суб-пакет у монорепозиторії) { "name": "ui-lib", "peerDependencies": { "react": "^18.0.0" }, "dependencies": { "lodash": "^4.17.21" } } ``` Якщо кореневий застосунок встановить React 17, а `ui-lib` оголошує `react@^18` як peer dependency - npm попередить але встановить. В рантаймі компоненти що використовують React 18 API на React 17 рантаймі викидатимуть помилки. ```json // Кореневий package.json - примусово встановлює єдину версію React { "workspaces": ["packages/*"], "overrides": { "react": "^18.3.0" } } ``` Поле `overrides` примушує всі суб-пакети вирішувати React в `^18.3.0`, усуваючи невідповідність. Без нього: `Module not found` або помилки типів у рантаймі. З ним: одна версія React по всьому workspace.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.