ב-codelab הזה נסביר איך צמצום ודחיסה של חבילת JavaScript עבור האפליקציה הבאה משפרים את ביצועי הדף על ידי הקטנת גודל הבקשה של האפליקציה.
מדידה
לפני שמתחילים להוסיף אופטימיזציות, תמיד כדאי קודם לנתח את המצב הנוכחי של האפליקציה.
- כדי לראות תצוגה מקדימה של האתר, לוחצים על הצגת האפליקציה ואז על מסך מלא
.
האפליקציה הזו, שמוסברת גם ב-codelab "הסרת קוד שלא נעשה בו שימוש", מאפשרת להצביע לגור החתולים האהוב עליכם. 🐈
עכשיו בודקים מה הגודל של האפליקציה הזו:
- מקישים על Control+Shift+J (או על Command+Option+J ב-Mac) כדי לפתוח את כלי הפיתוח.
- לוחצים על הכרטיסייה רשת.
- מסמנים את תיבת הסימון השבתת המטמון.
- טוענים מחדש את האפליקציה.
למרות שהושג שיפור משמעותי ב-codelab "Remove unused code" (הסרת קוד שלא נמצא בשימוש) כדי להקטין את גודל החבילה, 225KB עדיין נחשב לגודל גדול.
הקטנה
נבחן את בלוק הקוד הבא.
function soNice() {
let counter = 0;
while (counter < 100) {
console.log('nice');
counter++;
}
}
אם הפונקציה הזו נשמרת בקובץ משלה, גודל הקובץ הוא בערך 112 בייט.
אם מסירים את כל הרווחים הלבנים, הקוד שמתקבל נראה כך:
function soNice(){let counter=0;while(counter<100){console.log("nice");counter++;}}
גודל הקובץ יהיה עכשיו בערך 83 בייט. אם הקוד עובר שינוי נוסף בגלל קיצור של שם המשתנה ושינוי של חלק מהביטויים, הקוד הסופי יכול להיראות כך:
function soNice(){for(let i=0;i<100;)console.log("nice"),i++}
גודל הקובץ מגיע עכשיו ל-62 בייט.
בכל שלב, קשה יותר לקרוא את הקוד. עם זאת, מנוע ה-JavaScript של הדפדפן מפרש כל אחד מהם בדיוק באותו אופן. היתרון של טשטוש קוד באופן הזה הוא שניתן להקטין את גודל הקבצים. הנפח של 112 בייט לא היה גדול מלכתחילה, אבל עדיין הייתה ירידה של 50% בנפח!
באפליקציה הזו נעשה שימוש בגרסה 4 של webpack ככלי לאריזת מודולים. אפשר לראות את הגרסה הספציפית ב-package.json
.
"devDependencies": {
//...
"webpack": "^4.16.4",
//...
}
בגרסה 4, החבילה כבר עוברת מיניפיקציה כברירת מחדל במהלך מצב הייצור. הוא משתמש
TerserWebpackPlugin
בפלאגין ל-Terser.
Terser הוא כלי פופולרי שמשמש לדחיסת קוד JavaScript.
כדי לראות איך נראה הקוד הממוזער, לוחצים על main.bundle.js
בחלונית Network של DevTools. לוחצים על הכרטיסייה תגובה.
הקוד בצורה הסופית שלו, אחרי מיניפיקציה ושינוי שמות, מוצג בגוף התשובה.
כדי לברר מה היה גודל החבילה אם היא לא הייתה מכווצת, פותחים את webpack.config.js
ומעדכנים את ההגדרה mode
.
module.exports = {
mode: 'production',
mode: 'none',
//...
טוענים מחדש את האפליקציה ומסתכלים שוב על גודל ה-bundle דרך החלונית Network בכלי הפיתוח.
זה הבדל משמעותי! 😅
לפני שממשיכים, חשוב לבטל את השינויים.
module.exports = {
mode: 'production',
mode: 'none',
//...
הכללת תהליך לצמצום הקוד באפליקציה תלויה בכלים שבהם אתם משתמשים:
- אם משתמשים ב-webpack גרסה 4 ומעלה, לא צריך לבצע פעולות נוספות כי הקוד עובר מינימיזציה כברירת מחדל במצב ייצור. 👍
- אם משתמשים בגרסה ישנה יותר של webpack, צריך להתקין את
TerserWebpackPlugin
ולכלול אותו בתהליך הבנייה של webpack. ההסבר המפורט מופיע במסמכים. - קיימים גם פלאגינים אחרים להקטנת קבצים שאפשר להשתמש בהם במקום זאת, כמו BabelMinifyWebpackPlugin ו-ClosureCompilerPlugin.
- אם לא משתמשים בכלל בכלי לאיגוד מודולים, אפשר להשתמש ב-Terser ככלי CLI או לכלול אותו ישירות כתלות.
דחיסה
לפעמים משתמשים במונח 'דחיסה' כדי להסביר איך קוד מצטמצם במהלך תהליך המיניפיקציה, אבל בפועל הקוד לא נדחס במובן המילולי.
דחיסה מתייחסת בדרך כלל לקוד שעבר שינוי באמצעות אלגוריתם לדחיסת נתונים. בניגוד למיניפיקציה שמספקת קוד תקין לחלוטין, צריך לפתוח קוד דחוס לפני שמשתמשים בו.
בכל בקשת HTTP ותגובת HTTP, דפדפנים ושרתי אינטרנט יכולים להוסיף כותרות כדי לכלול מידע נוסף על הנכס שאותו מאחזרים או מקבלים. אפשר לראות את זה בכרטיסייה Headers
בחלונית Network בכלי הפיתוח, שבה מוצגים שלושה סוגים:
- General מייצג כותרות כלליות שרלוונטיות לאינטראקציה שלמה של בקשה ותשובה.
- Response Headers (כותרות תגובה) מציג רשימה של כותרות שספציפיות לתגובה בפועל מהשרת.
- Request Headers (כותרות בקשה) מציג רשימה של כותרות שמצורפות לבקשה על ידי הלקוח.
מעיינים בכותרת accept-encoding
ב-Request Headers
.
accept-encoding
משמש את הדפדפן כדי לציין אילו פורמטים של קידוד תוכן או אלגוריתמים של דחיסה הוא תומך בהם. יש הרבה אלגוריתמים לדחיסת טקסט, אבל יש רק שלושה אלגוריתמים שנתמכים כאן לדחיסה (ולפריסה) של בקשות HTTP ברשת:
- Gzip (
gzip
): פורמט הדחיסה הנפוץ ביותר לאינטראקציות בין שרתים ללקוחות. הוא מבוסס על אלגוריתם Deflate ונתמך בכל הדפדפנים העדכניים. - Deflate (
deflate
): לא בשימוש נפוץ. - Brotli (
br
): אלגוריתם דחיסה חדש יותר שמטרתו לשפר עוד יותר את יחסי הדחיסה, מה שיכול להוביל לטעינה מהירה עוד יותר של הדפים. היא נתמכת בגרסאות העדכניות של רוב הדפדפנים.
אפליקציית הדוגמה במדריך הזה זהה לאפליקציה שנוצרה ב-codelab "הסרת קוד שלא נמצא בשימוש", למעט העובדה שעכשיו נעשה שימוש ב-Express כמסגרת שרת. בקטעים הבאים נסביר על דחיסה סטטית ודחיסה דינמית.
דחיסה דינמית
דחיסה דינמית כוללת דחיסה של נכסים תוך כדי תנועה, בזמן שהדפדפן מבקש אותם.
יתרונות
- אין צורך ליצור ולעדכן גרסאות דחוסות ושמורות של נכסים.
- דחיסה תוך כדי תנועה מתאימה במיוחד לדפי אינטרנט שנוצרים באופן דינמי.
חסרונות
- דחיסת קבצים ברמות גבוהות יותר כדי להשיג יחסי דחיסה טובים יותר נמשכת זמן רב יותר. הדבר עלול לפגוע בביצועים כי המשתמש צריך לחכות עד שהנכסים יידחסו לפני שהשרת ישלח אותם.
דחיסה דינמית עם Node/Express
הקובץ server.js
אחראי להגדרת שרת Node שמארח את האפליקציה.
const express = require('express');
const app = express();
app.use(express.static('public'));
const listener = app.listen(process.env.PORT, function() {
console.log('Your app is listening on port ' + listener.address().port);
});
בשלב הזה, הפקודה רק מייבאת את express
ומשתמשת בתוכנת הביניים express.static
כדי לטעון את כל קובצי ה-HTML, ה-JS וה-CSS הסטטיים בספרייה public/
(וקבצים אלה נוצרים על ידי webpack בכל בנייה).
כדי לוודא שכל הנכסים נדחסים בכל פעם שמבקשים אותם, אפשר להשתמש בספריית התוכנה compression. כדי להתחיל, מוסיפים אותו כdevDependency
בpackage.json
:
"devDependencies": {
//...
"compression": "^1.7.3"
},
מייבאים אותו לקובץ השרת, server.js
:
const express = require('express');
const compression = require('compression');
ואז מוסיפים אותו כתוכנת ביניים לפני שהרכיב express.static
מותקן:
//...
const app = express();
app.use(compression());
app.use(express.static('public'));
//...
עכשיו טוענים מחדש את האפליקציה ובודקים את גודל ה-bundle בחלונית Network.
מ-225KB ל-61.6KB! ב-Response Headers
עכשיו, כותרת content-encoding
מראה שהשרת שולח את הקובץ הזה כשהוא מקודד באמצעות gzip
.
דחיסה סטטית
הרעיון מאחורי דחיסה סטטית הוא לדחוס ולשמור נכסים מראש.
יתרונות
- זמן האחזור בגלל רמות דחיסה גבוהות כבר לא מהווה בעיה. אין צורך לבצע דחיסה של קבצים בזמן אמת כי עכשיו אפשר לאחזר אותם ישירות.
חסרונות
- צריך לדחוס את הנכסים בכל בנייה. זמני הבנייה יכולים להתארך משמעותית אם משתמשים ברמות דחיסה גבוהות.
דחיסה סטטית באמצעות Node/Express ו-webpack
דחיסה סטטית כוללת דחיסה של קבצים מראש, ולכן אפשר לשנות את ההגדרות של webpack כדי לדחוס נכסים כחלק משלב הבנייה.
אפשר להשתמש ב-CompressionPlugin
.
כדי להתחיל, מוסיפים אותו כdevDependency
בpackage.json
:
"devDependencies": {
//...
"compression-webpack-plugin": "^1.1.11"
},
כמו כל פלאגין אחר של webpack, מייבאים אותו לקובץ ההגדרות,
webpack.config.js:
const path = require("path");
//...
const CompressionPlugin = require("compression-webpack-plugin");
וכוללים אותו במערך plugins
:
module.exports = {
//...
plugins: [
//...
new CompressionPlugin()
]
}
כברירת מחדל, הפלאגין דוחס את קובצי ה-build באמצעות gzip
. במסמכי התיעוד מוסבר איך להוסיף אפשרויות לשימוש באלגוריתם אחר או לכלול/להחריג קבצים מסוימים.
כשהאפליקציה נטענת מחדש ונבנית מחדש, נוצרת גרסה דחוסה של החבילה הראשית. פותחים את Glitch Console כדי לראות מה יש בספרייה הסופית public/
שמוגשת על ידי שרת Node.
- לוחצים על הלחצן כלים.
- לוחצים על הלחצן Console.
- במסוף, מריצים את הפקודות הבאות כדי לעבור לספרייה
public
ולראות את כל הקבצים שלה:
cd public
ls
הגרסה הדחוסה ב-gzip של החבילה, main.bundle.js.gz
, נשמרת גם כאן. כברירת המחדל, CompressionPlugin
דוחס גם את index.html
.
הדבר הבא שצריך לעשות הוא להנחות את השרת לשלוח את הקבצים האלה בפורמט gzip בכל פעם שמבוקשות גרסאות ה-JS המקוריות שלהם. אפשר לעשות את זה על ידי הגדרת נתיב חדש ב-server.js
לפני שהקבצים מוגשים באמצעות express.static
.
const express = require('express'); const app = express(); app.get('*.js', (req, res, next) => { req.url = req.url + '.gz'; res.set('Content-Encoding', 'gzip'); next(); }); app.use(express.static('public')); //...
app.get
משמש כדי להגיד לשרת איך להגיב לבקשת GET לנקודת קצה ספציפית. לאחר מכן משתמשים בפונקציית קריאה חוזרת כדי להגדיר איך לטפל בבקשה הזו. כך המסלול פועל:
- הגדרת
'*.js'
כארגומנט הראשון אומרת שהפונקציה הזו פועלת בכל נקודת קצה שמופעלת כדי לאחזר קובץ JS. - בתוך הקריאה החוזרת,
.gz
מצורף לכתובת ה-URL של הבקשה וכותרת התגובהContent-Encoding
מוגדרת ל-gzip
. - לבסוף,
next()
מוודא שהרצף ממשיך לכל קריאה חוזרת שעשויה להיות הבאה.
אחרי שהאפליקציה נטענת מחדש, בודקים שוב את החלונית Network
.
כמו קודם, יש צמצום משמעותי בגודל ה-bundle.
סיכום
ב-codelab הזה למדתם על התהליך של מזעור ודחיסה של קוד מקור. שתי הטכניקות האלה הופכות לשיטת ברירת מחדל ברבים מהכלים שזמינים היום, ולכן חשוב לברר אם שרשרת הכלים שלכם כבר תומכת בהן או אם כדאי להתחיל להשתמש בשני התהליכים בעצמכם.