ToCode
رفتن به کانال در Telegram
1 419
مشترکین
اطلاعاتی وجود ندارد24 ساعت
-27 روز
-530 روز
آرشیو پست ها
1 419
זהירות פלסטר
המתכון הוא פשוט-
1. כותבים קוד תשתית.
2. צריכים פיצ'ר או תיקון באג והקוד לא מסתדר עם התשתית.
3. מתקנים את התשתית.
4. בונים את הפיצ'ר עם התשתית החדשה.
מה שקורה בפועל הוא ששלב 3 הרבה יותר קשה מכל האחרים, לכן אנחנו מדביקים במקומו "פלסטר" שהוא תיקון זמני שעוקף את התשתית הקיימת. אנחנו לא חושבים על הפלסטר הזה בתור תשתית חדשה כי הוא פותר רק בעיה קטנה וספציפית בפיצ'ר שפשוט לא הצלחתי לבנות עם התשתית הקיימת. המחשבה היא "אף אחד אף פעם לא יצטרך שוב מעקף כזה, ולכן אין טעם לכתוב את הקוד גנרי מדי. ובכלל אולי אני פשוט לא מבין את התשתית הגאונית של האפליקציה וכן יש דרך שפשוט לא ראיתי בתוך התשתית לבנות את הפיצ'ר שרציתי, אז אני אכתוב את הפלסטר בינתיים וכשאהיה מספיק חכם או יהיה לי מספיק זמן אני כבר אעדכן את התשתית או אמצא את הדרך הנכונה לכתוב את זה".
את ההמשך של הסיפור אתם מכירים - הפלסטר הופך לפיתרון הקבוע, מועתק לעוד מקומות באפליקציה והופך לתשתית החדשה שאף אחד לא חשב עליה מראש.
אם אתם צריכים פיצ'ר שהתשתית לא מאפשרת הבעיה היא התשתית לא הפיצ'ר שלכם. קחו את הזמן לתקן את התשתית, עדיף לפני בניית הפלסטר, ובטוח לפני שהפלסטר משכפל את עצמו לשאר הקוד.
1 419
זהירות פלסתר
המתכון הוא פשוט-
1. כותבים קוד תשתית.
2. צריכים פיצ'ר או תיקון באג והקוד לא מסתדר עם התשתית.
3. מתקנים את התשתית.
4. בונים את הפיצ'ר עם התשתית החדשה.
מה שקורה בפועל הוא ששלב 3 הרבה יותר קשה מכל האחרים, לכן אנחנו מדביקים במקומו "פלסתר" שהוא תיקון זמני שעוקף את התשתית הקיימת. אנחנו לא חושבים על הפלסתר הזה בתור תשתית חדשה כי הוא פותר רק בעיה קטנה וספציפית בפיצ'ר שפשוט לא הצלחתי לבנות עם התשתית הקיימת. המחשבה היא "אף אחד אף פעם לא יצטרך שוב מעקף כזה, ולכן אין טעם לכתוב את הקוד גנרי מדי. ובכלל אולי אני פשוט לא מבין את התשתית הגאונית של האפליקציה וכן יש דרך שפשוט לא ראיתי בתוך התשתית לבנות את הפיצ'ר שרציתי, אז אני אכתוב את הפלסתר בינתיים וכשאהיה מספיק חכם או יהיה לי מספיק זמן אני כבר אעדכן את התשתית או אמצא את הדרך הנכונה לכתוב את זה".
את ההמשך של הסיפור אתם מכירים - הפלסתר הופך לפיתרון הקבוע, מועתק לעוד מקומות באפליקציה והופך לתשתית החדשה שאף אחד לא חשב עליה מראש.
אם אתם צריכים פיצ'ר שהתשתית לא מאפשרת הבעיה היא התשתית לא הפיצ'ר שלכם. קחו את הזמן לתקן את התשתית, עדיף לפני בניית הפלסתר, ובטוח לפני שהפלסתר משכפל את עצמו לשאר הקוד.
1 419
לא שוב טייפסקריפט
בואו נכתוב קומפוננטת Vue שיכולה להיות מופעלת בשני אופנים - או שאפשר להעביר לה כ Property מספר או שמעבירים לה שתי מחרוזות. אם מעבירים מספר אז המחרוזת הראשונה תהיה המספר שעבר והמחרוזת השנייה תהיה טקסט קבוע. ניסיון ראשון עשוי להיראות כך:
<script setup lang="ts">
const props = defineProps<
| {number: number}
| {text1: string, text2: string}>();
const text1 = "number" in props ? String(props.number) : props.text1;
const text2 = "number" in props ? "Great Number!" : props.text2;
</script>
<template>
<p>{{ text1 }}</p>
<p>{{ text2 }}</p>
</template>
והקריאה לקומפוננטה עשויה להיראות כך:
<Demo :number="5" />
<Demo text1="hello" text2="world" />
זה עבד מבחינת טייפסקריפט אבל הקוד עצמו נכשל. בהגדרת props עם defineProps ב Vue, אוביקט הפרופס תמיד מכיל את כל הפרופס האפשריים, כלומר בשתי ההפעלות הוא יקבל גם את number, גם את text1 וגם את text2, פשוט בהפעלה הראשונה number מקבל את הערך 5 והטקסטים יהיו undefined ובהפעלה השניה זה number שיהיה undefined והטקסטים מקבלים את הערכים הנכונים שלהם.
רעיון אחד לצאת מהסיפור עשוי להיות לעדכן את הקוד שמושך את הטקסטים מ props ולתקן את הבדיקה. הבעיה שזה לא ממש עובד:
const text1 = props.number ? String(props.number) : props.text1;
const text2 = props.number ? "Great Number!" : props.text2;
טייפסקריפט לא מאפשר לגשת ל props.number כי מבחינתו אולי אוביקט ה props לא כולל את number. מה שכן יעבוד הוא לעדכן את defineProps כך שכל אפשרות תכיל התיחסות לדברים באפשרות השניה אבל עם טיפוס never כדי שלא יעבירו ערכים למפתחות אלה:
const props = defineProps<
| {number: number, text1?: never, text2?: never}
| {number?: never, text1: string, text2: string}>();
const text1 = props.number ? String(props.number) : props.text1;
const text2 = props.number ? "Great Number!" : props.text2;
הקוד הזה עובד ובנוסף יש לנו בדיקת טייפסקריפט טובה. שתי השורות האלה תקינות:
<Demo :number="5" />
<Demo text1="hello" text2="world" />
ושלושת אלה מראות פס אדום על הקומפוננטה כדי שנזהה הפעלה לא נכונה:
<Demo :number="5" text1="hello" text2="world" />
<Demo text1="hello" />
<Demo />1 419
הבעיה עם עצות גרועות
הבעיה עם עצות גרועות היא שהן גרועות. טוב תכלס זאת הבעיה היותר קלה איתן.
עצות גרועות באמת הן כאלה שקשה לזהות שהן גרועות. וזאת בעיה הרבה יותר גדולה, כי רוב הזמן (בבעיות מעניינות) גם לעצות טובות לוקח זמן לעבוד. עצות גרועות נראות כמו עצות טובות, אבל עד שאתה מבין שעבדו עליך זה כבר הרבה יותר קשה לתקן. הנה כמה מאפיינים של עצות גרועות שמתחפשות לטובות:
1. עצה גרועה תשתמש בז'ארגון לא מובן ותנצל פערי ידע בין היועץ למקבל העצה.
2. עצה גרועה הרבה פעמים כן תפתור בעיה, פשוט לא את הבעיה שלך (זה מה שגורם לה להיות מבלבלת).
3. עצה גרועה תתמקד בתוצאה ולא בתהליך, ולא תאפשר לך להבין בזמן שאתה בדרך הלא נכונה.
בואו נחבר את זה רגע לדוגמה. נניח שיש לי בעיה של איטיות ב DB ואני לא מבין למה שליפות מסוימות חוזרות לאט. אני מצלצל ליועץ מומחה לבסיסי נתונים והוא מתחיל להסביר לי על אינדקסים וכמה הם חשובים, וגם מוסיף המון מידע טכני על סוגי האינדקסים ובסוף משכנע אותי לארגן מחדש את הטבלאות והאינדקסים כי רק ככה בעיית הביצועים תיפטר. הוא יוכל גם להפנות אותי ללקוחות אחרים שלו שעשו תהליך דומה ובסוף קיבלו שאילתות מהירות יותר. הפיתרון באמת פותר בעיה אבל לא בטוח שאת הבעיה שלי.
יועץ טוב יותר ייכנס איתי לקוד שלי ויראה לי איך לנתח מה בסיס נתונים עושה כשמריצים שליפה, איזה אינדקסים נמצאים בשימוש ואיזה לא, ומתוך זה יראה לי ממש מה גורם לאיטיות. הדגש פה הוא לתת לי את הכלים להבין את הבעיה ולהתמודד איתה, ולעזור לי כשאני נתקע, במקום לתת לי פיתרון שעבד במקומות אחרים אבל לא בטוח שיעבוד עבורי (ואפילו אם יעבוד לא בטוח אם ימשיך לעבוד עבורי בפעם הבאה שאתקל בבעיה).
הבעיה עם עצות גרועות היא שעצות גרועות הן פשוט עצות טובות שיצאו מקונטקסט.
1 419
באמת צריך לדעת לענות על השאלה הזאת?
יש המון בעיות שאפשר לפתור גם בלי להבין כל שורת קוד בפרויקט.
ואפילו לא מעט בעיות שאפשר לפתור בלי להבין את שורות הקוד הרלוונטיות לבעיה.
ויש מספיק שאלות שיהיה מאוד מעניין ולחלוטין לא מועיל לגלות את התשובה להן. כמה עמוק שווה לצלול בקוד של ספריה חיצונית רק בשביל למצוא באג בקוד שלה, כשאפשר במקום לשדרג גירסה של הספריה החיצונית ולהתקדם עם הפרויקט? לא חבל על הזמן שלנו?
איך נדע כמה עמוק צריך לחפור בתקלה מסוימת כדי להבין אותה עד הסוף? זה תלוי בפרויקט אבל ככלל זה סוג השאלות שלא הייתי מוותר על למצוא להן תשובה במערכות Full Stack:
1. מה מחובר למה? מאיפה מגיע המידע ולאן הוא הולך? איפה המידע נשמר?
2. למה אוביקט מסוים נשלח ברשת? למה תוכן מסוים חוזר? איזה חלק בתוכנה אחראי לכל חלק ב HTML?
3. מה קורה כשאני לוחץ על כפתור או מבצע אירוע אחר? איזה חלקים בקוד לוקחים חלק בטיפול באירוע.
4. מה הספריות החיצוניות בהן אני משתמש? מה תפקיד כל ספריה? באיזה גירסה כל ספריה? מתי היא שודרגה לאחרונה? מה הגירסה העדכנית ביותר שלה? מי מתחזק את הספריה החיצונית הזאת?
5. איזה שרתים יש לי במערכת? מה קורה אם אחד מהם מפסיק לעבוד? מה התוכנות שרצות על כל שרת? מה תפקיד כל תוכנה?
עכשיו אתם- מתחזקים מערכת Full Stack? על איזה שאלות לא תוותרו לעצמכם ותתאמצו למצוא את התשובות?
1 419
עשר שנים של פרונטאנד
אפאל שה כתב על הפרויקטים המובילים ב JavaScript לפני 10 שנים ואיפה הם היום. פוסט מרתק ומלא נוסטלגיה ואני ממליץ לקרוא אותו. בקצרה הוא מחלק את הפרויקטים לנושאים הבאים:
1. פרונטאנד
2. בקאנד
3. באנדלרים
4. מריצי משימות
5. כלי בדיקות
אני לא יודע למה הוא בחר בחלוקה הזאת, אבל כן רוצה להוסיף על הניתוח המעולה שלו כמה מילים על מה השתנה בכל נושא.
פרונטאנד
לפני 10 שנים SPA היו הטרנד הגדול. מאז למדנו שהם לא כאלה טובים כי לא לכולם יש JavaScript ומנועי חיפוש לא הכי מרוצים מהם ובכלל למדנו לאהוב את הדפדפן. התוצאה של השיעור הזה היתה סיבוב חוזר של המטוטלת לכיוון של פריימוורקים של Full Stack. היום מאוד נפוץ למצוא את צוות הפרונטאנד מתחזק יישום next.js.
מעניין לשים לב שלמרות שהפריימוורקים האלה רצים בצד שרת הם לא מחליפים את קוד צד השרת הרגיל. בהרבה חברות אני רואה תהליך של מעבר מיישום SPA שכתוב ב JavaScript בלבד ומתחבר ל API, ליישום Next.js או רמיקס שמושך מידע מ API מבצע רינדור בצד שרת ומחזיר קוד ריאקט ללקוח. צוות בקאנד ממשיך לתחזק את מכונת "צד השרת" וצוות הפרונט אנד הפך בעצם לצוות Full Stack ובונה קוד ב Next.js.
לאן זה הולך? מבחינת פריימוורק אין ספק שריאקט היא השורדת החזקה והוותיקה ביותר. מצד אחד קשה לראות את הסוף של ריאקט באקוסיסטם הנוכחי ומצד שני דברים כאלה מגיעים במכה אחת. ריאקט היא היום מסובכת מדי ורק הופכת יותר מסובכת עם כל גירסה חדשה, ודברים כאלה לא מחזיקים לאורך זמן.
בקאנד
למרות הרבה ניסיונות לחדש בתחום הזה בסוף אנשים חוזרים לאקספרס כי אנחנו מעדיפים את המוכר במיוחד כשהוא לא מספיק גרוע. גרף קיו אל לא הצליח לאיים על המודל של REST וגם לא דברים כמו tRPC. בסוף יש משהו נוח ויפה בשרת שפשוט מחזיר JSON-ים.
לאן זה הולך? קשה לדעת. אולי נתחיל לכתוב שרתים ב Rust, אולי Go יתחזק שוב, ואולי נישאר עם אקספרס עוד 10 שנים.
באנדלרים
וואו כמה השתנה פה וכמה עוד הולך להשתנות. לפני עשר שנים RequireJS עוד היה דבר, וובפאק היה חדש ומהפכני אבל יותר מהכלים הסיפור הגדול של הבאנדלרים זה השינויים בפלטפורמה. לפני 10 שנים היינו צריכים להמציא מנגנונים שיאפשרו לכתוב JavaScript בקבצים שונים ואז לחבר את כל הקבצים האלה לקובץ JS אחד לשלוח לדפדפן כי דפדפנים לא יכלו להוריד הרבה קבצים במקביל מאותו דומיין. מאז ES Modules הפך לסטנדרט ודפדפנים (וגם node.js) יודעים לקרוא פקודות import ו export באופן טבעי, ה JavaScript נהיה הרבה יותר גדול ופתאום לחבר הכל לקובץ אחד זה רעיון טפשי כי הקובץ גדול מדי ומאט את זמן הטעינה של העמוד.
היום המטרה העיקרית של באנדלרים היא לתמוך ב FrontEnd Frameworks שאנחנו בוחרים, לדוגמה לקמפל את ה JSX של ריאקט או את קבצי ה Vue או ה TypeScript לקוד JavaScript שדפדפן יוכל להבין. קשה לראות אותם נעלמים אבל ככל שהם צריכים לעשות פחות הם הופכים להרבה פחות מעניינים.
לאן זה הולך? בעתיד הנראה לעין יהיו פרויקטים שיחליפו את הבאנלדר ב Import Maps. כן לאט בהתחלה ואז יותר מהם. לדעתי גם נתחיל לראות Front End Frameworks שתומכות ב Import Maps ולא דורשות Build Step ויהיה מי שיתעניין בזה (DHH כבר מדבר על גישה כזאת בריילס, אני חושב שיהיו עוד). מצד שני זה לא מה שיחסל את ריאקט וחבריו ולכן באנדלרים ימשיכו ללוות אותנו לפחות בתור עוד רכיב בתוך ה Full Stack Framework שנבחר.
מריצי משימות
כן פעם היו דברים בשם grunt ו gulp וכן השתמשנו בהם כדי להריץ את הבאנדלר. ואז הבאנדלר נהיה מספיק טוב ועברנו להשתמש ב npm כך שלא היה בהם יותר צורך. יש אנשים שחוזרים ל Makefile-ים, יש את ג'אסט שגם כתבתי עליו ואני חושב שהוא אחלה אבל יותר מהכל יש npm scripts ו Github Actions.
לאן זה הולך? קשה לי לראות את התחום של מריצי משימות מתרומם שוב. זה היה רעיון יפה לתקופה אבל כבר אין בו צורך.
כלי בדיקות
לפני 10 שנים מוקה וג'סמין היו בחירות פופולריות. היום שתיהן עדיין עובדות אבל רבים מעדיפים את Jest, אבל האמת העצובה היא שלפני 10 שנים לא כתבנו המון בדיקות והיום אנחנו עדיין לא כותבים המון בדיקות, כך שפחות משנה הכלי.
לאן זה הולך? אולי AI יתחיל לכתוב בדיקות בשבילנו. כנראה שלא.
1 419
ומגלה שכל לחיצה על כל משבצת גורמת לקומפוננטה להתעדכן. בגלל שהקומפוננטה גדולה עדכון שלה אומר להריץ מחדש את שתי הלולאות.
בנו קומפוננטה בשם Square שאחראית על הצגת תוכן משבצת בודדת, וקומפוננטה נוספת בשם Player שאחראית על הצגת השחקן הנוכחי - וגם בשתיהן הוסיפו את קטע הקוד שמקשיב ל updated ומדפיס כשיש עדכון לקומפוננטה.
עכשיו מצאו דרך לעדכן את הקוד כך שלחיצה על משבצת תגרום לעדכון רק של הקומפוננטות Square ו Player אבל לא לעדכון של הקומפוננטה הראשית TicTacToeGame.
1 419
דוגמת ויו: משחק איקס עיגול
לפני כמה שנים כתבתי מדריך על Vue ואחד הדברים שאז לא עבדו מספיק טוב היה מנגנון הריאקטיביות. רוב הזמן זה היה בסדר אבל היה יחסית קל להגיע לקצוות מבלבלים. לאחרונה אני מרענן את החומרים על Vue לקראת קורס שאני מעביר בנושא ושמח לגלות שהריאקטיביות עובדת הרבה יותר טוב היום. הנה דוגמה קצרה לפיתוח משחק איקס עיגול ריאקטיבי ב Vue, עם כל הפינוקים של הפרדה בין קובץ לוגיקה לקומפוננטות.
לוגיקת המשחק
הדרך הסטנדרטית ב Vue לתקשר בין לוגיקה לקומפוננטות היא ריאקטיביות. הקסם של Vue (בניגוד לריאקט), הוא שאפשר ליצור משתנים ריאקטיביים בכל מקום ולא רק בתוך קומפוננטות וכך אפשר לבנות לוגיקה שכוללת State מחוץ לקומפוננטות. בדוגמה שלנו זה הקוד של משחק איקס עיגול שמחזיק סטייט ריאקטיבי:
import {ref, Ref} from 'vue';
export default class ReactiveTicTacToe {
board: Ref<Array<Array<'O'|'X'|'.'>>>;
winner: Ref<undefined|'O'|'X'>;
currentPlayer: Ref<'O'|'X'>;
constructor() {
this.board = ref([
['.', '.', '.'],
['.', '.', '.'],
['.', '.', '.']
]);
this.currentPlayer = ref('X');
this.winner = ref();
}
play(row: number, column: number) {
if ((this.winner.value) || (this.board.value[row][column] !== '.')) {
return;
}
this.board.value[row][column] = this.currentPlayer.value;
if (this.checkWinner(this.currentPlayer.value)) {
this.winner.value = this.currentPlayer.value;
}
this.currentPlayer.value = this.nextPlayer();
}
nextPlayer() {
if (this.currentPlayer.value === 'X') {
return 'O'
} else {
return 'X'
}
}
checkWinner(player: 'X' | 'O') {
const winningCoordinates = [
[[0, 0], [0, 1], [0, 2]],
[[1, 0], [1, 1], [1, 2]],
[[2, 0], [2, 1], [2, 2]],
[[0, 0], [1, 0], [2, 0]],
[[0, 1], [1, 1], [2, 1]],
[[0, 2], [1, 2], [2, 2]],
[[0, 0], [1, 1], [2, 2]],
[[0, 2], [1, 1], [2, 0]]
]
if (winningCoordinates.some(triplet =>
triplet.every(([row, column]) => (
this.board.value[row][column] === player
))
)) {
return true;
}
return false;
}
}
זה קוד שקל לעבוד איתו, קל לבדוק אותו בבדיקות יחידה או לשלב אותו יחד עם קבצי לוגיקה אחרים. אפשר להגיד שקוד כזה היה כל מה שתמיד רציתי מ mobx, אבל דברים שם אף פעם לא עבדו מספיק טוב.
הקומפוננטה
ויו מעודד כתיבת לוגיקה פשוטה של קומפוננטה בתוך סקריפט האיתחול שלה, אבל כמובן שדברים יותר מורכבים עדיף לכתוב בקובץ נפרד. אחרי שכתבנו את קובץ הלוגיקה נוכל להשתמש בו מתוך קומפוננטה באופן הבא:
<script setup lang="ts">
import ReactiveTicTacToe from '../game/logic';
const {game} = defineProps<{game: ReactiveTicTacToe}>()
</script>
<template>
<div>
<h1 v-if="game.winner.value">Bravo! {{ game.winner }} won</h1>
<h1 v-else>Next player = {{ game.currentPlayer }}</h1>
<div v-for="(_, row) in 3" :style="{display: 'flex'}">
<div v-for="(_, column) in 3" :style="{flex: 1, cursor: 'pointer'}">
<div
@click="game.play(row, column)"
>
{{ game.board.value[row][column] }}
</div>
</div>
</div>
</div>
</template>
ובטח שכיף להשתמש בקיצורי הדרך של vue במיוחד באפשרות של לולאה על מספר בתוך ה v-for ובאפשרות לרשום ב @click משהו שנראה כמו הפעלה של פונקציה ו vue כבר יהפוך את זה לפונקציה.
לסיום בשביל להשתמש במשחק נוכל להפעיל אותו באופן הבא:
<script setup lang="ts">
import TicTacToeGame from './components/TicTacToeGame.vue';
import ReactiveTicTacToe from './game/logic';
</script>
<template>
<TicTacToeGame :game="new ReactiveTicTacToe()"/>
<TicTacToeGame :game="new ReactiveTicTacToe()"/>
<TicTacToeGame :game="new ReactiveTicTacToe()"/>
</template>
עכשיו אתם
ולסיום תרגיל לאלה מכם שרוצים לצלול קצת יותר לעומק הריאקטיביות ב Vue. אני מוסיף את הקוד הבא ל script setup של הקומפוננטה TicTacToeGame.vue:
onUpdated(() => {
console.count(\TicTacToeGame::updated\);
})1 419
איזה סוג של מכוער
כשאנחנו אומרים שמצאנו פיתרון מכוער לבעיה אנחנו מתכוונים שאנחנו לא מאוד מרוצים מהפיתרון, אבל עוד לא אמרנו מה בדיוק אנחנו לא אוהבים. כל אחת מהאפשרויות האלה יכולות לקבל את התווית "פיתרון מכוער":
1. מצאתי באינטרנט דרך שפותרת את הבעיה והעתקתי בלי להבין.
2. כתבתי קוד שפותר את הבעיה בכמה מקומות שונים, אבל לא הצלחתי למצוא דרך לאחד אותם למקום אחד.
3. היו לי כמה רעיונות איך לפתור את הבעיה ובחרתי בפיתרון שפוגע בביצועים של המערכת כי היה יותר קל לממש אותו.
4. הפיתרון שלי מטפל רק ב 90% מהמקרים.
5. פתרתי את הבעיה בצורה שיש סיכוי טוב שתישבר כשיהיו עדכונים לפלטפורמה או לספריות אחרות.
6. פתרתי את הבעיה בצורה שתפריע לכל הוספת פיצ'ר חדש למערכת.
7. פתרתי את הבעיה בצורה שמוסיפה אילוץ חדש למערכת שלא היה שם קודם.
כשאנחנו ברורים לגבי הבעיות בפיתרונות שלנו הרבה יותר קל לנו להתקדם לפיתרונות טובים יותר.
1 419
<meta name="description" content="Generated by create next app" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
</Head>
<div
className={\${styles.page} ${geistSans.variable} ${geistMono.variable}\}
>
<main className={styles.main}>
<p>{props.one}</p>
<p>{props.two}</p>
</main>
</div>
</>
);
}
הפעם אלה הצ'אנקים שנוצרו בבנייה:
ynonp@Ynons-MacBook-Air ~/tmp/perf (main*?) $ ls -l .next/static/chunks
total 816
-rw-r--r-- 1 ynonp staff 181258 נוב 4 19:46 framework-88d0cc4abe8a5763.js
-rw-r--r-- 1 ynonp staff 113610 נוב 4 19:46 main-22f485a16e76a198.js
drwxr-xr-x 5 ynonp staff 160 נוב 4 19:46 pages
-rw-r--r-- 1 ynonp staff 112594 נוב 4 19:46 polyfills-42372ed130431b0a.js
-rw-r--r-- 1 ynonp staff 1459 נוב 4 19:46 webpack-59041051e025bed2.js
ynonp@Ynons-MacBook-Air ~/tmp/perf (main*?) $ ls -l .next/static/chunks/pages
total 24
-rw-r--r-- 1 ynonp staff 400 נוב 4 19:46 _app-f45d290ef4eb0435.js
-rw-r--r-- 1 ynonp staff 232 נוב 4 19:46 _error-8c2b6ff87cd513a2.js
-rw-r--r-- 1 ynonp staff 1480 נוב 4 19:46 index-f051a589b07bc05e.js
הפעם אף קובץ בתיקיית chunks לא כולל את ספריית moment או את ספריית lodash. ספריות אלה נטענות ונמצאות בשימוש רק בצד השרת, והגודל הכולל של כל ה JavaScript שנשלח לדפדפן הוא כ 20% פחות.
שורה תחתונה בעבודה עם next גם בגירסת ה Pages Router אנחנו יכולים להוריד משמעותית את כמות ה JavaScript שנשלחת לדפדפן באמצעות העברת הקוד לצד שרת, או להשתמש נכון יותר ב Cache באמצעות העברת הספריות לקבצים של הדפים. הקובץ app צריך להיות ממש המוצא האחרון כיוון שקוד שאני כותב בו יישלח לכל הדפים במרוכז.1 419
טיפ נקסט: שימו לב ל import-ים
ב next.js בגירסה ה Page Router יש לנו שלוש אפשרויות לטעון ספריות חיצוניות, ואני חושש שההבדל ביניהן לא תמיד מספיק ברור.
שימוש בספריה חיצונית בקובץ נפרד
דרך ראשונה היא לקרוא ל import מתוך קובץ של Page, לדוגמה בפרויקט next חדש אני כותב בקובץ index.tsx את הקוד הבא:
import _ from 'lodash';
import moment from 'moment';
// export const getServerSideProps = (async () => {
// const _ = (await import('lodash')).default;
// return { props: { name: foo(), number: _.random(100) } }
// });
export default function Home(props: any) {
return (
<>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
</Head>
<div
className={\${styles.page} ${geistSans.variable} ${geistMono.variable}\}
>
<main className={styles.main}>
<p>Hello {_.head([1, 2, 3])}</p>
<p>{moment("20111031", "YYYYMMDD").fromNow()}</p>
</main>
</div>
</>
);
}
בבנייה של הקוד עם npm run build אני מקבל בתיקיית chunks את:
$ ls -1 .next/static/chunks
29107295-e679c2f4ae05922d.js
75fc9c18-25d3a9414ec027b7.js
framework-88d0cc4abe8a5763.js
main-22f485a16e76a198.js
pages
polyfills-42372ed130431b0a.js
webpack-69db33922baa0ba7.js
שימו לב לשני הקבצים ששמותיהם הם מספרים, אלה הספריות lodash ו moment. הם נכנסו לפרויקט בתור קבצים נפרדים, וזה נחמד כי דפדפן יכול לשמור כל אחד מהם בנפרד ב cache, ולטעון אותם רק מהדפים שבאמת צריכים אותם.
שימוש בספריה חיצונית מתוך app
בפרויקט next יש גם קובץ כללי בשם _app.tsx. ספריות שאנחנו טוענים ממנו יאוחדו לקובץ אחד שייטען בכל אחד מהדפים ביישום. אני מחזיר את index.tsx למצבו ההתחלתי ומעדכן הפעם את _app.tsx לתוכן הבא:
import "@/styles/globals.css";
import type { AppProps } from "next/app";
import _ from 'lodash';
import moment from 'moment';
export default function App({ Component, pageProps }: AppProps) {
return <div>
<p>Hello {_.head([1, 2, 3])}</p>
<p>{moment("20111031", "YYYYMMDD").fromNow()}</p>
<Component {...pageProps} />;
</div>
}
מבחינת העמוד דברים נראים אותו דבר יש לי את אותם טקסטים בדיוק (כן במקום אחר אבל זה לא חשוב כרגע), אבל מה שכן חשוב זה ה JavaScript שנוצר:
$ ls -l .next/static/chunks
total 816
-rw-r--r-- 1 ynonp staff 181258 נוב 4 19:42 framework-88d0cc4abe8a5763.js
-rw-r--r-- 1 ynonp staff 113610 נוב 4 19:42 main-22f485a16e76a198.js
drwxr-xr-x 5 ynonp staff 160 נוב 4 19:42 pages
-rw-r--r-- 1 ynonp staff 112594 נוב 4 19:42 polyfills-42372ed130431b0a.js
-rw-r--r-- 1 ynonp staff 1553 נוב 4 19:42 webpack-69db33922baa0ba7.js
הפעם אין צ'אנקים עבור הספריות החיצוניות ובמקומים בתוך תיקיית pages אני מקבל:
$ ls -l .next/static/chunks/pages
total 272
-rw-r--r-- 1 ynonp staff 129509 נוב 4 19:42 _app-852f41acee199ae3.js
-rw-r--r-- 1 ynonp staff 232 נוב 4 19:42 _error-8c2b6ff87cd513a2.js
-rw-r--r-- 1 ynonp staff 1385 נוב 4 19:42 index-de2181fd4b2adca1.js
כלומר קובץ אחד בשם _app-852f41acee199ae3.js שמכיל את שתי הספריות. הקובץ הזה ייטען בכל אחד מהדפים ביישום ודפדפן לא יוכל לשמור אותו ב cache, כי כל שינוי באחד המרכיבים שלו יגרום ליצירה מחדש של כל הקובץ.
שימוש בספריה חיצונית רק מתוך קוד צד שרת
אופציה שלישית היא לטעון את הספריה החיצונית ולהשתמש בה רק בפונקציה getServerSideProps מתוך אחד הדפים. אני מחזיר את _app.tsx לצורתו המקורית וחוזר ל index.tsx ועכשיו זה יהיה תוכנו:
import _ from 'lodash';
import moment from 'moment';
export const getServerSideProps = (async () => {
return {
props: {
one: _.head([1, 2, 3]),
two: moment("20111031", "YYYYMMDD").fromNow(),
}
}
});
export default function Home(props: any) {
return (
<>
<Head>
<title>Create Next App</title>
اکنون در دسترس! پژوهش تلگرام ۲۰۲۵ — مهمترین بینشهای سال 
