הערה חשובה למשתמשי yarn – אני כותב פה על npm אבל המאמר הזה רלוונטי גם למשתמשי yarn שבת'כלס עובדת די דומה ל npm בקטע הזה. אני מצרף בתחתית המאמר "מילון מונחים למשתמשי yarn".
כאשר אנו מתקינים תוכנה כלשהי לראשונה ב-Node.js – אנו משתמשים ב-npm install (גם npx אבל על כך נכתוב במאמר המשך) הפקודה npm install בודקת את מה שיש ב-package.json ומתקינה את כל מה שיש שם. כך Node.js ו-npm עובדות.
מה בדיוק מותקן? package.json כוללת בתוכה dependencies ו-devDependepncies ושם יש את כל התלויות עם הגרסאות. כאשר npm install מסתכלת על הגרסאות השונות ומתקינה אותן. איך היא מתקינה? תלוי מה יש ליד כל מספר.
Semantic versioning
npm עובדת לפי שיטת semantic versioning – למשל מספר הגרסה 1.2.3. המספר הראשון הוא major. אם הוא משתנה, אז הגרסה בהכרח עלולה לשבור דברים. אם משדרגים מגרסה 1 לגרסה 2. למשל מ-1.2.3 ל 2.0.0, השינוי בהכרח מחייב שינוי בקוד שלכם.
המספר השני הוא minor. אם הוא משתנה, אז הגרסה לא תשבור דברים, אבל עלולה להופיע אזהרת deprecation על חלק מהתכונות/פונקציות שמשתמשים בהן או שנוספות פונקציות חדשות עם תאימות לאחור. אם למשל משדרגים מ-1.2.3 ל .1.3.0, אז לא יישבר כלום, אבל בהתקנה תוכלו לקבל אזהרות בטרמינל על התיישנות עתידית. יש גם שינויים בפונקציות אך עם תאימות לאחור.
המספר השלישי הוא patch. אם משדרגים מ-1.2.3 ל-1.2.556 אז אפשר להיות בטוחים שכלום לא נשבר אם השתמשתם במודול באופן סטנדרטי.
כש-npm install פועלת, היא מתקינה את הגרסאות החדשות ביותר שההוראות ב package.json אומרות לה להתקין. למשל. במידה ויש "~" פקודת npm install תתקין את גרסת ה-patch הכי חדשה. למשל:
"dependencies": {
"@material-ui/core": "~4.8.8"
},
בדוגמה הזו, npm install בודקת את המודול הזה ואם יש לו גרסה של 4.8.954, היא תתקין אותה. אבל לא את 4.9.0.
במידה ויש "^", אז פקודת npm install תתקין את גרסת המיינור או ה-patch הכי חדשה. למשל:
"dependencies": {
"@material-ui/core": "^4.8.8"
},
בדוגמה הזו, npm install בודקת את המודול הזה ואם יש לו גרסה של 4.8.954, היא תתקין אותה. אם יש גרסה של 4.9.0, היא תתקין אותה. גרסה של major לא תותקן. כלומר לא 5.0.0.
הבעיה עם semantic versions
מה הבעיה? יש כמה בעיות עם המודל הזה – אנחנו צריכים לסמוך על המתחזקים שבאמת יקפידו על semantic versions. כמובן שעדכונים אוטומטיים עלולים לגרום לבעיות אבטחה. אם מישהו רוצה להזכר בפרשות שבהם הסתמכו על עדכונים אוטומטיים כדי להכניס מודול רעיל – הוא מוזמן להכנס לפוסט הזה.
אז באפליקציות פרודקשן, רוב האנשים עושים version pinning. מה זה אומר? לא לכתוב ^ או ~ ליד הגרסה. משהו בסגנון הזה:
"dependencies": {
"@material-ui/core": "4.8.8"
},
ואז npm install תתקין אך ורק את הגרסה הזו. שזה מקובל מאוד אבל מה הבעיה? הבעיה היא שלמודולים האלו יש גם dependencies וגם ל-dependencies יש dependencies משלהן וכך הלאה. מתישהו מישהו שם לא עושה version pinning ואז קיבלנו את אותה בעיה. בכל npm install, יותקנו גרסאות שונות במקצת של חלק מהמודולים.
Versions pinning אמיתי עם package-lock
אז מה עושים? הכירו את package-lock.json. כשאנו מפעילים את npm install, הקובץ הזה נוצר. אם תסתכלו לתוכו תראו json ארוך שמכיל את כל ה-dependencies של הפרויקט. כלומר את כולן, הילדים של, הנכדים של, הנינים. כולן כולן כולן וכל ישראל חברים.
אם אנו מפעילים את npm ci, ולא את npm install. ההתקנה מתבצעת בדיוק, אבל בדיוק – לפי הקובץ הזה. כלומר אין הפתעות. אין dependency של dependency שבמקרה השאירה איזה ^ באיזה package.json. מה רואים? זה מה שמקבלים. את package-lock.json מכניסים אל תוך הגיט (או כל מערכת ניהול גרסאות שאתם משתמשים בה) ובבילדים השונים? לבצע את ההתקנה עם npm ci. כאשר מפתח מתקין סביבה מקומית אצלו? כנ"ל.
כדאי לשים לב שאם לא תבצעו עדכון יזום עם npm update (או מחיקה של node_modules ו-npm install) – אז package-lock.json לא יתעדכן ולעולם ה-dependencies לא יקבלו עדכונים. וחלק מהעדכונים הם עדכוני אבטחה חשובים. מה אני עושה? נעזר בשירותי סניק כדי לקבל התראות אבטחה או לחלופין מריץ כל יום npm audit. יש בעיות? יוצר פול ריקווסט נפרד של "עדכון dependencies". לעולם לא דוחף עדכון ל package-lock.json על הדרך. גם כל איזה שבוע או שבועיים, מריץ npm install בנפרד כדי לקבל עדכונים שהם לא עדכוני אבטחה. זה נשמע קצת מסורבל, אבל זה מונע הפתעות, טלפונים באמצע הלילה ממפתחים בג'הנמיסטן שפתאום הבילד שלהם נשבר וזו הדרך הנכונה לעבוד לדעתי. אם יש לכם איש devops טוב, הוא גם יכניס את הכל לאוטומציה באופן חלק. אם לא – ובכן, אתם גם יכולים לעשות את זה בעצמכם.
אבל (!) זה לא הכל, במאמר הבא אלמד על shrinkwrap ואיך הוא יכול לסייע לנו עם npx ובדיוק באותה בעיה ואיך שימוש בו יכול למנוע בעיה ששברה את create react app.
מילון מונחים ל-yarnיסטים מתנשאים ועילאיים:
npm install === yarn install
package-lock.json === yarn.lock
npm ci === yarn install –frozen-lockfile
האמת היא ש-yarn.lock לא זהה ל package-lock.json במאה אחוז אבל על כך באמת במאמר הבא.
⚠️ תזכורת – המדריכים האלו הם רק טעימה, בספר שלי "ללמוד Node.js בעברית" יש הסברים מלאים ומקיפים על השפה המיועדים ללימוד עצמי. עם תרגילים והסברים. הספר יצא לאור בשיתוף הקריה האקדמית אונו ובתמיכת החברות אלמנטור, ו-Iron source ונערך טכנית על ידי בנג'י גרינבאום (מפתח ליבה של Node.js), גיל פינק ומתכנתים מעולים נוספים.
3 תגובות
היי, כשאתה אומר ״אין dependency של dependency שבמקרה השאירה איזה ^ באיזה package.json״ אתה מתכוון לזה שאם השתמשנו בnpm ci, אפילו אם לתלות של תלות יש טילדה למשל הוא יתקין את הגרסה בדיוק?
למשל במקרה של 4.8.8~ יותקן 4.8.8 תמיד?
תודה.
Or use
https://github.com/apps/dependabot-preview
עשית לי סדר במספרים של הגרסאות…
תודה!!