1 419
订阅者
无数据24 小时
无数据7 天
-530 天
帖子存档
1 419
אז OpenAI עברו לרמיקס
הרשת גועשת סביב המעבר של OpenAI מ next ל remix. כולם משתפים חוויות ומסבירים כמה גם להם היה קשה לעבוד עם next ואיך ה App Router החדש מסובך מדי. אנסה לעשות קצת סדר:
1. אפליקציות עמוד-יחיד (SPA) זה מסובך. זה תמיד היה. ספריות לפיתוח SPA כמו react-router קיימות בדיוק בגלל שזה מסובך ושלאף אחד אין כח לטפל בלחיצות על כפתור "אחורה" וסימניות בדפדפן. אנחנו בונים SPA כי לפעמים זה נותן חווית שימוש טובה יותר.
2. רינדור בצד שרת (SSR) זה אפילו עוד יותר מסובך, כי יש לנו את כל הסיבוך של SPA ובנוסף צריכים להתמודד עם ניהול Cache בצד שרת ולוודא שהקוד של הקומפוננטות לא מפעיל דברים שצריכים את window כשהוא רץ בשרת. אנחנו מרנדרים בצד שרת כי מנועי חיפוש, קוראי מסך ועוד כלים רבים אחרים אוהבים לקבל את ה HTML מוכן גם אם הם לא מריצים JavaScript.
3. קומפוננטות צד שרת (RSC) זה אפילו עוד יותר מסובך מרינדור צד שרת. ברינדור צד שרת רגיל כל הקוד של האפליקציה משמש לבניית ה HTML ואז נשלח לדפדפן כדי להוסיף את ה JavaScript וקוד הטיפול באירועים. במבנה של קומפוננטות צד שרת צריך לבחור מראש איזה קומפוננטות כוללות קוד JavaScript שצריך לרוץ בדפדפן (אלה ה"איים" או הקומפוננטות צד לקוח) ואת הקוד שלהן אנו שולחים לדפדפן, ויש קומפוננטות אחרות שאין להן קוד טיפול באירועים ולכן נעדיף לא לשלוח את הקוד שלהן לדפדפן. ככה באפליקציה גדולה אפשר לשלוח לדפדפן פחות קוד ולקבל שיפור בזמני הטעינה ובחווית המשתמש.
הנטיה של יותר מדי מפתחים היא לבחור את שיטת העבודה הכי מסובכת כי אולי נצטרך את זה בעתיד. זה מה שקרה ב OpenAI, שבונים אפליקציית SPA אבל השתמשו ב next.js שמתאים לקומפוננטות צד שרת. בשלב מסוים הם כנראה הבינו שהם משלמים יותר מדי על הבחירה הטכנולוגית שלהם ועברו לאפליקציית SPA רגילה עם רמיקס, שאגב הספיק לאחרונה להתמזג חזרה לתוך React Router.
מי שמחפש תחמושת לאיזה ויכוח דמיוני בין next.js ל react router לדעתי לא ימצא אותה כאן. אם כבר יש פה רק חידוד של ההבדל בין שני הכלים והבנה יותר מדויקת של מה מתאים למה. באופן כללי התאמה של שיטת העבודה לאתגר איתו אנחנו מתמודדים ולאילוצים הטכנולוגיים של המערכת שלנו זאת הדרך הטובה ביותר לבחור ספריות לפרויקט הבא שלנו.
1 419
ניסוי Hono - סינכרון ערך בין דפדפנים
התמיכה של דינו ב Web Standards הביאה לזה שמאוד קל לכתוב קוד הונו שמשתמש ב Streams כדי לקבל הודעות מגולשים ולדווח אותן הלאה לגולשים האחרים באתר, הכל ב SSE. בואו נראה איך לחבר את החוטים בפרויקט דינו ו Hono.
מה אנחנו בונים
האתר יכיל תיבת טקסט וכפתור. אם יש מספר גולשים שמחוברים לאתר ואחד מהם לוחץ על הכפתור אז הערך בתיבת הטקסט יישלח לכל הדפדפנים של הגולשים האחרים ויעודכן על המסך שלהם.
קוד צד שרת
קוד צד השרת מורכב משני נתיבים - אחד מטפל בעידכון והשני מטפל ברישום. כל גולש שנכנס לאתר פותח חיבור לנתיב הרישום, וכל פעם שלוחצים על הכפתור הגולשים שולחים הודעת POST לנתיב העידכון. אני מתחיל בהגדרת מערך של כל הגולשים שכרגע מחוברים:
const clients: Array<SSEStreamingApi> = [];
הממשק SSEStreamingApi מגיע מ Hono ומאפשר לשלוח אירועי צד שרת ללקוחות שממתינים להם. נתיב הרישום נראה כך:
app.get('/count', (c) => {
return streamSSE(c, async (stream) => {
stream.writeln("data: Start\n\n");
clients.push(stream);
const id = clients.length - 1;
stream.onAbort(() => {
clients.splice(id, 1);
console.log(\removed client ${id}\);
});
while(true) {
await stream.sleep(1000);
}
})
})
לקוחות שמתחברים לאתר ייכנסו לנתיב זה כדי להירשם לקבלת הודעות. הפונקציה streamSSE מגיעה מ hono. היא מקבלת את אוביקט החיבור של hono עצמו ופונקציית Callback. כל פעם שלקוח מתחבר מופעלת פונקציית ה Callback עם הפרמטר stream שמייצג את זרם התקשורת לאותו גולש. בקוד הדוגמה אני מדביק את האוביקט הזה למערך וגם רושם עליו פונקציית ביטול כך שכשהזרם ייסגר אוטומטית הוא יוסר מהמערך.
הפונקציה מסתיימת בלולאה אינסופית שמטרתה לשמור את החיבור בחיים. את הכתיבה אני מבצע מקוד הטיפול בנתיב העידכון:
app.post('/count', async (c) => {
console.log(\POST /count\);
const { value } = await c.req.json();
console.log(\Got ${value}\);
clients.forEach(c => {
c.writeSSE({ data: value });
})
return c.json({ message: 'Value received' });
})
קוד זה נקרא כל פעם שנשלחת בקשת POST לנתיב העידכון. הקוד רץ על כל הלקוחות במערך וכותב לכל אחד מהם את הערך שנשלח.
העליתי את הקוד המלא של main.ts עם כל ה import-ים לגיטהאב מוזמנים למצוא אותו בדף הפרויקט:
https://github.com/ynonp/hono-sync-counter-demo
קוד צד לקוח
בצד הלקוח אני רוצה להתחבר לנתיב הרישום בעלייה, ואחרי זה לשלוח הודעות POST לנתיב העדכון כל פעם שלוחצים על הכפתור. זה קובץ ה JavaScript שאני שולח ללקוח:
// client/sync.js
const evtSource = new EventSource("/count");
const ti = document.querySelector('input');
evtSource.onmessage = (event) => {
ti.value = event.data;
};
document.querySelector('button').addEventListener('click', async () => {
await fetch('/count', {
method: "POST",
body: JSON.stringify({ value: ti.value }),
contentType: 'application/json',
});
});
העבודה עם SSE בדפדפן מאוד נוחה ואני מעדיף אותו על פני Web Sockets עבור עידכונים אסינכרוניים מצד שרת.1 419
זמן לקבור את זה
מה עושה ראשת הצוות שמגלה שאין תשתית בחברה לבדיקות אוטומטיות למערכת? מה עושה המפתחת שמבינה שבלי אתר תיעוד אף אחד לא יבין את הפריימוורק שהיא בונה?
התרבות בתעשייה שלנו מעודדת יזמות כולל יזמות פנימית. אנחנו מעדיפים לפתור בעיות עם הכלים שעומדים לרשותנו, ואם אין פיתרון טוב מכניסים כלים חדשים. מפתחת שתביא פריימוורק עם תיעוד ובדיקות תקבל יותר קרדיט על העבודה שלה, אפילו אם זה היה מעבר לדרישות. ראש הצוות שיצליח להוריד את כמות הבאגים באמצעות שילוב מערכת בדיקות אוטומטיות יזכה בתהילה ובצדק.
האתגר הוא מה שקוראים בטעות Job Security - הצורך של אותם אנשים להמשיך לתחזק את המוצרים שהם הכניסו אפילו אחרי שהם ממשיכים לתפקידים חדשים בחברה ולפעמים גם בתור פרילאנסרים אחרי שעוזבים את החברה. המשך תחזוקת מוצרים ישנים זה לא באינטרס של אף אחד. החברה מפסידה כי היא תלויה במפתח בודד, המפתח מפסיד כי הוא לא מצליח להתפנות 100% לפרויקטים חדשים, והפרויקט מפסיד כי יגיע יום שכל הקוד הזה יגיע לפח ונצטרך לכתוב מערכת חדשה.
מוצרים פנימיים, כמו מוצרים חיצוניים, יכולים "לתפוס" או לא. יוניקס החלה את דרכה כמוצר פנימי, אבל ברגע שכולם בחברה השתמשו בה היה ברור שאין דרך חזרה. תשתית בדיקות פנימית שמישהו בנה בשביל הצוות שלו יכולה בקלות להפוך למוצר ליבה של החברה ולקבל צוות שאחראי להרחיב ולפתח אותה. ובאותה מידה מוצרים פנימיים, כמו מוצרים חיצוניים, יכולים להישאר בשימוש של מספר קטן של מפתחים ולהמשיך להיות מתוחזקים על ידי אותו מפתח אומלל שכתב אותם לפני שנים. כשזה קורה וכח האינרציה הוא הדבר היחיד שמשאיר אותם בחיים שווה לתכנן מועד סיום חיים לאותם מוצרים.
האתגר מבחינת החברה הוא שלכאורה אנחנו רק מרוויחים - יש לנו מוצר עם אפס עלות תחזוקה כי אלברט מהפיתוח מתחזק אותו בנוסף לעבודה הרגילה שלו ולא מתלונן. בטווח הרחוק מועד התשלום יגיע ובריבית, כשאלברט מהפיתוח יחליט לעזוב ונמצא את עצמנו מחפשים מפתח TypeScript שגם יודע awk בשביל להחליף אותו.
1 419
צוות תשתיות
קולומביאני שירצה לעבור 400 ק"מ כדי להגיע ממדג'ין לבוגוטה יצטרך לשבת 10 שעות באוטובוס. צרפתי שירצה לעבור את אותו מרחק מפריז לבורדו יצטרך רק שעתיים וחצי ברכבת. החיסכון בזמן הוא עצום אבל גם אפשרויות ההרחבה - ברכבת אפשר לעבוד תוך כדי נסיעה והקיבולת הגדולה יותר מאפשר להעביר מטענים.
איך ההבדל הזה משפיע על הכלכלה? על התרבות? על המדע? על החיים?
עבודה על תשתיות לא צריכה להיות רק תחזוקה ותיקון תקלות בכביש. צוותי תשתיות טובים הם אלה שבונים את העתיד. הם מאפשרים לפיתוח לחלום בגדול והופכים את החלומות האלה לאפשריים. צוותי תשתיות הם אלה שמניחים את מסילת הרכבת. המעבר מתחזוקה ליוזמה הוא אחד החשובים שנעשה בקריירה.
(נ.ב. הקושי בפרויקטי תשתית טובים דומה לקושי כללי בפיתוח מוצרים - איך יודעים מה לבנות והאם מה שאני בונה באמת יעזור. רק בגלל שאפשר להעביר את כל החברה לקוברנטיס לא אומר שזה הדבר הנכון לעשות. האתגר היום יותר מאי פעם בעבר הוא להבין את צרכי החברה ולעשות את השינויים שאחרים פוחדים לעשות אבל כן יפתחו דלתות להצלחה).
1 419
כמה מחשבות על סטאק אוברפלו ו AI
למה Stack Overflow לא רוצים ש Chat GPT יענה על שאלות? ומה זה אומר על שאר הדברים שאנחנו רוצים או לא רוצים לעשות עם AI?
נתחיל עם המקרים הקלים - אם אני צריך להוסיף ניתוב לפרוקסי חדש על שרת ה Apache שלי ברור שאני יכול להשתמש ב AI כדי לייצר את קובץ ההגדרות. גם בשביל ליצור מניפסט לקוברנטיס, להמיר קובץ JSON ל YAML וליצור נתונים אקראיים לבדיקות. כל המקרים האלה נראים הגיוניים כי אלה דברים שיכולתי בקלות למצוא בתיעוד ברשת, אפשר להגיד שה AI לא חסך לי פה המון עבודה.
אבל זאת רק ההתחלה. מה לגבי הקוד האמיתי? עדיין נראה לי הגיוני לבקש מ AI לכתוב לי שאילתת SQL, גם אם היא מסובכת, לבנות בשבילי בלוק CSS גם אם הוא משתמש ביכולות CSS שאני לא ממש מכיר ואפילו לעצב ולצייר קוד לקומפוננטת ריאקט כולל הפונקציונאליות שלה. בכל המקרים האלה אני כבר לא חושב על התוצאה שה AI מייצר בתור משהו שהייתי יכול למצוא ברשת, אלא אני חושב על ה AI בתור מישהו שעובד בשבילי. מעין עוזר וירטואלי שנכנס לקוד ועוזר לי למצוא תקלות ולהוסיף פיצ'רים קטנים. אפשר לדמיין שבגלל שאני משלם סכום חודשי ל Open AI אז אני יכול להשתמש בקוד של אותו עוזר וירטואלי באותו אופן שהייתי יכול להעסיק מתכנת אמיתי ולהשתמש בקוד שהוא כותב (רק הרבה יותר זול).
מסיבות אלה אנחנו גם מרגישים בנוח לתת ל AI לבנות עבורנו דפי נחיתה, לעזור בניסוח מיילים, להעלות רעיונות חדשים (שיום אחד גם נציג אותם בפגישה בתור רעיונות שלנו), לייצר גרפיקה או מוזיקה ועוד עבודות קריאייטיב שיהיו זמינות לנו בלי זכויות יוצרים.
אז מה לגבי Stack Overflow? ופוסטים לבלוג? או ציוצים באיזה רשת חברתית? או אפילו ברכת יום הולדת לקרוב משפחה? עכשיו זה נראה ברור - המשחק הוא לא כמה ה AI נותן תוצאה טובה, אלא כמה חשוב לנו תוכן אותנטי באותם מצבים. כשאנחנו מחפשים תשובה של בן אדם ומגיעים לטקסט של AI זה פוגע באמון שלנו באותו מדיום. אם מישהו פעם אחת ישלח לי ברכה שכתב AI אני לא אקרא יותר את הברכות שאותו חבר ישלח בעתיד. אם אגלה שבלוגר פעם אחת פירסם פוסט שכתב עבורו AI אני לא אטרח לקרוא את הפוסטים של אותו בלוגר בעתיד. מעבר לשאלת הדיוק, הבחירה של סטאק אוברפלו ואתרים רבים נוספים היא בחירת כיוון, איזה סוג אתר אנחנו רוצים להיות ואיזה סוג משתמשים אנחנו רוצים שיגיעו. לאורך זמן אני לא אתפלא אם התשובות של Chat GPT יהיו טובות יותר מהתשובות שנמצא בסטאק אוברפלו, כשם שההסברים שלו יכולים להיות טובים יותר מפוסטים בבלוגים או ציוצים ברשתות חברתיות. אבל דווקא ככל שההסברים של ה AI ימשיכו להשתפר זה רק ידגיש את המקום של תוכן מקורי, יצירתי ואישי שבני אדם כותבים.
1 419
ערך ריק בממשק
אחד הדברים שתמיד מבלבלים אותי הוא המשמעות של ערכים ריקים בתוך ממשקים. כמה דוגמאות מהתקופה האחרונה:
1. ב Rails אפשר להגדיר ב
config.hosts רשימה של דומיינים מורשים. מערך ריק אומר שכל הדומיינים מורשים, ולכן אם אני "רק" מוסיף ערך למערך בעצם אני מפעיל בדיקת אבטחה חדשה.
2. ב Gremlin הפקודה g.V(id) מחזירה צומת מהגרף לפי id. אפשר להעביר מספר מזהים כדי לקבל מספר צמתים למשל g.V(2, 3, 5) יחזיר את הצמתים 2, 3 ו-5. העברה של ערך ריק (בלי כלום בתוך הסוגריים) מחזירה את כל הצמתים בגרף.
3. הפקודה cat ביוניקס לוקחת שם קובץ ומציגה אותו על המסך. אם נעביר לה מספר קבצים היא תדפיס את כולם, אבל אם לא נעביר אף שם היא תדפיס את התוכן של STDIN שלה, כלומר תכנס למצב של קליטת שורות ותחזור על כל שורה שאנחנו כותבים עד שנלחץ Ctrl+D לסיים את הקלט.
יצירת התנהגות שונה לפונקציה עבור ערך ריק יכולה להיות טריק חמוד ליודעי דבר וגם לקצר קצת את הקוד ולכן הרבה פעמים זה קיצור שמפתה להכניס. בפועל הקיצורים האלה הרבה פעמים מבלבלים משתמשים חדשים ורק הופכים את הממשק שלכם לפחות ידידותי.1 419
הונו משתלב עם מנגנון הבדיקות האוטומטיות של דינו בצורה מובנית. ניצור תיקייה בשם
tests ובתוכה קובץ בשם tests/demo1_test.ts עם התוכן הבא:
import { expect } from "jsr:@std/expect";
import demo1 from '../routes/demo1.ts';
// basic test
Deno.test("simple test", async () => {
const res = await demo1.request('/text')
expect(res.status).toBe(200)
expect(await res.text()).toBe('Hello Hono!')
});
אפשר להפעיל את הבדיקה עם:
$ deno test
או בלחיצה על אייקון ההרצה המשולש ליד קוד הבדיקה שיופיע אוטומטית ב VS Code.
העלאה לרשת
וכמובן בגלל שזה דינו יש לנו העלאה אוטומטית לרשת בחינם ב denodeploy. צריך רק לכתוב:
deployctl deploy
משורת הפקודה ולעקוב אחר ההוראות. הפרויקט שלי זמין כבר בקישור:
https://ynonp-hono-demo.deno.dev/demo1/api/hello
ואתם מוזמנים לעבור על הקוד המלא בגיטהאב בקישור:
https://github.com/ynonp/hono-demo
סך הכל העבודה עם הונו היתה כל מה ש Express היה פעם - כיף, עובד בלי כאב ראש ועושה כל מה שצריך.1 419
הונו הוא כל מה שרציתי מ express
הונו (hono) הוא ספרייה לפיתוח ווב שקצת מזכירה את אקספרס אבל על סטרואידים - יש לו תמיכה מלאה בכל סביבת הרצת JavaScript, תמיכה מובנית בבדיקות, טייפסקריפט ישר מהקופסה, הוא יותר מהיר וגם מגיע עם ספריית צד-לקוח שמזכירה את ריאקט אבל הרבה יותר קטנה. הנה שלוש דוגמאות מהירות כדי להתרשם ממנו.
פיתוח פרויקט חדש
כל מה שצריך בשביל לפתוח פרויקט hono חדש זה את דינו מותקן ולהריץ את הפקודה:
deno run -A npm:create-hono@latest my-app
אני בוחר את דינו בתור סביבת הריצה וזה הכל. עכשיו אפשר להפעיל VS Code בתיקייה ולהתחיל לעבוד.
הדבר הראשון שאני רוצה לשנות בהגדרות הפרויקט יהיה בקובץ deno.json וזה להוסיף לתבנית משימה של פיתוח שתעבוד במצב watch. אני מעדכן את הקובץ ואחרי השינוי הוא נראה כך:
{
"imports": {
"hono": "jsr:@hono/hono@^4.5.10"
},
"tasks": {
"start": "deno run --allow-net main.ts",
"dev": "deno run --allow-net --watch main.ts"
},
"compilerOptions": {
"jsx": "precompile",
"jsxImportSource": "hono/jsx"
},
"deploy": {
"project": "b737e735-3715-4077-a44b-782fc7424cb5",
"exclude": [
"**/node_modules"
],
"include": [],
"entrypoint": "main.ts"
}
}
השורה היחידה שהוספתי היא הערך dev בתוך האוביקט tasks.
משורת הפקודה אני מפעיל:
deno task dev
ויש לי שרת שמקשיב על פורט 8000 ואני יכול להתחיל לעבוד, הקוד כתוב בטייפסקריפט וכל שינוי קוד אוטומטית מרענן את השרת.
דוגמה 1 - החזרת מידע
אני יוצר תיקיה חדשה בשם routes ובתוכה קובץ בשם demo1.ts עם התוכן הבא:
import { Hono } from "hono";
const router = new Hono();
let counter = 0;
router.get('/text', (c) => {
return c.text('Hello Hono!')
})
router.get('/api/hello', (c) => {
return c.json({
ok: true,
message: 'Hello Hono!',
})
})
router.get('/api/counter', (c) => {
return c.json({
value: counter++,
})
})
export default router;
הקובץ מגדיר שלושה נתיבים, אחד מחזיר טקסט, השני והשלישי מחזירים אוביקטי JSON. בשביל לגשת לנתיבים אני צריך לחבר אותם לשרת ולכן אני הולך לקובץ main.ts ושם מעדכן את הקובץ לתוכן הבא:
import { Hono } from 'hono'
import demo1 from './routes/demo1.ts';
const app = new Hono()
app.route('demo1', demo1);
Deno.serve(app.fetch)
עכשיו בדפדפן אני גולש ל localhost:8000/demo1/text כדי לראות את הטקסט, ל localhost:8000/demo1/api/hello כדי לראות את האוביקט הראשון ול localhost:8000/demo1/api/counter כדי לראות את האוביקט השני.
דוגמה 2 - גישה לפרמטרים של הבקשה
בואו נפתח קובץ נוסף בשם routes/demo2.ts והפעם עם התוכן הבא:
import { Hono } from "hono";
const router = new Hono();
router.get('hello', (c) => {
const name = c.req.query('name') ?? 'Guest';
return c.text(\Hello! ${name}\);
});
router.post('item', async (c) => {
const props = await c.req.json();
console.log(props);
return c.json(props);
})
export default router;
הקובץ מגדיר שני נתיבים - נתיב אחד לוקח את הפרמטר name מה Query String ומחזיר טקסט שכולל את הערך של הפרמטר או שם ברירת מחדל. הנתיב השני לוקח את גוף הבקשה בתור אוביקט JSON ומחזיר אותו. בשביל לחבר אותם לאפליקציה צריך רק להוסיף את השורה:
app.route('demo2', demo2);
לקובץ main.ts, וכן אפילו לא צריך לסגור ולפתוח את התוכנית הכל פשוט מתרענן ומראה את הנתיבים המעודכנים.
דוגמה 3 - שליחת HTML
הקובץ השלישי שלנו נקרא routes/demo3.tsx, וכן הסיומת היא tsx ולא ts הפעם. זה התוכן שלו:
import { Hono } from "hono";
const router = new Hono();
const View = ({name = "Guest"}) => {
return (
<html>
<body>
<h1>Hello! {name}</h1>
</body>
</html>
)
}
router.get('/', (c) => {
return c.html(<View name="ynon" />)
})
export default router;
אתם לא טועים - זה נראה כמו JSX וזה אכן זה. ל Hono יש דרך מובנית ופשוטה לכתוב HTML באמצעות jsx. יש שם עוד כל מיני קיצורים גם ל CSS ואפילו ספריית JavaScriptשהולכת עם זה ומאפשרת להוסיף תמיכה ב Hooks כמו useEffect, אבל זה כבר סיפור לפוסט אחר.
בדיקות אוטומטיות1 419
אין או יש מקום בדיסק?
הפלט של שתי הפקודות האלה על השרת הפתיע אותי -
ynon@schooler-prod-2:~$ touch a
touch: cannot touch 'a': No space left on device
ynon@schooler-prod-2:~$ df -h
Filesystem Size Used Avail Use% Mounted on
udev 7.9G 0 7.9G 0% /dev
tmpfs 1.6G 8.5M 1.6G 1% /run
/dev/sda 315G 103G 197G 35% /
tmpfs
איך יכול להיות ש touch לא יכול ליצור קובץ חדש ומתלונן שאין מספיק מקום על הדיסק, כש df מראה שיש דווקא המון מקום? האם df משקר? האם יש corruption במערכת הקבצים? מה קורה פה?
ייתכן. אבל לפני שרצים להפעיל fsck שווה לנסות גם את df -i. מערכות קבצים בלינוקס צריכות לשמור בנוסף לקבצים עצמם גם אינדקס שמקשר בין שם הקובץ לתוכן שלו על הדיסק. האינדקס הזה גם תופס מקום והמבנה שלו יוצר מגבלה על מספר הקבצים שאפשר לשמור בתיקייה. הפקודה df -i מציגה כמה קבצים אפשר לשמור על כל כונן וכמה יש לכם, בלי קשר לגודל שלהם.
כשיש מקום על הדיסק אבל אין מספיק inodes פנויים, הפקודה touch (ויצירת קבצים באופן כללי) יציגו את אותה הודעת שגיאה מבלבלת No space left on device, למרות שהם היו צריכים להגיד Not enough free inodes on device.1 419
למידע נוסף על הספריה ועוד אינסוף דוגמאות שווה לבקר באתר שלהם בקישור:
https://observablehq.com/plot/
1 419
הכירו את Observable Plot - הגירסה הקלה של D3
אני אוהב את D3 כי אפשר לעשות איתו הכל ויש אינסוף דוגמאות ברשת, אבל אי אפשר להכחיש ש D3 קצת מסובך וכולל שפה ומושגים שלא לכולם יש כח להכיר. אם אתם רק צריכים להוסיף איזה גרף למסכי הניהול שלכם בלי יותר מדי אינטרקציה ספריית Observable Plot יכולה לתת פיתרון מהיר. השימוש העיקרי שלה אגב הוא בתוך מחברות ג'ופיטר כשצד השרת הוא deno.
דוגמה 1 - ציור הפונקציה x בריבוע
בדוגמה הראשונה נראה גרף פשוט של הפונקציה x בריבוע. את observable plot אפשר לטעון דרך jsdeliver ישירות לדפדפן עם הפקודה:
import * as Plot from "https://cdn.jsdelivr.net/npm/@observablehq/plot@0.6/+esm";
המטרה של Observable Plot היא לקחת מידע והוראות איך להציג אותו ולחבר אותם יחד לגרף, לכן אנחנו מתחילים עם יצירת המידע:
const data = Array.from({length: 100}, (_, i) => {
let x = i - 50;
return {x, y: x * x};
});
מה שיוצר מערך של מאה תאיך, כל תא מכיל אוביקט עם שדה בשם x שמכיל מספר (בין מינוס 50 ל 50) ושדה y שמכיל את x בריבוע.
החלק השני לוקח את המערך והוראות איך להציג אותו ויוצר אוביקט plot:
const plot = Plot.plot({
marks: [
Plot.line(data, {x: "x", y: "y", stroke: "blue"})
],
x: {label: "x"},
y: {label: "f = x²"},
})
התוצאה plot היא ממש SVG שאפשר להוסיף ל DOM. אפשר לראות את הקוד המלא והתוצאה בקודפן:
https://codepen.io/ynonp/pen/abgamwv?editors=1010
או מוטמע:
<iframe height="300" style="width: 100%;" scrolling="no" title="Untitled" src="https://codepen.io/ynonp/embed/abgamwv?default-tab=html%2Cresult" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true">
See the Pen <a href="https://codepen.io/ynonp/pen/abgamwv">
Untitled</a> by Ynon Perek (<a href="https://codepen.io/ynonp">@ynonp</a>)
on <a href="https://codepen.io">CodePen</a>.
</iframe>
דוגמה 2 - היסטוגרמת תדירות מילים
דוגמה שניה וקצת יותר מורכבת היא ציור היסטוגרמת תדירות הופעה של מילים בטקסט. אני מתחיל עם המידע:
const wordFrequency = [
{word: "quick", count: 4},
{word: "brown", count: 2},
{word: "fox", count: 2},
{word: "jumps", count: 1},
{word: "over", count: 1},
{word: "the", count: 5},
{word: "lazy", count: 3},
{word: "dog", count: 2},
{word: "is", count: 3},
{word: "very", count: 2},
{word: "and", count: 1},
{word: "agile", count: 1},
{word: "not", count: 1},
{word: "as", count: 1},
{word: "but", count: 1},
{word: "certainly", count: 1}
];
וכן אין בעיה לקחת מחרוזת ולייצר את המידע הזה דינמית, פשוט רציתי להראות כאן את המידע כמו שהוא עובר ל Observable Plot.
בחלק השני אני יוצר את ה SVG:
const plot = Plot.plot({
marks: [
Plot.barY(wordFrequency, {x: "word", y: "count", sort: {x: "y", reverse: true}})
],
x: {
label: "Word",
tickRotate: -45
},
y: {
label: "Frequency",
tickFormat: d => d, // Ensure only integer ticks are shown
ticks: Math.max(...wordFrequency.map(d => d.count)) + 1 // Set the number of ticks to match the highest count
},
height: 400,
width: 600
});
הפעם יש קצת יותר קוד כי רציתי לשלוט באופן ציור השנתות על ציר ה y ולוודא שלא יופיעו חצאי מספרים. עדיין מה שחשוב זה המיפוי בין אוביקט המידע ל plot, מיפוי שבא לידי ביטוי בשורה:
Plot.barY(wordFrequency, {x: "word", y: "count", sort: {x: "y", reverse: true}})
התוצאה שוב בקודפן:
https://codepen.io/ynonp/pen/gONdwZK?editors=1010
או מוטמעת:
<iframe height="300" style="width: 100%;" scrolling="no" title="Untitled" src="https://codepen.io/ynonp/embed/gONdwZK?default-tab=html%2Cresult" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true">
See the Pen <a href="https://codepen.io/ynonp/pen/gONdwZK">
Untitled</a> by Ynon Perek (<a href="https://codepen.io/ynonp">@ynonp</a>)
on <a href="https://codepen.io">CodePen</a>.
</iframe>
现已上线!2025 年 Telegram 研究 — 年度关键洞察 
