טעינה מראש של מודולים

Sérgio Gomes

תאריך פרסום: 23 בנובמבר 2024

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

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

<link rel="preload"> היא דרך לבקש משאבים באופן דקלרטיבי מראש, לפני שהדפדפן זקוק להם.

<head>
  <link rel="preload" as="style" href="critical-styles.css">
  <link rel="preload" as="font" crossorigin type="font/woff2" href="myfont.woff2">
</head>

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

<link rel="preload"> והמקבילה שלו בכותרת ה-HTTP מספקים דרך פשוטה ודקלרטיבית להודיע לדפדפן באופן מיידי על קבצים קריטיים שיידרשו כחלק מהניווט הנוכחי. כשהדפדפן מזהה את ההורדה מראש, הוא מתחיל הורדה בעדיפות גבוהה של המשאב, כך שבזמן הצורך הוא כבר יהיה זמין או חלקי. עם זאת, אי אפשר להשתמש בהם במודולים.

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

בתגים <script> ו-<link>, אפשר להגדיר את מצב פרטי הכניסה באמצעות המאפיין crossorigin. עם זאת, מסתבר ש-<script type="module"> ללא מאפיין crossorigin מציין מצב פרטי כניסה של omit, שלא קיים ב-<link rel="preload">. כלומר, תצטרכו לשנות את המאפיין crossorigin גם ב-<script> וגם ב-<link> לאחד מהערכים האחרים, ויכול להיות שלא תהיה לכם דרך קלה לעשות זאת אם מה שאתם מנסים לטעון מראש הוא יחסי תלות של מודולים אחרים.

בנוסף, אחזור הקובץ הוא רק השלב הראשון בהפעלת הקוד בפועל. קודם הדפדפן צריך לנתח ולקמפל אותו. מומלץ לעשות זאת מראש, כדי שהקוד יהיה מוכן להפעלה כשהמודול יידרש. עם זאת, V8 (מנוע ה-JavaScript של Chrome) מנתח ומעבד מודולים באופן שונה מ-JavaScript אחר. <link rel="preload"> לא מספק דרך לציין שהקובץ שנטען הוא מודול, ולכן כל מה שהדפדפן יכול לעשות הוא לטעון את הקובץ ולהעביר אותו למטמון. אחרי שהסקריפט נטען באמצעות תג <script type="module"> (או נטען על ידי מודול אחר), הדפדפן מנתח ומאגד את הקוד כמודול JavaScript.

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

<head>
  <link rel="modulepreload" href="super-critical-stuff.mjs">
</head>
[...]
<script type="module" src="super-critical-stuff.mjs">

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

תמיכה בדפדפנים

  • Chrome: 66.
  • Edge: ‏ ≤79.
  • Firefox: 115.
  • Safari: 17.

מקור

אבל מה קורה עם יחסי התלות של המודולים?

Funny you should ask! אכן, יש משהו שלא צוין במאמר הזה: חזרה חוזרת על עצמה (recursion).

למעשה, מפרט <link rel="modulepreload"> מאפשר לטעון אופציונלית לא רק את המודול המבוקש, אלא גם את כל עץ התלות שלו. הדפדפנים לא חייבים לעשות זאת, אבל הם יכולים.

אז מהו הפתרון הטוב ביותר לדפדפנים שונים לטעינת מודול מראש וכן את עץ התלות שלו, מכיוון שצריך את עץ התלות המלא כדי להריץ את האפליקציה?

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

<head>
  <!-- dog.js imports dog-head.js, which in turn imports
       dog-head-mouth.js, which imports dog-head-mouth-tongue.js. -->
  <link rel="modulepreload" href="dog-head-mouth-tongue.mjs">
  <link rel="modulepreload" href="dog-head-mouth.mjs">
  <link rel="modulepreload" href="dog-head.mjs">
  <link rel="modulepreload" href="dog.mjs">
</head>

האם טעינת מודולים מראש עוזרת לשפר את הביצועים?

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

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