Published on

איך לשלב Prettier בפרויקט קיים

Authors

זה לגמרי הגיוני למחוק חתיכה של קוד ולהשאיר רווח  מעצבן  כזה , כי אחרי שנייה, בצורה אוטומטית בIDE או אוטומטית האצבעות לוחצות על Format Document. אבל זה הופך להיות לא הגיוני כשמפתח לא מקפיד על זה (וראיתי דברים). ואז מה? להעיר לו בCode Review על רווח מיותר? באמת?

אז יש לנו פרויקטים חיים ובועטים, עם קוד ישן ומכוער, וגם פרויקטים חדשים עם מתכנתים ישנים ומכוערים (כמובן שלא), ואנחנו רוצים להתחיל לשלוט בהם.

נו, מה הבעיה?

בגדול, יש לנו שתי דרכים לגשת לזה, ולכל אחת מהן יש מחיר כואב:

1. שינוי לאורך זמן

בגישה זו, אנחנו מחילים את הפורמט רק על קבצים שנגענו בהם.

הבעיה: זה הופך את ה-Code Review לסיוט. קשה להבדיל בין שינויי לוגיקה לבין שינויי עיצוב ("רעש"), ומפתחים שונאים לראות שינויים מאסיביים ב-PR שלהם בקבצים שהם בקושי נגעו בהם.

2. הגישה הכוללת

לפרמט את כל הפרויקט בבת-אחת.

הבעיה: זה יוצר שני מכשולים עיקריים. הראשון הוא Merge Conflicts מול כל מי שמחזיק גרסה לוקאלית לא עדכנית. השני, והחמור יותר, הוא אובדן ההיסטוריה. ברגע שקומיט אחד נוגע בכל השורות בקוד, פקודת git blame הופכת לחסרת תועלת. במקום לראות מי כתב את הלוגיקה המקורית, אנחנו תמיד נאשים את מי שנגע בכל הקוד כדי לפרמט אותו.

הבחירה שלי: לפרמט הכל

למה? כי את בעיית הקונפליקטים קל לפתור (אם כולם מריצים Prettier, הקונפליקטים נעלמים), ואת בעיית ההיסטוריה אפשר לפתור בעזרת פיצ'ר חכם (ופחות מוכר) של Git שמאפשר לנו "להעלים" קומיטים ספציפיים מההיסטוריה.

כדי שהקסם הזה יעבוד בלי תקלות, אנחנו לא זורקים את הכל בקומיט אחד ענק. במקום זה, נפצל את המהלך לשני שלבים (שני קומיטים) נפרדים (טוב, עדיין אחד מהם יהיה ענק), כשלכל אחד מטרה ברורה:

קומיט 1: הניקוי הגדול

בשלב הזה אנחנו מריצים את ה-Prettier על כל הקבצים בפרויקט.

הקומיט הזה הולך להיות ענק מבחינת כמות השורות שהוא משנה, אבל הוא חייב להכיל שינויי עיצוב בלבד (White-space, פסיקים, רווחים). שום לוגיקה לא משתנה כאן.

המטרה: ליישר קו שבו כל הקוד בפרויקט עומד בסטנדרט החדש. את הקומיט הזה אנחנו הולכים "להעלים" אחר כך.

קומיט 2: מנגנונים לשמירה על המצב הקיים

מיד אחרי הניקוי, אנחנו מכניסים את התשתית שתשמור על הסדר:

  1. אוטומציה (Husky + Lint-staged): כדי לוודא שכל קובץ חדש או ערוך יעבור פירמוט אוטומטי לפני שהוא נכנס לגיט.

  2. בדיקות (CI): כדי למנוע מ-PR לא מפורמט להיכנס ל-Main.

  3. ההסתרה (Git Blame Ignore): כאן אנחנו מוסיפים קובץ קונפיגורציה מיוחד שאומר לגיט: "רואה את הקומיט הקודם (קומיט 1)? תתעלם ממנו כשאתה מחשב git blame".

למה חייבים לפצל?

זה הקטע הקריטי. הפיצ'ר של גיט שמתעלם מקומיטים (ignore-revs) עובד ברמת הקומיט השלם.

אם נערבב את שינויי הפורמט יחד עם שינויי הקונפיגורציה (הוספת סקריפטים ל-package.json, הגדרות CI וכו') בקומיט אחד, ונבקש מגיט להתעלם ממנו – אנחנו בעצם "נעלים" מההיסטוריה גם את השינויים התשתיתיים החשובים האלו.

הפיצול מבטיח שרק "הרעש" של הפירמוט ייעלם, בעוד ששינויי התשתית יישארו גלויים וברורים בהיסטוריה.


איך מיישמים את זה בפועל

עכשיו, בואו נעשה את זה צעד אחר צעד.

שלב 1: קומיט הניקוי הגדול

אם לא התקנתם את prettier בפרויקט, תעצרו רגע ותתקינו עם קונפיגורציה.

עכשיו, הרגע האמיתי – נריץ את Prettier על כל הפרויקט:

npx prettier . --write

חשוב: לפני שממשיכים, וודאו שהרצתם את הפרויקט והבדיקות עברו בהצלחה. השלב הזה חייב לשנות רק פורמט, לא לוגיקה.

עכשיו נעשה commit:

git add .
git commit -m "chore: format entire codebase with prettier"

הקטע הקריטי ביותר: מיד אחרי הקומיט, תשמרו את ה-SHA שלו:

git rev-parse HEAD

הפלט יהיה משהו כמו: a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0

שלב 2: קומיט התשתית והסתרת ההיסטוריה

עכשיו נתקין את הכלים שישמרו על הסדר מכאן והלאה:

npm install --save-dev husky lint-staged
npx husky init

נגדיר את lint-staged בקובץ package.json:

{
  "lint-staged": {
    "*.{js,jsx,ts,tsx,json,css,md,mdx}": ["prettier --write"]
  }
}

נוסיף Pre-commit Hook. פתחו את הקובץ .husky/pre-commit והוסיפו:

npx lint-staged

עכשיו הקסם: ניצור קובץ בשם .git-blame-ignore-revs בתיקיית הפרויקט, ונדביק לתוכו את ה-SHA ששמרנו קודם:

# Format entire codebase with prettier
a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0

זהו! עכשיו נעשה commit לכל השינויים האלו:

git add .
git commit -m "chore: add prettier automation and ignore formatting commit"

שלב 3: אוטומציה של הקונפיגורציה

יש בעיה קטנה: גיט לא משתמש בקובץ .git-blame-ignore-revs באופן אוטומטי. כל מפתח צריך להגדיר את זה ידנית במחשב שלו.

הפתרון? נוסיף סקריפט postinstall ב-package.json שיגדיר את זה אוטומטית (מזל שאני בפרויקט של NodeJS):

{
  "scripts": {
    "postinstall": "git config --local blame.ignoreRevsFile .git-blame-ignore-revs || true"
  }
}

הסקריפט הזה ירוץ אוטומטית אחרי כל npm install, וידאג שהקונפיגורציה המקומית של Git תצביע על הקובץ שלנו.

ה-|| true בסוף מוודא שהסקריפט לא ייכשל אם משהו השתבש (למשל, אם מישהו הריץ npm install מחוץ לריפו של Git).

שלב 4: הגנה ב-CI

כדי למנוע מקוד לא מפורמט להיכנס לענף הראשי, נוסיף בדיקה לCI (למשל ב-GitHub Actions). למרות ששינינו את כל הקבצים, אני עדיין חושש לטעות ולהפריע למפתחים אחרים, וגם בגלל שהפרויקט שלנו די גדול, אז להריץ prettier על כולו לוקח זמן, אני משתמש בGitHub Action שיודע להריץ בדיקת prettier רק על הקבצים שהשתנו בPR, ככה אם בטעות יש קובץ לא מפורמט (כי מישהו למשל שינה קובץ דרך הUI ישירות בענף הראשי), הוא לא יחסום את כולם.

name: tests

on: [pull_request]

jobs:
  prettier:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          # Make sure the actual branch is checked out when running on pull requests
          ref: ${{ github.head_ref }}
          # This is important to fetch the changes to the previous commit
          fetch-depth: 0

      - name: Prettify code
        uses: creyD/prettier_action@v4.6
        with:
          dry: True
          only_changed: True

אבל תמיד אפשר פשוט להריץ prettier --check . וזה יבדוק את הקבצים, לא ישנה אותם, ויחזיר שגיאה אם הם לא עומדים בכללים.


לקחים מהשטח: מה קרה אחרי שבוע

שבוע אחרי שביצעתי את המהלך, אני חוזר עם לקחים לפעם הבאה:

1. יצירת Git Patch למפתחים אחרים

כמו שאמרנו, כדי להפחית הבדלים (diffs), כל אחד יצטרך להריץ אצלו מקומית את הפורמט, כדי שההבדלים בינו לבין הענף הראשי לא יכללו את השינויים של הפורמט. כדי ליצור אחידות ולהקל על השינוי, כדאי להכין patch עם הקבצים הרלונטיים (שינויים ב package.json, prettier.config.ts, .prettierignore), וכל אחד ייקח אליו את הקבצים הללו כדי שהפורמט יהיה אחיד.

git format-patch HEAD~1 --stdout > prettier-config.patch

המפתחים יוכלו להחיל את ה-Patch על הענפים שלהם:

git apply prettier-config.patch
npx prettier . --write

2. בעיית ה-Diff ב-GitHub

אז הכנסנו את השינוי לענף הבדיקות, וכשהגיע הזמן להכניס אותו לענף הראשי, איך שלא ניסינו לשחק עם הענפים, תמיד GitHub הראה לנו 800 קבצים שהשתנו. אחד החשדות שלי (אם כי לא הצלחתי להוכיח) הוא שGitHub באמת מתעלם מהקומיט של השינוי הגדול, כמו שרצינו, ולכן הוא לא לוקח אותו בחשבון ולא משווה מולו.

לכן ייתכן שכדאי לחכות עם הוספת .git-blame-ignore-revs כמה ימים, עד שמסיימים להגיע עם השינוי לכל מקום. ייתכן שהblame לא יהיה מושלם בינתיים, אבל כשנרגיש מוכנים נחזיר אותו להיות רלוונטי.

3. עדכון כל הענפים בהקדם

בגלל בעיית ה-Diff שהוזכרה, אני ממליץ להתרכז ולעדכן את כל הענפים בהקדם, ככה שגם אם GitHub מראה שינוי עצום, אנחנו יודעים שהשינוי הזה הוא פורמט בלבד, ומכיוון שהוא עוד טרי, אנחנו בטוחים בו.

למשל, אם עובדים עם Git Flow (ענפים main & dev), כדאי לעשות את השלבים הבאים:

  1. להכניס את dev לmain (ולהכניס את main. חזרה לdev אם יש לכם hotfixes)
  2. לנעול את dev
  3. לעשות את השינוי המדובר בdev
  4. להכניס שוב את dev לmain
  5. לפתוח את dev