ToCode
Ir al canal en Telegram
1 419
Suscriptores
Sin datos24 horas
Sin datos7 días
-530 días
Archivo de publicaciones
1 419
היום למדתי: nth-child וקלאס ב CSS
נניח שיצרתם טבלה עם CSS Grid ועכשיו אתם רוצים לצבוע רק שורה מסוימת - או יותר טוב, להדגיש את הגבול מסביב לשורה מסוימת. בעולם הישן של table היה מספיק למצוא את ה tr שמתאים לשורה ולהגדיר לו גבול, אבל בגריד הטבלה לא כוללת אלמנט tr. כל ה HTML שלה הוא בסך הכל:
<div class='container'>
<div>0</div>
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
<div>6</div>
<div>7</div>
<div>8</div>
<div>6</div>
<div>7</div>
<div>8</div>
</div>
אז הצעד הראשון הוא לזהות את ה div-ים שמתאימים בכלל לשורה שמעניינת אותנו. בשביל המשחק נסמן אותם בקלאס נפרד:
<div class='container'>
<div>0</div>
<div>1</div>
<div>2</div>
<div class='selected-row'>3</div>
<div class='selected-row'>4</div>
<div class='selected-row'>5</div>
<div>6</div>
<div>7</div>
<div>8</div>
<div>6</div>
<div>7</div>
<div>8</div>
</div>
עכשיו מה לגבי ההדגשה של השורה? טוב אפשר להגדיר גבול לכל התאים, אבל אז נקבל גם את הגבול בין התאים וזה לא מה שרצינו. אני רוצה רק גבול מסביב לשורה. טוב טוב, אז אפשר להגדיר גבול עליון ותחתון לכל תא בשורה, וגבול שמאלי וימני רק לתאים שבקצה. הגבול למעלה ולמטה הוא קל:
.container .selected-row{
border-top-color: red;
border-bottom-color: red;
}
ושני התאים בקצה? אפשר היה לדמיין להשתמש פה ב nth-child(1):
.container .selected-row:nth-child(1) {
border-left-color: red;
}
אבל זה לא עובד. ה div הראשון עם הקלאס selected-row הוא לא הילד הראשון ולכן כלום לא נצבע. מה עושים? מסתבר שיש ל nth-child טריק כדי לתפוס את הילד ה n-י שמתאים לקלאס מסוים וזה נראה ככה:
.container :nth-child(1 of .selected-row) {
border-left-color: red;
}
.container :nth-child(3 of .selected-row) {
border-right-color: red;
}
הדוגמה כאן למי שרוצה לשחק עם זה:
https://codepen.io/ynonp/pen/zYXQqmO1 419
כל שאר הקובץ נשאר ללא שינוי.
השינוי השני הוא בקומפוננטת הצייר, ובעצם בכל קומפוננטה שצריכה להקשיב לסיגנלים - עליי להוסיף קריאה לפונקציה
useSignals של signals-react. הקומפוננטה Painter בגירסת ריאקט נראית לכן כך:
import { color, setColor, picture } from './state.js'
import { useSignals } from "@preact/signals-react/runtime";
export default function Painter() {
useSignals();
const size = picture.value.size;
console.count('render');
return (
<div style={{display: 'grid',
gridTemplateColumns: \repeat(${size}, minmax(0, 1rem))\}}>
{
new Array(size).fill(0).map((_, i) => (
new Array(size).fill(0).map((_, j) => (
<div
key={i * 8 + j}
style={{background: color(i, j), height: '1rem', width: 'auto', border: '1px solid grey'}}
onClick={() => setColor(i, j, 1)}
></div>
))
))
}
</div>
)
}
בגירסת ריאקט יש כמה מגבלות המרכזית שבהן היא שאי אפשר להשתמש בסיגנל בתור ערך בתוך ה JSX, מה שאומר שאם יש שינוי בסיגנל תמיד צריך לרנדר מחדש את כל הקומפוננטה.
קוד וזה
מה עוד? אין עוד. כשלוחצים על פיקסל הסיגנל מתעדכן, הקומפוננטה מרונדרת מחדש והצבע על המסך מתעדכן. העליתי את הדוגמה בגירסת preact ל Deno Deploy אז אתם יכולים לשחק איתה כאן:
https://painter.deno.dev
ובגירסת ריאקט לפלייקוד כאן:
https://playcode.io/1853176
והקוד בגיטהאב:
https://github.com/ynonp/deno-painter-demo1 419
טיפ ריאקט - שמירת סטייט גלובאלי בסיגנלים
סיגנלים הם היום המנגנון הכי פשוט לניהול סטייט בריאקט. הם הגיעו מ preact ואומנם לא עובדים ישר מהקופסה ביישום ריאקט אבל עם החבילה המתאימה ממש פשוט לשלב אותם.
מה זה סיגנל
סיגנל הוא מידע שיודע לדווח לקוד שמסתכל עליו כשהוא השתנה. זה אוביקט שיש לו מאפיין value שמחזיק את הערך. בשביל לשפר ביצועים אפשר לשים סיגנל בתור ערך ב JSX ואז לא צריך לייצר את כל ה Virtual DOM של הקומפוננטה ובאופן אוטומטי preact מעדכן רק את הערך של הסיגנל, או לקחת את הערך שלו ואז הקומפוננטה תרונדר מחדש כשהערך משתנה.
זאת הדוגמה מתוך התיעוד שלהם:
import { signal } from "@preact/signals";
// Create a signal that can be subscribed to:
const count = signal(0);
function Counter() {
// Accessing .value in a component automatically re-renders when it changes:
const value = count.value;
const increment = () => {
// A signal is updated by assigning to the \.value\ property:
count.value++;
}
return (
<div>
<p>Count: {value}</p>
<button onClick={increment}>click me</button>
</div>
);
}
אפשר לראות שהסיגנל מוגדר מחוץ לקומפוננטה, הקומפוננטה מעדכנת אותו ומציגה אותו. על המסך אנחנו נראה את הערך במונה הלחיצות גדל כל פעם שלוחצים על הכפתור, ואם יהיו כמה Counter-ים באותו עמוד כולם יהיו מסונכרנים כי הם משתמשים באותו סיגנל. כן זה עד כדי כך פשוט וככה זה צריך לעבוד, כמו לשמור State מחוץ לקומפוננטה.
בואו נמשיך לדוגמה יותר מורכבת של צייר, רק שבמקום לשמור את התמונה בסטייט של קומפוננטה אחת נשמור אותה בסיגנל.
הסיגנל והפונקציות
מאוד נוח לשים סיגנל וכמה פונקציות שמטפלות בו בקובץ אחד ולחשוב על זה כמו Store של Flux. בדוגמת הצייר שלי זה יהיה:
// file: state.tsx
import { signal } from "@preact/signals";
const palette = [
'#ffffff', // White
'#000000', // Black
'#9bbc0f', // Lime Green
'#8bac0f', // Olive Green
'#306230', // Dark Green
'#0f380f', // Darker Green
'#34a853', // Green
'#31a354', // Slightly Darker Green
];
const initialState = () => ({
size: 8,
data: new Array(8).fill(0).map((_, i) =>
new Array(8).fill(0).map((_, j) => (
signal(0)
)))
})
export const picture = signal(initialState())
export function color(row: number, column: number) {
const n = picture.value.data[row][column].value
return palette[n % palette.length]
}
export function setColor(row: number, column: number, color: number) {
picture.value.data[row][column].value = color;
}
export function clear() {
picture.value = initialState()
}
הקובץ מייצא את הסיגנל של התמונה שמכיל מערך כפול של סיגנלים לכל הפיקסלים. כשפיקסל ישנה צבע רק הסיגנל שמתאים לו "יידלק" ויעדכן את האלמנטים שמקשיבים לו.
קומפוננטת צייר
קומפוננטת הצייר פשוט מציגה את התמונה ומחברת כל פיקסל לפונקציית setColor שהגדרתי בסטייט:
import { color, setColor, picture } from './state.tsx'
export default function Painter() {
const size = picture.value.size;
return (
<div className="grid" style={{gridTemplateColumns: \repeat(${size}, minmax(0, 1rem))\}}>
{
new Array(size).fill(0).map((_, i) => (
new Array(size).fill(0).map((_, j) => (
<div
className="h-4 w-auto border"
style={{background: color(i, j)}}
onClick={() => setColor(i, j, 1)}
></div>
))
))
}
</div>
)
}
קומפוננטת כפתור מחיקה
קומפוננטת כפתור המחיקה מציגה כפתור בודד עבור מחיקה שמחובר לפונקציה clear שהגדרתי בסטייט:
import { clear } from './state.tsx';
export default function ControlPanel() {
return (
<div>
<button onClick={clear}>Clear</button>
</div>
)
}
ריאקט
בשביל להפעיל את הסיגנלים האלה בריאקט צריך רק את החבילה המתאימה ולקרוא לעוד פונקציה. ברמת החבילות יש להתקין את signals-react ולהשתמש בה בייבוא. לדוגמה בקובץ state אני אשתמש בשורת הייבוא הבאה:
import { signal } from "@preact/signals-react";1 419
מה בעצם אנחנו פוחדים לאבד?
חלק מהפחד לעזוב עבודה הוא התהייה "האם אצליח למצוא עבודה טובה כל כך פעם נוספת?". ופחד כזה תוקף גם אנשים שבכלל לא אוהבים את העבודה שלהם. אנחנו נקטר כל היום אבל ברגע שמישהו מציע לעזוב פתאום ניבהל וניזכר שבעצם יש גם הרבה דברים טובים איפה שאנחנו נמצאים. שחבל להפסיד את מה שכבר השגנו.
אולי בגלל זה אנחנו מעדיפים למצוא עבודה חדשה לפני שעוזבים את הקודמת. קודם כל נטפל בפחד, אחרי זה אפשר לעבוד. אם זה אפשרי אני בעד, אבל מה עושים כשחיפוש עבודה טובה יותר דורש השקעה שאין לנו את המשאבים עבורה כי אנחנו יותר מדי עסוקים בעבודה קיימת?
דרך אחרת להסתכל על הבעיה היא לשנות את השאלה - במקום לשאול איך אני רוצה לשפר את המצב אני אשאל "מה בעצם אני פוחד לאבד פה?", מה ה Best Case שיקרה אם אשאר?
כשקשה אפילו לדמיין עתיד במסלול הנוכחי זה סימן טוב שצריך להיפרד.
1 419
מטלות והזדמנויות
הנה רשימת הדברים שאתה צריך לעשות בשביל להצליח.
מול-
הנה רשימה של דברים שתוכלי לעשות. אנחנו לא יודעים מה האפשרות הטובה ביותר ומה יקרה אם תבחרי כל אפשרות.
הרבה שנים המשפט הראשון היה הדרך לגרום לתלמידים להשקיע בבית ספר. עד שיום אחד גילינו שזה בעצם לא נכון או שההצלחה הפכה פחות מעניינת. היום רבים מאיתנו עדיין מחפשים הבטחות בסגנון של המשפט הראשון, ומתאכזבים כל פעם מחדש אחרי שסיימנו את כל הרשימה ועדיין לא הצלחנו.
השינוי שאנחנו צריכים לעשות, כתלמידים וכמורים, הוא המעבר למשפט השני. עלינו לחפש הזדמנויות ולעודד אחרים לנצל אותם, לא בגלל שזו הדרך להצלחה (אנחנו כבר לא יודעים מה הדרך להצלחה), אלא כי זאת הדרך היחידה קדימה.
1 419
אם ערך חוזר ואף אחד לא מסתכל עליו, הוא עדיין משמיע רעש?
תמיד כעסתי על פייתון ו JavaScript בגלל שהן דרשו ממני לכתוב return בצורה מפורשת. הקוד הזה בפייתון יחזיר None למרות שברור שזה לא מה שרציתי:
def twice(x): x * 2
ברובי זה לא היה קורה. הפונקציה הזאת ברובי מחזירה בדיוק את מה שרציתי - כלומר את המספר שקיבלה כפול 2:
def twice(x) = x * 2
אבל אז מגיעה הדוגמה הבאה ששוב מזכירה שקיצורי דרך בתכנות זה נחמד אבל תמיד עדיף להבין בדיוק מה קורה מתחת:
def fib_rb(n)
a = 0
b = 1
n.times { a, b = b, a + b }
a
end
בדוגמה הזאת הבלוק (הקוד שרץ n פעמים מוקף בסוגריים מסולסלים) בעצם עושה את זה:
def fib_rb(n)
a = 0
b = 1
n.times {
a, b = b, a + b
return [b, a + b]
}
a
end
ולמה זה חשוב? כי כשמחזירים מערך אז צריך להקצות אותו ופעולת ההקצאה עצמה לוקחת זמן ומקום בזיכרון. בדוגמה בקישור ג'ון הורטון משפר משמעותית את זמן הריצה של הקוד באמצעות השינוי הזה:
def fib_rb(n)
a = 0
b = 1
n.times { a, b = b, a + b; nil }
a
end
עכשיו רובי לא צריך להקצות מערך רק בשביל להחזיר שני ערכים ובמקום זה מחזיר nil, מה שבכלל לא משנה כלום בתוכנית כי ממילא אף אחד לא מסתכל על ערך ההחזר.1 419
טיפ ריאקט: איך לתפוס לחיצה מחוץ לקומפוננטה
חבר שואל - יש לי קומפוננטה שפותחת תפריט קופץ. איך אני מעלים את התפריט כשמשתמש לוחץ על המסך מחוץ לקומפוננטה? יש משהו כמו onClick אבל על כל שאר הדברים?
תשובה - בשביל לענות על זה צריך להיזכר באיך עובדים אירועים ב JavaScript. כל פעם שיש איזשהו אירוע על המסך הדפדפן אוטומטית נותן הזדמנות לאלמנטים לטפל באירוע. ברירת המחדל היא שאלמנט יכול לטפל באירוע שקורה אצלו, ואם הוא לא מטפל אז האירוע הזה "מבעבע" הלאה לאלמנט שמכיל אותו ואז לאלמנט שמכיל אותו וכך הלאה עד לאלמנט הכללי ביותר על המסך שזה window.
לכן בשביל לתפוס לחיצה מחוץ לאלמנט כל מה שאנחנו צריכים לעשות זה להקשיב לאירוע click על אוביקט window ושם נתפוס את כל הלחיצות. מאחר וגם אירועים בתוך האלמנט מבעבעים למעלה ל window, נצטרך דרך לזהות שאירוע מסוים התקיים מחוץ לאלמנט שמעניין אותנו. דרך קלה לפתור את זה היא להשתמש בפונקציה
stopPropagation של אוביקט אירוע כשאנחנו מטפלים בלחיצות שאנחנו לא רוצים שיגיעו הלאה ל window.
סך הכל הקוד נראה כך:
import React, { useEffect, useState } from 'react';
const initialText = "Click outside the component to change this text"
export function App(props) {
const [text, setText] = useState(initialText);
useEffect(() => {
const changeText = () => setText('Yay!');
window.addEventListener('click', changeText);
return () => {
window.removeEventListener(changeText);
}
}, []);
return (
<div
className='App'
style={{background: 'red'}}
onClick={(e) => e.stopPropagation()}
>
<h2>{text}</h2>
</div>
);
}
מוזמנים לשחק עם הקוד לייב בקודפליי בקישור:
https://playcode.io/18487621 419
טרנזאקציות
שליחת הפוסט אתמול לטלגרם עבדה חלקית, וזה מעניין. ביום רגיל יש תוכנית פייתון ששוברת את הפוסט להודעות שייכנסו בטלגרם (יש להם מגבלה של מילים) וגם מוסיפה Escape Characters איפה שצריך. בגלל שטלגרם נודניקים לא תמיד הקוד שלי מוסיף את ה Escape Characters במקומות הנכונים ולכן שליחה של הודעות לפעמים נכשלת.
ומה קורה כשיש פוסט ארוך ובעיה בהוספת ה Escape Characters בהודעה השנייה? השלישית? אולי הודעה באמצע? אתם כבר מבינים לאן זה הולך, הפרסום לטלגרם יישלח חלקית ונקבל פוסט חתוך.
כשהקוד טוב בעיות כאלה קורות מעט מאוד, אבל כשהן קורות קשה להתמודד איתן. בואו נחשוב על הטלגרם ופיתרונות אפשריים לאתגר:
1. צעד ראשון יהיה ליצור קבוצת "בדיקה", לשלוח אליה את כל החלקים של הפוסט ורק אם הכל עבר לשלוח את אותם חלקים לקבוצה האמיתית. אבל זה לא מספיק טוב כי אולי יש משהו שונה בהגדרות בין קבוצת הבדיקה לקבוצה האמיתית שיגרום להודעות לעבור לקבוצת הבדיקה ולהיכשל בשליחה לקבוצה האמיתית.
2. פיתרון קצת יותר טוב יהיה בדיקה אחרי שליחת כל הודעה לראות אם השליחה נכשלה, ואם כן למחוק את כל ההודעות ברצף. גם זה לא מספיק טוב כי אולי התוכנית עצמה התרסקה ולכן אין מי שימחק את הרצף.
3. פיתרון עוד קצת יותר טוב יהיה להריץ את תוכנית השליחה כל חמש דקות במקום כל פעם ביום. התוכנית תבדוק אם הפוסט המלא נשלח לטלגרם. אם כלום לא נשלח היא תנסה לשלוח, ואם היא מוצאת חלק מהפוסט בקבוצה היא תמחק את מה שמצאה.
4. בשביל לא להיכנס ללופ אותה תוכנית שרצה פעם בחמש דקות תצטרך לסמן באיזשהו בסיס נתונים ששליחה נכשלה ושהיא מחקה את כל השאריות (או שהשליחה הצליחה). כשהתוכנית עולה היא תוכל לבדוק בבסיס הנתונים באיזה סטטוס היא סיימה פעם קודמת וכך היא תדע אם דרושה עוד עבודה.
פיתוח תוכנה, כל תוכנה, דורש איזון בין הרצון שלנו לבנות את הקוד הכי אמין וטוב שאפשר לבין המציאות והמשאבים שיש לנו (כולל משאבים של תחזוקה ובאגים בקוד יותר מורכב). במקרה של פירסום לטלגרם הייתי עוצר בסעיף 2 כדי לקבל קוד טוב יותר מהקוד שיש לי היום שם, אבל עדיין לא ידרוש יותר משאבים.
1 419
טרנזאקציות
שליחת הפוסט אתמול לטלגרם עבדה חלקית, וזה מעניין. ביום רגיל יש תוכנית פייתון ששוברת את הפוסט להודעות שייכנסו בטלגרם (יש להם מגבלה של מילים) וגם מוסיפה Escape Characters איפה שצריך. בגלל שטלגרם נודניקים לא תמיד הקוד שלי מוסיף את ה Escape Characters במקומות הנכונים ולכן שליחה של הודעות לפעמים נכשלת.
ומה קורה כשיש פוסט ארוך ובעיה בהוספת ה Escape Characters בהודעה השנייה? השלישית? אולי הודעה באמצע? אתם כבר מבינים לאן זה הולך, הפרסום לטלגרם יישלח חלקית ונקבל פוסט חתוך.
כשהקוד טוב בעיות כאלה קורות מעט מאוד, אבל כשהן קורות קשה להתמודד איתן. בואו נחשוב על הטלגרם ופיתרונות אפשריים לאתגר:
1. צעד ראשון יהיה ליצור קבוצת "בדיקה", לשלוח אליה את כל החלקים של הפוסט ורק אם הכל עבר לשלוח את אותם חלקים לקבוצה האמיתית. אבל זה לא מספיק טוב כי אולי יש משהו שונה בהגדרות בין קבוצת הבדיקה לקבוצה האמיתית שיגרום להודעות לעבור לקבוצת הבדיקה ולהיכשל בשליחה לקבוצה האמיתית.
2. פיתרון קצת יותר טוב יהיה בדיקה אחרי שליחת כל הודעה לראות אם השליחה נכשלה, ואם כן למחוק את כל ההודעות ברצף. גם זה לא מספיק טוב כי אולי התוכנית עצמה התרסקה ולכן אין מי שימחק את הרצף.
3. פיתרון עוד קצת יותר טוב יהיה להריץ את תוכנית השליחה כל חמש דקות במקום כל פעם ביום. התוכנית תבדוק אם הפוסט המלא נשלח לטלגרם. אם כלום לא נשלח היא תנסה לשלוח, ואם היא מוצאת חלק מהפוסט בקבוצה היא תמחק את מה שמצאה.
4. בשביל לא להיכנס ללופ אותה תוכנית שרצה פעם בחמש דקות תצטרך לסמן באיזשהו בסיס נתונים ששליחה נכשלה ושהיא מחקה את כל השאריות (או שהשליחה הצליחה). כשהתוכנית עולה היא תוכל לבדוק בבסיס הנתונים באיזה סטטוס היא סיימה פעם קודמת וכך היא תדע אם דרושה עוד עבודה.
פיתוח תוכנה, כל תוכנה, דורש איזון בין הרצון שלנו לבנות את הקוד הכי אמין וטוב שאפשר לבין המציאות והמשאבים שיש לנו (כולל משאבים של תחזוקה ובאגים בקוד יותר מורכב). במקרה של פירסום לטלגרם הייתי עוצר בסעיף 2 כדי לקבל קוד טוב יותר מהקוד שיש לי היום שם, אבל עדיין לא ידרוש יותר משאבים.
1 419
יום 17 של Advent Of Code 2023 - היום בו סקאלה עבדה נגדי
אני לא יודע אם זה הקוד שלי או משהו מובנה בשפה, אבל התרגיל של יום 17 היה הכי מאתגר עד כה. בפעם הראשונה בסידרה הרגשתי שמבני הנתונים ה Immutable של סקאלה שכל כך כיף לעבוד איתם לא מצליחים לספק את הביצועים הנדרשים ועברתי להשתמש ב Mutable Collections. בואו נראה את התרגיל והפיתרון וכמו תמיד תרגישו בנוח להציע פיתרונות טובים יותר או יעילים יותר למקרה שפספסתי משהו.
האתגר - חיפוש מסלול קצר ביותר בגרף
האתגר של יום 17 הוא מימוש כמעט קלאסי של אלגוריתם מציאת המסלול הקצר ביותר בגרף. המסלול שלנו בנוי בצורת גריד של מספרים לדוגמה:
2413432311323
3215453535623
3255245654254
3446585845452
4546657867536
1438598798454
4457876987766
3637877979653
4654967986887
4564679986453
1224686865563
2546548887735
4322674655533
אנחנו מתחילים מהנקודה השמאלית עליונה וצריכים להגיע לפינה הימנית תחתונה של הלוח, ולמצוא את המסלול שסכום המספרים עליו הוא הנמוך ביותר (לא סופרים את ה-2 של נקודת ההתחלה, אלא אם כן נעבור בה פעם שנייה). יש גם טוויסט כי אסור שיהיו במסלול יותר מ-3 צעדים באותו כיוון.
בשביל למצוא מסלול קצר ביותר אנחנו יוצאים מנקודת ההתחלה ומסמנים את כל הנקודות אליהן אפשר להגיע, ולכל נקודה שומרים מה ה"ניקוד" של אותה נקודה. שמים את נקודת ההתחלה בצד ומכל הנקודות שנשארו מוצאים את זו עם הניקוד הנמוך ביותר וממשיכים ממנה את המסלול כדי לקבל את הנקודות אליהן אפשר להגיע מנקודה זו, ואז גם את הנקודה השנייה שמים בצד ושוב ומוצאים את הנקודה עם הניקוד הכי נמוך וממשיכים ממנה את המסלול. כך ממשיכים עד שמגיעים לסוף.
הניקוד בתרגיל הזה מורכב מסכום של שני דברים: המרחק מהיעד וסכום המספרים שהוביל אותנו לנקודה. כשלוקחים כל פעם את הנקודה עם הניקוד הכי נמוך וממשיכים ממנה אנחנו מבטיחים שנתקדם בכיוון הנכון ובמסלול עם הסכום הנמוך ביותר.
פיתרון בסקאלה
לא משנה כמה ניסיתי לכתוב את רקורסיית הזנב בצורה יעילה, בסופו של דבר חיפוש הנקודה הבאה מתוך אוסף כל הנקודות הפוטנציאליות לקח יותר מדי זמן - ורק גדל ככל שאספתי יותר אפשרויות. מה שהייתי צריך כאן היה scala.collection.mutable.PriorityQueue שהוא חלק ממבני הנתונים ה Mutable של סקאלה.
נקודה נוספת חשובה מהפיתרון היא ההתמודדות עם המגבלה של שלושה צעדים מקסימליים לאותו כיוון. בשביל זה הגדרתי לכל צומת גם את הכיוון בו הגעתי אליו, וכך התיחסתי לאותו צומת בצורה שונה אם הגעתי אליו בצורה אנכית או אופקית.
איך עובד הקוד? קודם כל מגדירים את הצומת עם הפונקציה שמחזירה את כל השכנים:
case class Node(pos: (Int, Int),
direction: Direction,
count: Int,
map: Map[(Int, Int), Int]) {
def move(newPosition: (Int, Int), newDirection: Direction): Option[Node] =
if (((count == 3) && newDirection == direction) || (!map.contains(newPosition))) {
None
} else {
Some(Node(
pos=newPosition,
direction=newDirection,
count=if ((newDirection == direction) || (direction == Direction.Start)) count + 1 else 1,
map=map))
}
def movePart2(newPosition: (Int, Int), newDirection: Direction): Option[Node] =
if (!map.contains(newPosition)) {
return None
}
if ((newDirection == direction) && (count >= 10)) {
return None
}
if ((newDirection != direction) && (direction != Direction.Start) && (count < 4)) {
return None
}
Some(Node(
pos = newPosition,
direction = newDirection,
count = if ((newDirection == direction) || (direction == Direction.Start)) count + 1 else 1,
map = map))
def up: Option[Node] = if (direction == Direction.Down) {
None
} else {
move(newPosition=(pos._1 - 1, pos._2), newDirection=Direction.Up)
}
def down: Option[Node] = if (direction == Direction.Up) {
None
} else {
move(newPosition=(pos._1 + 1, pos._2), newDirection=Direction.Down)
}
def left: Option[Node] = if (direction == Direction.Right) {
None
} else {
move(newPosition=(pos._1, pos._2 - 1), newDirection=Direction.Left)
}1 419
נשים לב ש esbuild לא בודק הגדרות טיפוסים ולכן בנוסף אליו נצטרך להגדיר קובץ tsconfig.json לפרויקט כדי להריץ את ה TypeScript Compiler, תוך שאנחנו זוכרים ש tsc יבנה רק גירסה של הפרויקט שמותאמת ל node (וכן זה היה מושלם אם היתה דרך לשכנע את tsc לבנות גירסה שמתאימה ל deno. עד כמה שאני יודע אין).
בואו נראה את זה בקוד.
בתיקיית הדוגמה אני משנה את הסיומות של שני קבצי הקוד ל ts, מוסיף קובץ tsconfig.json עם התוכן הבא:
{
"compilerOptions": {
"target": "ESNext",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
"strict": true,
"skipLibCheck": true,
"outDir": "./distNode"
}
}
משנה את שורת ה import ל:
import { text } from './helper';
ומריץ tsc כדי לראות שהכל מתקמפל.
אחרי זה אני מריץ את שתי פקודות ה esbuild שהראיתי:
$ esbuild --bundle main.ts --packages=external --outdir=distNode --format=cjs
$ esbuild --bundle main.ts --packages=external --outdir=distDeno --format=esm
ונוצרו לי שני פרויקטים בשתי תיקיות היעד. אני יכול להריץ כל אחד מהם עם הכלי שמתאים לו:
$ deno run -A distDeno/main.js
_________________
< I'm a moooodule >
-----------------
\ ^__^
\ (oO)\_______
(__)\ )\/\
U ||----w |
|| ||
$ node distNode/main.js
_________________
< I'm a moooodule >
-----------------
\ ^__^
\ (oO)\_______
(__)\ )\/\
U ||----w |
|| ||
בשביל לבדוק שהכל מתקמפל גם עם דינו נוכל להפעיל:
$ deno check --unstable-sloppy-imports *.ts
כאשר המתג --unstable-sloppy-imports גורם לדינו לעבוד גם עם import-ים ללא סיומת, כמו אלה בקבצי המקור של הטייפסקריפט שלנו. אבל זה מתג עבודה לא מומלץ ואמור לרדת בגירסה 2 של דינו.
בעיניי שיטה זו פחות טובה גם בגלל שהיא מסורבלת יותר וגם בגלל שאין באמת טעם לבנות את אותו פרויקט גם ל node וגם ל deno.
כיוון עבודה מומלץ
אנחנו נמצאים היום בצומת דרכים מבחינת העתיד של סביבות להרצת JavaScript מחוץ לדפדפן. עד לא מזמן node.js היתה האופציה היחידה שלנו, אבל היום יש כבר שלוש סביבות טובות להרצה - deno ,node, ו bun שהוא ניסיון לייצר סביבה אפילו יותר מהירה מ deno ויותר תואמת ל node.js. אני לא יודע איך התחרות הזאת תיגמר, ואם בסופו של דבר יהיה מנצח או שאנשים ישתמשו בשלושת הכלים לפי סוג הפרויקט.
כרגע יש חבילות שעובדות טוב יותר בדינו, וחבילות אחרות שעובדות טוב יותר ב node, וחבילות שעובדות די טוב בשתי הסביבות. אקספרס עובדת סביר בשתי הסביבות אבל עדיין פיצ'רים מסוימים שלה עובדים רק ב node. לדינו יש פריימוורק מתחרה וספציפי לדינו בשם Oak. נקסט עובדת רק ב node אבל לדינו יש פריימוורק מתחרה בשם fresh.
עוד דוגמה היא חבילות החיבור לבסיס נתונים. דרייברים של בסיס נתונים לא עובדים בצורה חלקה בין שתי הסביבות ויש הרבה ספריות לגישה לבסיס נתונים שמוגבלות רק ל node.js. אני אוהב לעבוד עם ספריה בשם kysely שכן עובדת על שתי הסביבות (יש גם את drizzle שעובדת בכל מקום), אבל הרבה ספריות מובילות במיוחד ספריות ORM לא תומכות בדינו.
אני מקווה לראות את האקוסיסטם של דינו ממשיך לגדול ויום אחד לקבל מערכת שמכילה מספיק מודולים ותיעוד כדי שאפשר יהיה לעבוד איתה בקלות בלי קשר ל node.js. כרגע דינו מתאים יותר להרפתקנים או לפיתוח יישומים ספציפיים. שירות Deno Deploy שלהם הוא מצוין ולכן כן הייתי בונה איתו היום מערכת ווב תוך שימוש ב Web Frameworks שלהם, אבל החיבור למודולים מ npm עדיין לא עובד בצורה מושלמת ובעיות תאימות שם יכולות לתסכל.
¡Ya disponible! Investigación de Telegram 2025 — los principales insights del año 
