RE:NODE

აპლიკაციები12 წუთის საკითხავი

npm ci თუ npm install: რომელი უნდა იყოს deploy-ში

რას აკეთებს npm ci სხვანაირად, რატომ უნდა იყოს lockfile commit-ში და როგორ შეინახო devDependencies build-ისთვის და მოიშორო ის მის შემდეგ.

0 მკითხველი

გამოიყენე npm install შენს კომპიუტერზე, როცა dependency-ებს ცვლი. გამოიყენე npm ci ყველგან, სადაც მანქანა შენს მაგივრად აყენებს - შენს სერვერზე, build ნაბიჯში, CI-ში. განსხვავება ისაა, რომ npm install-ს უფლება აქვს შეცვალოს შენი dependency ხე, npm ci-ს - არა: ის შლის node_modules-ს, აყენებს ზუსტად იმას, რაც package-lock.json-ში წერია, და ხმამაღლა ვარდება, თუ lockfile და package.json ერთმანეთს არ ეთანხმება. ეს ერთი თვისება არის მიზეზი, რის გამოც ვერსია, რომელიც გამოსცადე, არის ვერსია, რომელიც გადის. ყველაფერი ქვემოთ აქედან გამომდინარეობს, იმ ნაწილის ჩათვლით, რომელიც ყველას ებმევა - build ნაბიჯი, რომელსაც სჭირდება dev dependency-ები, რომელთა გამოტოვებასაც ცდილობდი.

რას აკეთებს თითოეული ბრძანება სინამდვილეში#

npm installnpm ci
სჭირდება lockfileარადიახ, თორემ გამოდის
წერს lockfile-სდიახ, როცა უნდაარასოდეს
არსებული node_modulesიყენებს და ასწორებსჯერ იშლება
პაკეტის ვერსიის დიაპაზონებიხელახლა ითვლის ^ და ~-სუგულებელყოფს, აყენებს ხეს
სინქრონიდან გასული package.jsonჩუმად ასწორებსვარდება EUSAGE-ით
ერთი პაკეტის დაყენებაnpm install lodashარ არის მხარდაჭერილი
ტიპიური სიჩქარეუფრო ნელიხშირად დაახლოებით ორჯერ სწრაფი

npm install კითხულობს package.json-ს, ხედავს დიაპაზონებს, როგორიცაა "express": "^4.18.2", და ითვლის ხეს, რომელიც მათ აკმაყოფილებს. თუ lockfile არსებობს და მისი ვერსიები ამ დიაპაზონებს ჯერ კიდევ აკმაყოფილებს, იყენებს მათ. თუ არა - რადგან ვინმემ package.json ხელით შეცვალა, ან dependency სხვა branch-ზე დაემატა - ახლიდან წყვეტს და lockfile-ს ხელახლა წერს. ლეპტოპზე ეს გამოსადეგია. სერვერზე ეს ნიშნავს, რომ შენმა deploy-მ ჩუმად ის დააყენა, რაც არასოდეს გაგიშვია.

npm ci არავითარ resolution-ს არ აკეთებს. lockfile უკვე შეიცავს სრულ ხეს: ყველა პაკეტს, მის ზუსტ ვერსიას, resolved URL-ს და integrity hash-ს. npm ci ამ ხეს აწყობს და ჩერდება. ის დეტერმინისტულია ძლიერი აზრით - იგივე lockfile იძლევა იგივე node_modules-ს ნებისმიერ მანქანაზე, ნებისმიერ დღეს, რაც არ უნდა გამოქვეყნებულიყო შუალედში.

გვერდითი ეფექტი, რომელიც უნდა იცოდე: ERESOLVE peer-dependency შეცდომები npm install-ის ფენომენია. ისინი resolution-ის დროს ჩნდება, npm ci კი არ ითვლის. თუ ლოკალურად npm install --legacy-peer-deps-ის გაშვება გიწევდა, მიღებული ხე უკვე lockfile-შია ჩაფენილი და სერვერს ეს flag არ სჭირდება.

რატომ წყვეტს ყველაფერს lockfile#

package-lock.json build არტეფაქტი არ არის. ის საწყისი კოდია და git-ში მიდის. "განვითარებაში მუშაობდა"-ს ყველაზე გავრცელებული მიზეზი .gitignore-ია, რომელიც package-lock.json-ს შეიცავს, ჩვეულებრივ ბიბლიოთეკის შაბლონიდან გადმოკოპირებული, სადაც მისი გამორიცხვა დასაცავია. აპლიკაციისთვის ის არასოდეს არის.

შიგნით თითოეული ჩანაწერი დაახლოებით ასე გამოიყურება:

json
"node_modules/express": {  "version": "4.18.2",  "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",  "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",  "dependencies": { "accepts": "~1.3.8" }}

integrity hash არის ის, რაც განმეორებად ინსტალაციას უსაფრთხოების თვისებად აქცევს და არა უბრალოდ მოხერხებულობად: თუ tarball ამ URL-ზე ოდესმე შეიცვლება, ინსტალაცია ვარდება და იმავე ვერსიის ნომრით სხვა კოდს არ უშვებს.

Header ველი lockfileVersion გეუბნება, რომელმა npm-მა დაწერა:

ვერსიავინ წერსშენიშვნები
1npm 6ხის სრული მეტამონაცემების გარეშე; ახალი npm მას ანახლებს
2npm 7 და 8შეიცავს ორივე ფორმატს, ამიტომ npm 6-საც შეუძლია წაკითხვა
3npm 9 და 10 ახალი პროექტებისთვისუფრო პატარაა, სჭირდება npm 7 ან უფრო ახალი

npm-ის სხვადასხვა ვერსიის გუნდში შერევა არის გზა, რომლითაც ერთი dependency-ის შეცვლის pull request-ზე 4,000 ხაზიან lockfile diff-ს მიიღებ. ამის ნაცვლად runtime-ი დააფიქსირე: .nvmrc ფაილი და engines ბლოკი, რომელიც ამბობს, რას უჭერ მხარს.

package.json
{  "engines": { "node": ">=20.0.0", "npm": ">=10.0.0" }}

ნაგულისხმევად engines მხოლოდ რჩევაა. დაამატე engine-strict=true .npmrc-ში, რომ შეუსაბამობა შეცდომად იქცეს, რაც სწორი არჩევანია პროექტისთვის, სადაც სერვერის Node ვერსია ფიქსირებულია და შენი ლეპტოპისა - არა.

devDependencies, build-ები და ხაფანგი შუაში#

--omit=dev (deprecated --production-ის თანამედროვე მართლწერა) devDependencies-ში მყოფ ყველაფერს გამოტოვებს. სერვერზე ეს არის ის, რაც გინდა: არც TypeScript compiler, არც ტესტ runner, არც bundler, ასობით მეგაბაიტით ნაკლები დისკზე.

გარდა იმისა, რომ typescript, vite, webpack, esbuild, tailwindcss და prisma ყველა dev dependency-ა და ყველა მათგანია საჭირო იმისთვის, რასაც გასაშვებად ამზადებ. ამიტომ ეს ვარდება:

bash
$ npm ci --omit=dev$ npm run buildsh: tsc: not found

არსებობს ორი სწორი თანმიმდევრობა და რომელიც გინდა, იმაზეა დამოკიდებული, build იმავე მანქანაზე ხდება თუ არა, სადაც გაშვება.

Build სერვერზე. დააყენე ყველაფერი, გააკეთე build, შემდეგ prune:

bash
$ npm ci --no-audit --no-fund$ npm run build$ npm prune --omit=dev

npm prune --omit=dev dev პაკეტებს არსებული node_modules-იდან შლის და დანარჩენს არ ეხება. ეს ნაბიჯია, რომელსაც ხალხი ტოვებს და ჩვეულებრივ რამდენიმე ასეულ მეგაბაიტს ღირს.

Build სხვაგან. თუ CI job ან შენი ლეპტოპი აწარმოებს build-ის შედეგს და აგზავნის, სერვერს მხოლოდ runtime პაკეტები სჭირდება:

bash
$ npm ci --omit=dev --no-audit --no-fund$ node dist/server.js

ნუ დაეყრდნობი NODE_ENV=production-ს dev dependency-ების გამოსატოვებლად. ეს ქცევა npm-ის ვერსიებს შორის განსხვავდებოდა და deploy, რომელიც გარემოს ცვლადის გვერდით ეფექტზეა დამოკიდებული, deploy-ა, რომელიც განახლებისას გატყდება. გადაეცი flag.

კიდევ ერთი შედეგი dev dependency-ების გამოტოვებისა: prepare lifecycle script არ გაეშვება. პროექტები, რომლებიც prepare-ში საკუთარ თავს აგებენ - გავრცელებულია Husky-ით და პაკეტებით, რომლებიც პირდაპირ git URL-იდან ინსტალირდება - ჩუმად არაფერს აწარმოებენ. თუ შენი ინსტალაცია prepare-ზეა დამოკიდებული, სრული ინსტალაცია გჭირდება.

deploy-ის თანმიმდევრობა, რომელიც მუშაობს#

თანმიმდევრობით, Node deploy პატარა სერვერზე ასე გამოიყურება:

  1. მოიტანე კოდი, package-lock.json-ის ჩათვლით. თუ lockfile აკლია, გაჩერდი და ეს გამოასწორე ყველაფრამდე.
  2. `npm ci` dev dependency-ებით, თუ build აქ ხდება.
  3. Build - npm run build, ან tsc, ან რაც dist-ს აწარმოებს.
  4. გაუშვი migration-ები, თუ რელიზს სჭირდება, და გახადე ისინი უკუთავსებადი, რომ ძველ პროცესს შეეძლოს request-ების მომსახურება, სანამ ახალი იწყება. Migration-ები downtime-ის გარეშე გრძელი ვერსიაა.
  5. `npm prune --omit=dev` build ხელსაწყოების მოსაშორებლად.
  6. გადატვირთე პროცესი და უყურე log-ს, სანამ ის არ იტყვის, რომ უსმენს.

RE:NODE-ზე პირველი ნაბიჯია Git deploy ფუნქცია app გეგმებზე: GitHub GitHub App-ის გავლით მოკლევადიანი token-ებით, ასე რომ private repository-ები მუშაობს და პირად წვდომის token-ს არსად უნდა ჩასვა. მას ორი გადამრთველი აკონტროლებს - pull ყოველ გაშვებაზე და deploy push-ზე, რომელიც სერვერს მხოლოდ იმ შემთხვევაში გადატვირთავს, თუ ის უკვე გაშვებული იყო - და ყოველი deploy ჩანაწერს ტოვებს. Node აპლიკაციის deploy GitHub-იდან დაყენებას გადის.

წარუმატებლობა, რომლის თავიდან აცილებასაც ეს თანმიმდევრობა ემსახურება, crash loop-ია. თუ ინსტალაცია ვარდება, პროცესი გადის, რაღაც მას ხელახლა უშვებს, ინსტალაცია ისევ ვარდება. RE:NODE-ზე watcher ამას ამჩნევს: საათში სამი გადატვირთვა სერვერის გვერდზე გაფრთხილებას აყენებს და ავტომატურად ticket-ს ხსნის, ექვსი კი სერვერს აჩერებს. ეს განზრახ მუხრუჭია და არა სასჯელი, მაგრამ გაცილებით ადვილია, თუ ნაბიჯი 2 ერთხელ ვარდება, თვალსაჩინოდ, კონსოლში, რომელსაც კითხულობ. კონსოლის კითხვა ამბობს, რას უნდა დაუკვირდე.

თუ deploy-ს შეუძლია საიტი ჩამოაგდოს, ის ერთ წუთში შექცევადი უნდა იყოს. შეინახე წინა build და აიღე backup dependency-ის განახლებამდე, რომელიც რამეს native-ს ეხება - zero-downtime deploy-ები პატარა სერვერზე შეიცავს გადართვის შაბლონებს, რომლებიც ერთ მანქანაზე ჯდება.

ინსტალაციის დრო, cache და დისკი პატარა გეგმაზე#

npm ci-ის მიერ node_modules-ის წაშლა ძვირად ჟღერს და უმეტესად არ არის, რადგან თავად პაკეტები ლოკალური cache-დან მოდის ~/.npm/_cacache-ში და არა ქსელიდან. თბილი cache ინსტალაციების უმეტესობას ფაილების კოპირებად აქცევს.

Flag-ები, რომლებიც პატარა სერვერზე მართლა ეხმარება:

  • --no-audit - გამოტოვებს დაუცველობის შემოწმებას, რომელიც ინსტალაციის ბოლოს სრულდება. მას ქსელური ჩავლა სჭირდება და ბეჭდავს ტექსტის კედელს, რომელსაც deploy-ის დროს არავინ კითხულობს. audit გააკეთე CI-ში.
  • --no-fund - აქრობს დაფინანსების შეტყობინებას. კოსმეტიკურია, მაგრამ log-ს ამოკლებს.
  • --prefer-offline - იყენებს cache-ის მონაცემებს სადაც შეუძლია და registry-ს მხოლოდ იმას ეკითხება, რაც აკლია. გამოსადეგია, როცა registry ნელია და არა გათიშული.
  • --ignore-scripts - უარს ამბობს dependency-ების install script-ების გაშვებაზე. კარგია supply-chain უსაფრთხოებისთვის, მაგრამ ტეხავს ნებისმიერ პაკეტს, რომელიც native კოდს ინსტალაციისას აკომპილირებს, ამიტომ გამოსცადე აღებამდე.

დისკი შეზღუდვაა, რომელსაც ხალხი პირველად ხვდება. უბრალო Express API-ის node_modules 50-100 MB-ია; Next.js აპლიკაცია UI ბიბლიოთეკით ჩვეულებრივ 500-800 MB-ია, სანამ რამეს ააგებ, ხოლო build-ის შედეგი და npm cache მის გვერდით დგას. 5 GB-იან გეგმაზე ეს დამრგვალების ცდომილება არ არის. ორი ჩვევა მას კონტროლში ინახავს: prune build-ის შემდეგ და cache-ის დროდადრო გასუფთავება npm cache clean --force-ით - თუმცა გაითვალისწინე, რომ ეს შემდეგ ინსტალაციას ანელებს, ამიტომ გააკეთე მაშინ, როცა დისკი მართლა მჭიდროა და არა განრიგით.

მეხსიერება მეორეა. ინსტალაცია იაფია; bundling - არა. tsc და next build დიდ პროექტზე რეგულარულად ერთ გიგაბაიტზე მეტს ითხოვს და 1 GB-იან გეგმაზე build იღუპება და არა აპლიკაცია. RE:NODE-ზე მეხსიერების ლიმიტზე მიღწევა კონტეინერს აჩერებს და სუფთად გადატვირთავს swap-ის ნაცვლად, ამიტომ სიმპტომია build, რომელიც გაშვების შუაში უჩინარდება შეცდომის გარეშე. თუ ეს შენ ხარ, ან build გააკეთე სხვაგან და deploy-ე შედეგი, ან ამ დროისთვის ერთი დონით ზემოთ გადადი. Node მეხსიერების ლიმიტები ახსნილი heap flag-ებს ფარავს და იმას, რას ვერ ასწორებენ.

როცა npm ci უარს ამბობს და რას ნიშნავს შეცდომა#

`npm ci` can only install packages when your `package.json` and `package-lock.json` are in sync. lockfile არ შეიცავს რაღაცას, რასაც package.json ითხოვს. ვიღაცამ პირდაპირ შეცვალა package.json, ან merge-მა lockfile ცუდად გადაწყვიტა. გამოასწორე შენს მანქანაზე npm install-ით, გააკეთე განახლებული lockfile-ის commit, deploy-ე ისევ. ნუ "გამოასწორებ" სერვერის npm install-ზე გადართვით - ეს გადახრას სამუდამოდ მალავს.

`The npm ci command can only install with an existing package-lock.json`. რეპოზიტორიაში lockfile არ არის. დააგენერირე npm install-ით, გააკეთე commit.

Merge conflict-ები lockfile-ში. არასოდეს ჩაასწორო ხელით. აიღე რომელიმე მხარე, შემდეგ შეათანხმე:

bash
$ git checkout --theirs package-lock.json$ npm install$ git add package-lock.json

npm install სწორი package.json-ით ხელახლა წერს თანმიმდევრულ lockfile-ს, რაც ერთადერთი გზაა, რომ მართლა ვალიდური მიიღო.

`Cannot find module @rollup/rollup-linux-x64-gnu` ან მსგავსი პლატფორმა-სპეციფიკური პაკეტი. არჩევითი dependency-ები პლატფორმის მიხედვით წყდება და macOS-ზე ან Windows-ზე დაგენერირებულ lockfile-ს შეიძლება გამორჩეს Linux binary, რომელიც შენს სერვერს სჭირდება. ხელახლა დააგენერირე lockfile Linux-ზე - სერვერის შესაბამის კონტეინერში ან CI-ში - და გააკეთე commit. სერვერზე node_modules-ის წაშლა არ ეხმარება, რადგან გამოტოვებული ჩანაწერი lockfile-შია.

`EBADENGINE`. დაყენებული Node ან npm engines-ს არ აკმაყოფილებს. ან სერვერი ძველ runtime-ზეა, ან პაკეტი შენ ვარაუდზე უფრო ახალია. შეამოწმე node -v-ით და npm -v-ით, სანამ დაასკვნი, რომ პაკეტი არასწორია.

`EACCES` ან `EPERM` ინსტალაციისას. პროცესს არ შეუძლია იქ ჩაწერა, სადაც ცდილობს. კონტეინერზე დაფუძნებულ ჰოსტზე ეს ჩვეულებრივ ნიშნავს რაღაცას სერვერის საკუთარი დირექტორიის გარეთ, რომელიც დიზაინით ჩაწერადი არ არის.

`ENOSPC`. დისკი გამოილია. du -sh node_modules .npm გეტყვის, რომელმა შეჭამა.

lockfile-ის სიმართლის შენარჩუნება#

Lockfile გამოსადეგია მხოლოდ მაშინ, თუ ის ჭეშმარიტია. სამი ჩვევა მას ასეთს ინარჩუნებს.

ერთი ადამიანის მანქანა ეტალონი არ არის. გაუშვი npm ci CI-ში ყოველ pull request-ზე. ის ვარდება lockfile-ზე, რომელიც commit-ში არ შევიდა, და ვარდება lockfile-ზე, რომელიც აღარ ემთხვევა package.json-ს - ეს ორი შეცდომაა, რომლებიც production-მდე აღწევს.

განაახლე შეგნებულად. npm outdated გიჩვენებს, რა გადაინაცვლა. npm update შენს დიაპაზონებს პატივს სცემს და მათ შიგნით აწევს; მთავარი ვერსიის შეცვლა package.json-ის რედაქტირებაა, რასაც npm install მოსდევს. ერთ ჯგუფს აკეთე ერთდროულად, რომ გატეხილ deploy-ს ერთი ეჭვმიტანილი ჰქონდეს.

შეამოწმე, რას აგზავნი მართლა. npm ls --omit=dev --depth=0 ბეჭდავს runtime ხეს. ეს მოკლე სიაა და კვარტალში ერთხელ წაკითხვა პაკეტებს პოულობს, რომელთა დამატებაც არავის ახსოვს.

სხვა პაკეტ მენეჯერებისთვის npm ci-ის ეკვივალენტი flag-ია და არა ბრძანება:

ხელსაწყოგანმეორებადი ინსტალაცია
npmnpm ci
Yarn 1yarn install --frozen-lockfile
Yarn 2 და უფრო ახალიyarn install --immutable
pnpmpnpm install --frozen-lockfile (CI-ში ნაგულისხმევია)
Bunbun install --frozen-lockfile

რომელსაც არ უნდა იყენებდე, გააკეთე commit ერთი lockfile-ის და მხოლოდ ერთის. რეპოზიტორია, რომელიც შეიცავს ერთდროულად package-lock.json-ს და yarn.lock-ს, საბოლოოდ ორ განსხვავებულ ხეს დააყენებს იმის მიხედვით, ვინ რა გაუშვა, და დღე, როცა ამას აღმოაჩენ, დღეა, როცა ეს მნიშვნელოვანია.

Private registry-ებსა და scoped პაკეტებს credentials სჭირდება, რომლებიც გარემოს ცვლადში უნდა იყოს და არა commit-ში შესულ .npmrc-ში. npm .npmrc-ში ${NPM_TOKEN}-ს კითხვისას აფართოებს, ამიტომ ფაილი შეიძლება commit-ში იყოს, ხოლო მნიშვნელობა - არა - იხილე გარემოს ცვლადები და საიდუმლოებები, სად უნდა ინახებოდეს ეს მნიშვნელობა. თუ production-ის გვერდით staging სერვერი გყავს, თითოეულ საიდუმლოებას საკუთარი ასლი მიეცი; staging და production ერთ ანგარიშზე გამიჯვნას ფარავს.

FAQ#

უნდა გავაკეთო package-lock.json-ის commit?

დიახ, ნებისმიერი აპლიკაციისთვის. ეს არის ჩანაწერი იმისა, რაც გამოსცადე. ერთადერთი დასაცავი გამონაკლისია ბიბლიოთეკა, რომელიც registry-ში ქვეყნდება, სადაც მომხმარებლები საკუთარ ხეს ითვლიან და შენი lockfile მხოლოდ შენს განვითარებას ეხება.

npm ci უფრო სწრაფია, ვიდრე npm install?

ჩვეულებრივ, ხშირად დაახლოებით ნახევრით, რადგან ის dependency resolution-ს მთლიანად აცდენს. სხვაობა ყველაზე დიდია თბილი cache-ისა და დიდი ხის შემთხვევაში. სიჩქარე ბონუსია - მიზეზი დეტერმინიზმია.

შემიძლია npm ci გავუშვა devDependencies-ის გარეშე?

დიახ, npm ci --omit=dev-ით, მაგრამ მხოლოდ თუ შენს build-ს არაფერი სჭირდება მათგან. თუ build იმავე მანქანაზე ხდება, დააყენე ყველაფერი, გააკეთე build, შემდეგ გაუშვი npm prune --omit=dev მათ მოსაშორებლად.

რა ვქნა, თუ lockfile საერთოდ არ მაქვს?

npm ci უარს იტყვის. გაუშვი npm install ერთხელ მანქანაზე, რომელსაც ენდობი, გააკეთე დაგენერირებული package-lock.json-ის commit და მას შემდეგ npm ci გამოიყენე. ნუ დააგენერირებ მას production სერვერზე - ეს მიზანს ამარცხებს.

რატომ დააყენა ჩემმა deploy-მა ვერსია, რომელიც ჩემი ლეპტოპისგან განსხვავდება?

იმიტომ, რომ რამემ npm install გაუშვა სერვერზე lockfile-ით, რომელიც მას არ ზღუდავდა, ან lockfile-ის გარეშე. caret დიაპაზონი, როგორიცაა ^4.18.2, ნებისმიერ 4.x რელიზს იღებს და ერთ-ერთი მათგანი შენს ტესტსა და deploy-ს შორის გამოქვეყნდა.

npm ci შლის ჩემს ატვირთულ ფაილებს?

ის შლის node_modules-ს და სხვა არაფერს. ყველაფერი, რასაც ამ საქაღალდის გარეთ ინახავ - ატვირთვები, SQLite ფაილი, გენერირებული კონფიგი - ხელუხლებელია. ჩაწერადი მონაცემების node_modules-ში შენახვა ერთადერთი გზაა, რომ აქ დაზარალდე, და ღირს შემოწმება, რომ ამას არ აკეთებ.


კომენტარები

სრულიად ანონიმურად: ანგარიშის, ელფოსტის და cookie-ის გარეშე. ინახება მხოლოდ სახელი, ტექსტი და დრო - სხვა არაფერი. ბმულების რაოდენობა ლიმიტირებულია.

0/2000