הקטנה ודחיסה של מטענים ייעודיים (payload) ברשת באמצעות brotli

Michael DiBlasio
Michael DiBlasio

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

צילום מסך של האפליקציה

מדידה

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

  1. לוחצים על Remix to Edit כדי להפוך את הפרויקט לעריכה.
  2. כדי לראות תצוגה מקדימה של האתר, לוחצים על הצגת האפליקציה ואז על מסך מלא מסך מלא.

ב-codelab הקודם בנושא הקטנה ודחיסה של מטענים ייעודיים (payload) ברשת, צמצמנו את הגודל של main.js מ-225KB ל-61.6KB. ב-codelab הזה נסביר איך אפשר להקטין עוד יותר את גודל החבילה באמצעות דחיסת Brotli.

דחיסת Brotli

Brotli הוא אלגוריתם דחיסה חדש יותר שיכול לספק תוצאות דחיסה טובות יותר של טקסט מאשר gzip. לפי CertSimple, הביצועים של Brotli הם:

  • ‫14% קטן יותר מ-gzip עבור JavaScript
  • קטן ב-21% מהגודל של gzip ב-HTML
  • ‫17% קטן יותר מ-gzip עבור CSS

כדי להשתמש ב-Brotli, השרת צריך לתמוך ב-HTTPS. ‫Brotli נתמך בכל הדפדפנים המודרניים. בדפדפנים שתומכים ב-Brotli, הערך br יופיע בכותרות Accept-Encoding:

Accept-Encoding: gzip, deflate, br

אפשר לקבוע באיזה אלגוריתם דחיסה נעשה שימוש באמצעות השדה Content-Encoding בכרטיסייה 'רשת' ב'כלים למפתחים ב-Chrome' (Command+Option+I או Ctrl+Alt+I):

חלונית הרשת. בעמודה Content-encoding מוצגים הקידודים שמשמשים לנכסים שונים, כולל gzip ו-brotli ‏ (br).

איך מפעילים את Brotli

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

דחיסה דינמית

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

יתרונות

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

חסרונות

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

דחיסה דינמית באמצעות 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/directory (וקבצים אלה נוצרים על ידי webpack בכל בנייה).

כדי לוודא שכל הנכסים נדחסים באמצעות brotli בכל פעם שהם מבוקשים, אפשר להשתמש במודול shrink-ray. כדי להתחיל, מוסיפים אותו כdevDependency בpackage.json:

"devDependencies": {
  // ...
  "shrink-ray": "^0.1.3"
},

מייבאים אותו לקובץ השרת, server.js:

const express = require('express');
const shrinkRay = require('shrink-ray');

מוסיפים אותו כתוכנת ביניים לפני שהרכיב express.static מותקן:

// ...
const app = express();

// Compress all requests
app.use(shrinkRay());
app.use(express.static('public'));

עכשיו טוענים מחדש את האפליקציה ומסתכלים על גודל ה-bundle בחלונית Network:

גודל החבילה עם דחיסת Brotli דינמית.

מעכשיו אפשר לראות שההגדרה brotli חלה מ-bz בכותרת Content-Encoding. הגודל של main.bundle.js ירד מ-225KB ל-53.1KB. הגודל קטן בכ-14% בהשוואה ל-gzip (61.6KB).

דחיסה סטטית

הרעיון מאחורי דחיסה סטטית הוא לדחוס ולשמור נכסים מראש.

יתרונות

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

חסרונות

  • צריך לדחוס את הנכסים בכל בנייה. זמני הבנייה יכולים להתארך משמעותית אם משתמשים ברמות דחיסה גבוהות.

דחיסה סטטית באמצעות Node ו-Express עם webpack

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

כדי להתחיל, מוסיפים אותו כdevDependency בpackage.json:

"devDependencies": {
  // ...
 "brotli-webpack-plugin": "^1.1.0"
},

כמו כל פלאגין אחר של webpack, מייבאים אותו לקובץ ההגדרות, webpack.config.js:

var path = require("path");

//...
var BrotliPlugin = require('brotli-webpack-plugin');

ומוסיפים אותו למערך התוספים:

module.exports = {
  // ...
  plugins: [
    // ...
    new BrotliPlugin({
      asset: '[file].br',
      test: /\.(js)$/
    })
  ]
},

מערך התוספים משתמש בארגומנטים הבאים:

  • asset: שם נכס היעד.
  • מחליפים את [file] בשם הקובץ המקורי של הנכס.
  • test: כל הנכסים שתואמים לביטוי הרגולרי הזה (כלומר, נכסי JavaScript שמסתיימים ב-.js) יעברו עיבוד.

לדוגמה, main.js ישונה ל-main.js.br.

כשהאפליקציה נטענת מחדש ונבנית מחדש, נוצרת גרסה דחוסה של החבילה הראשית. פותחים את Glitch Console כדי לראות מה יש בספרייה הסופית public/ שמוצגת על ידי שרת Node.

  1. לוחצים על הלחצן כלים.
  2. לוחצים על הלחצן Console.
  3. במסוף, מריצים את הפקודות הבאות כדי לעבור לספרייה public ולראות את כל הקבצים שלה:
cd public
ls -lh
גודל ה-Bundle עם דחיסת Brotli סטטית

הגרסה הדחוסה של החבילה בפורמט Brotli,‏ main.bundle.js.br, נשמרת גם כאן, והגודל שלה קטן בכ-76% (‎225 KB לעומת ‎53 KB) בהשוואה ל-main.bundle.js.

לאחר מכן, צריך להגדיר לשרת לשלוח את הקבצים האלה שנדחסו באמצעות Brotli בכל פעם שמתקבלת בקשה לגרסאות ה-JS המקוריות שלהם. כדי לעשות זאת, צריך להגדיר נתיב חדש ב-server.js לפני שהקבצים מוגשים באמצעות express.static.

const express = require('express');
const app = express();

app.get('*.js', (req, res, next) => {
  req.url = req.url + '.br';
  res.set('Content-Encoding', 'br');
  res.set('Content-Type', 'application/javascript; charset=UTF-8');
  next();
});

app.use(express.static('public'));

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

  • הגדרת '*.js' כארגומנט הראשון אומרת שהפונקציה הזו פועלת בכל נקודת קצה שמופעלת כדי לאחזר קובץ JS.
  • בתוך הקריאה החוזרת, .br מצורף לכתובת ה-URL של הבקשה וכותרת התגובה Content-Encoding מוגדרת ל-br.
  • הכותרת Content-Type מוגדרת לערך application/javascript; charset=UTF-8 כדי לציין את סוג ה-MIME.
  • לבסוף, next() מוודא שהרצף ממשיך לכל קריאה חוזרת שעשויה להיות הבאה.

יכול להיות שדפדפנים מסוימים לא תומכים בדחיסת brotli, ולכן צריך לוודא שהדחיסה נתמכת לפני שמחזירים את הקובץ הדחוס ב-brotli. כדי לעשות זאת, בודקים אם כותרת הבקשה Accept-Encoding כוללת את br:

const express = require('express');
const app = express();

app.get('*.js', (req, res, next) => {
  if (req.header('Accept-Encoding').includes('br')) {
    req.url = req.url + '.br';
    console.log(req.header('Accept-Encoding'));
    res.set('Content-Encoding', 'br');
    res.set('Content-Type', 'application/javascript; charset=UTF-8');
  }

  next();
});

app.use(express.static('public'));

אחרי שהאפליקציה נטענת מחדש, בודקים שוב את החלונית Network (רשת).

גודל החבילה 53.1KB (מתוך 225KB)

הצלחת! השתמשת בדחיסת Brotli כדי לדחוס עוד יותר את הנכסים שלך.

סיכום

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