הבלוג של ינון פרק

טיפים קצרים וחדשות למתכנתים

גם מבנה הפרויקט הוא תלות

23/03/2022

בין כל הפעמים שאנחנו מריצים npx npm-check-updates או bundle update או אפילו pip install -U, יש דבר אחד שנוטים לשכוח - גם מבנה הפרויקט הוא תלות, וגם אותו אפשר לשדרג:

  1. אולי יש לכם מערכת Build מאוד מתוחכמת שבניתם לבד ב gulp בימים שהוא עוד היה אופנתי, והיא עדיין עובדת אבל כבר אי אפשר לשנות בה כלום.

  2. אולי יש לכם סט בדיקות ממש מוצלח שעדיין כתוב בג'סמין ומורץ עם karma.

  3. אולי יש לכם סקריפט שפשוט בונה את הפרויקט על מכונת הפיתוח ומעתיק לשרת את הקבצים עם rsync.

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

  1. היום יש דרכים יותר טובות לעשות את מה שלפני שנתיים היה קשה. בבניה של פרויקט ווב אולי כבר לא צריך לטרנספל לתמיכה ב IE6. אולי כבר אפשר להשתמש ב HTTP/2 Server Push כדי לשפר ביצועים.

  2. לאינטרנט יש נטיה להפוך מידע עדכני לזמין יותר, לפחות במה שנוגע לשאלות טכניות. לכן יהיה לי יותר קל למצוא טיפים על ג'סט מאשר על ג'סמין, ועל webpack במקום על gulp.

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

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

עלות שקועה

22/03/2022

עד גירסה 6 של ריילס לא היתה דרך להריץ בדיקות בצורה מקבילית. לא היתה דרך זו מילה גדולה, גיטהאב כתבו אינסוף קוד כדי לעקוף את הפריימוורק ולהריץ את הבדיקות בצורה מקבילית, אבל זה היה גיטהאב.

בסוף נמאס להם לעקוף מגבלות של הפריימוורק ובגירסה 6 של ריילס הם כבר תרמו את כל המנגנון המתוחכם שלהם.

עכשיו בואו נחזור אליכם ונניח שגם אתם, כמו גיטהאב, בניתם מנגנון סופר מתוחכם להרצת בדיקות בריילס בצורה מקבילית. מה עושים כשריילס יוצאים עם גירסה 6 ומציעים הרצת בדיקות במקביל built in? האם אתם זורקים את כל מה שכתבתם ועוברים לפיתרון הסטנדרטי?

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

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

ועכשיו יש לנו החלטה חדשה לקבל-

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

האם הייתי מעדיף פיתרון שהושקעו בו שבועיים ועדיין לא נוסה בפרודקשן, או פיתרון שהושקעו בו שנים ויש לו המון משתמשים פעילים?

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

טיפ CSS: המאפיין display: contents

שני מאפייני ה Layout המתקדמים של CSS, פלקסבוקס וגריד, כוללים מגבלה קצת מעצבנת: הם מכילים את הגדרות העיצוב על הילדים הבלוקים הישירים שלהם.

במילים אחרות אם יש לי פס עליון באתר ובו 6 לינקים, וה HTML שלי נראה ככה:

<div class="topbar">
    <a href="#">link #1</a>
    <a href="#">link #2</a>
    <a href="#">link #3</a>
    <a href="#">link #4</a>
    <a href="#">link #5</a>
    <a href="#">link #6</a>
</div>

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

.topbar {
  display: flex;
}

.topbar a {
  padding: 0.2rem;
  flex: 1;
}

אבל אם במקרה החלטתי לחלק את הלינקים ב HTML למספר "בלוקים", רק מבחינה סמנטית או כי היה לי יותר נוח לייצר HTML כזה:

<div class="topbar">
   <div class="part1">
    <a href="#">link #1</a>
    <a href="#">link #2</a>
    <a href="#">link #3</a>
  </div>

  <div class="part2">
    <a href="#">link #4</a>
    <a href="#">link #5</a>
  </div>

  <div class="part3">
    <a href="#">link #6</a>
  </div>
</div>

אז פתאום כל הפלקסבוקס שלי נשבר. יותר מדויק להגיד שהוא לא נשבר פשוט האלמנטים שמסודרים בפלקסבוקס הם עכשיו הדיבים part1, part2 ו part3, ולא הלינקים המקוריים.

הערך contents למאפיין display, שנתמך ברוב הדפדפנים (חוץ מ IE כמובן), מספק דרך קלה "לדלג" על אלמנטים כשמסדרים אותם בתוך פלקסבוקס או גריד. ה CSS הבא יגרום גם ל HTML השני להציג את הלינקים בצורה יפה בתוך פלקסבוקס:

.topbar {
  display: flex;
}

.topbar > div {
  display: contents;
}

.topbar a {
  padding: 0.2rem;
  flex: 1;
}

וככה זה נראה לייב בקודפן:

מערכת ניהול החבילות של דינו

20/03/2022

אם יש משהו שקל למתכנתי Node להסכים עליו זה ש npm שבור. הבעיות המרכזיות בו הן:

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

  2. הגדרה "גמישה" של גירסאות ב package.json וקובץ package-lock.json שחצי מהאנשים מוחקים אותו כל פעם שיש קונפליקט.

  3. תלות במאגר מרכזי לכל בניה (היוש left-pad)

  4. התקנת חבילות שכוללות אינסוף זבל בנוסף לקבצים שרצית, ורק מנפחות את node_modules (היוש readline)

דינו, הניסיון השני של ריאן דל ליצור סביבת ריצה ל JavaScript מחוץ לדפדפן, מתמודד עם ניהול חבילות חיצוניות בצורה מובנית, מבוזרת ודי מעניינת.

דינו משתמש ב import ולא ב require, אבל השוס הגדול הוא שבמקום להעביר ל import שם של חבילה אנחנו מעבירים את ה URL המלא אליה. כלומר בשביל להשתמש ב lodash בתוכנית דינו אני אכתוב:

import * as _ from 'https://cdn.skypack.dev/-/lodash-es@v4.17.21-rDGl8YjBUjcrrAbjNrmo/dist=es2019,mode=imports/optimized/lodash-es.js';

const squares = _.map(_.range(10), (x) => x * x);
console.log(squares);

וזה כבר פותר חלק גדול מהבעיות של npm:

  1. בגלל שאני מעביר URL, לא צריך ללכת מכות על שמות.

  2. הגדרת הגירסה היא חלק מה URL והיא מאוד ספציפית, כמו בתגיות ה script שהיו לנו פעם בקבצי HTML, לפני שעברנו לוובפאק. וגם אין ולא צריך package.json. גם לא צריך ללמוד את ההבדל בין devDependencies ל peerDependencies ול dependencies רגילים. מה שעושים לו import נטען.

  3. לא צריך מאגר מרכזי.

  4. מורידים רק מה שמשתמשים בו ולא חבילה שלמה

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

$ deno cache --lock=lock.json --lock-write -r demo.js

וזה ייצור לי קובץ בשם lock.json על המחשב עם התוכן:

{
  "https://cdn.skypack.dev/-/lodash-es@v4.17.21-rDGl8YjBUjcrrAbjNrmo/dist=es2019,mode=imports/optimized/lodash-es.js": "4a1b35d34b5e0d3ae68aa46ddaabb9700ccecb75b830974db7c46a271f10daa8"
}

עכשיו כשאני אעביר את התוכנית למכונה אחרת אני מעביר איתה את קובץ ה lock.json שיצרתי, ומפעיל שם:

$ deno cache --lock=lock.json  -r 03-with-lodash.js

ודינו יוריד את כל החבילות החיצוניות וישווה את ה hash-ים שלהן מול מה שכתוב בקובץ המנעול. אחרי זה בדרך כלל נריץ עם:

$ deno run --lock=lock.json --cached-only  03-with-lodash.js

וזה מוודא שכל החבילות נטענות רק מה cache ושום דבר חדש לא יורד מהרשת.

האם זה יתפוס? זאת השאלה הגדולה. במעבר לדינו נצטרך להתרגל לחפש שוב URL-ים מלאים ב cdn-ים במקום פשוט להתקין עם npm install ושם קצר של חבילה. לאורך זמן הגישה של דינו נשמעת לי יותר יציבה, אבל יהיה קשה להיפרד מ npm install lodash.

אפשר לראות את ה vimrc שלך?

19/03/2022
vim

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

כשאנחנו קוראים קבצי הגדרות כאלה של אחרים יש לנו שתי אפשרויות-

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

  2. אנחנו יכולים להתחיל מאפס, לעבור על קבצי ה vimrc שאנחנו מוצאים ברשת כשיש זמן וכל פעם שמוצאים משהו שאנחנו אוהבים ומבינים להעתיק אותו אלינו.

השיטה השניה תביא תוצאות מהר יותר. השניה את התוצאות הטובות יותר.

לא אותה מטרה

18/03/2022

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

כמה דוגמאות ברשותכם-

המשך קריאה

שימו לב: נוד קורא גם את ה gitignore שלכם כשמפרסמים חבילות

17/03/2022

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

מפה לשם אני מנסה לבנות את החבילה ולפבלש, ובחלון אחר מפעיל npm install כדי לראות שזה עובד.

החבילה עצמה היא פרויקט צד לקוח שבניה שלו מייצרת תיקיה בשם dist, ובחלון השני שמשתמש בחבילה יש קוד שעושה import מאותה dist.

בקיצור בחזרה לבניה מפעיל publish בחלון אחד ו install בחלון השני ו... אין dist בספריה שהותקנה. נו, בטח שכחתי להפעיל build בין כל הילדים שקופצים פה מסביב.

ניסיון שני בבניה, שוב publish, שוב install ו... dist עדיין לא שם.

בסופו של דבר ברחתי מהילדים למקום שקט, הסתכלתי בשקט בספריה ומצאתי שם קובץ .gitignore עם השורה:

dist/

גיט לוג חשף שהיתה כוונה טובה מאחורי השורה הזאת - אין צורך להוסיף את כל תוצרי הבניה ל git. אבל היא באה עם באג: בלי קובץ .npmignore בתיקיה, באופן אוטומטי npm יתעלם מהקבצים ב .gitignore ולא יפרסם אותם עם החבילה שלכם.

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

טיפ טייפסקריפט: הפקודות Parameters ו ReturnType

16/03/2022

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

עדיין כללי מדי? בואו ננסה דוגמה ספציפית עם טייפסקריפט וריאקט.

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

function setTextAndCount(stuff) {
    setText(stuff);
    setCount(c => c + 1);
}

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

בתרגום ל TypeScript הפשטות הולכת לאיבוד. פתאום אני צריך לספר לטייפסקריפט מה סוג המשתנה שאני צריך לקבל, ובשביל זה צריך לדעת מה סוג המשתנה בסטייט ולהגדיר Union Type שמורכב מהמשתנה בסטייט או מפונקציית העדכון שלו. לא נעים.

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

function setTextAndCount(
  ...args: Parameters<typeof setText>
): ReturnType<typeof setText> {
  setText(...args);
  setCount((c) => c + 1);
}

דוגמת קוד מלאה? בטח למה לא: https://codesandbox.io/s/restless-night-u4ukmn?file=/src/App.tsx:186-347

טיפ דוקר: סדר פעולות ב Dockerfile

14/03/2022

קובץ ה Dockerfile הבא מגדיר בניה של אפליקציית Rails שגם מכילה קוד צד לקוח, שבתורו מתקמפל עם node.js:

FROM ruby:2.7

WORKDIR /app
COPY . .

ENV RAILS_ENV=production
ENV NODE_ENV=production

RUN gem install bundler:2.2.5
RUN sh -c "apt-get update && apt-get install -y nodejs npm"

RUN bundle install

RUN sh -c "cd client; npm install"
RUN "./bin/rails assets:precompile"

CMD ["./bin/rails", "s"]

רואים מה הבעיה כאן?

המשך קריאה