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

Michael DiBlasio
Michael DiBlasio

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

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

מדידה

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

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

בגרסה הקודמת של Minify ו-דחיסה של מטענים ייעודיים (payloads) ברשת, צמצמנו את הגודל של main.js מ-225KB ל-61.6KB. בסדנת הקוד הזו תלמדו איך דחיסת 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 בכל גרסת build).

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

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

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

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

ומוסיפים אותו כתוכנה לעיבוד נתונים (middleware) לפני שמרכז הנתונים express.static מוצמד:

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

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

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

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

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

דחיסה סטטית

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

יתרונות

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

חסרונות

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

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

מאחר שדחיסה סטטית כוללת דחיסת קבצים מראש, אפשר לשנות את ההגדרות של webpack כדי לדחוס נכסים כחלק משלב ה-build. אפשר להשתמש ב-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: כל הנכסים שתואמים ל-RegExp הזה (כלומר, נכסי JavaScript שמסתיימים ב-.js) עוברים עיבוד.

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

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

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

גם הגרסה הדחוסה של brotli, main.bundle.js.br, נשמרת עכשיו כאן וגודלה כ-76% קטן יותר (225KB לעומת 53KB) ביחס ל-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() מוודא שהרצף ממשיך לכל קריאה חוזרת (callback) שתתבצע לאחר מכן.

יכול להיות שחלק מהדפדפנים לא תומכים בדחיסת brotli, לכן חשוב לוודא שיש תמיכה ב-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'));

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

גודל החבילה הוא 53.1KB (מ-225KB)

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

סיכום

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