تصغير حمولات الشبكة وضغطها باستخدام brotli

هذا الدرس التطبيقي حول الترميز هو امتداد لدرس تصغير حِزم بيانات الشبكة وضغطها التطبيقي، ويفترض أنّك على دراية بأساسيات ضغط البيانات. مقارنةً بالخوارزميات الأخرى للضغط، مثل gzip، يستكشف هذا الدليل التعليمي كيفية استخدام ضغط Brotli (br) للحدّ بشكل أكبر من نسب الضغط وحجم تطبيقك بشكل عام.

لقطة شاشة التطبيق

القياس

قبل البدء في إضافة تحسينات، من الأفضل دائمًا تحليل الحالة الحالية للتطبيق أولاً.

  1. انقر على Remix to Edit (إنشاء ريمكس لتعديل المحتوى) ليصبح المشروع قابلاً للتعديل.
  2. لمعاينة الموقع الإلكتروني، اضغط على عرض التطبيق. ثم اضغط على ملء الشاشة ملء الشاشة.

في الدورة التدريبية السابقة حول تصغير حمولات البيانات على الشبكة وضغطها، تم تقليل حجم main.js من 225 كيلوبايت إلى 61.6 كيلوبايت. في هذا الدليل التعليمي حول الرموز البرمجية، ستستكشف كيف يمكن أن يؤدي ضغط 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):

لوحة الشبكة يعرض عمود "ترميز المحتوى" ترميزات مواد العرض المختلفة، بما في ذلك 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'));

أعِد الآن تحميل التطبيق، وألقِ نظرة على حجم الحزمة في لوحة "الشبكة":

حجم الحِزمة باستخدام ضغط Brotli الديناميكي

يمكنك الآن الاطّلاع على أنّه تم تطبيق brotli من bz في عنوان Content-Encoding. تم تقليل حجم main.bundle.js من 225 كيلوبايت إلى 53.1 كيلوبايت. وهذا أصغر بنسبة% 14 تقريبًا مقارنةً بـ gzip (61.6 كيلوبايت).

الضغط الثابت

تتمثل الفكرة وراء ميزة "الضغط الثابت" في ضغط مواد العرض وحفظها مسبقًا.

الإيجابيات

  • لم تعُد مدة الاستجابة بسبب مستويات الضغط العالية مصدر قلق. لا يلزم إجراء أي عملية أثناء ضغط الملفات، إذ يمكن جلبها مباشرةً.

السلبيات

  • يجب ضغط مواد العرض مع كل عملية إنشاء. يمكن أن تزيد مدّة الإنشاء بشكل كبير في حال استخدام مستويات ضغط عالية.

الضغط الثابت باستخدام 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: تتم معالجة جميع مواد العرض التي تتطابق مع تعبير RegExp هذا (أي مواد عرض JavaScript التي تنتهي بعلامة .js).

على سبيل المثال، ستتم إعادة تسمية main.js إلى main.js.br.

عند إعادة تحميل التطبيق وإعادة إنشائه، يتم الآن إنشاء نسخة مضغوطة من الحزمة الرئيسية. افتح Glitch Console للاطّلاع على المحتوى المتوفّر في الدليل النهائي public/ الذي يعرضه خادم Node.

  1. انقر على زر الأدوات.
  2. انقر على الزر وحدة التحكّم.
  3. في وحدة التحكّم، نفِّذ الأوامر التالية للانتقال إلى الدليل public والاطّلاع على جميع ملفاته:
cd public
ls -lh
حجم الحِزمة باستخدام ضغط Brotli الثابت

تم حفظ الإصدار المضغوط من الحزمة main.bundle.js.br باستخدام تقنية brotli هنا أيضًا، وهو أصغر حجمًا بنسبة% 76 تقريبًا (225 كيلوبايت مقارنةً بـ 53 كيلوبايت) مقارنةً بملف main.bundle.js.

بعد ذلك، عليك إرسال تعليمات إلى الخادم لإرسال هذه الملفات المضغوطة بتنسيق brotli عند طلب إصدارات JavaScript الأصلية. ويمكن إجراء ذلك من خلال تحديد مسار جديد في 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'));

بعد إعادة تحميل التطبيق، اطّلِع على لوحة "الشبكة" مرة أخرى.

حجم الحزمة 53.1 كيلوبايت (من 225 كيلوبايت)

اكتمال عملية النقل بنجاح لقد استخدمت ضغط Brotli لضغط مواد العرض بشكل أكبر.

الخاتمة

توضّح ورشة رموز البرامج هذه كيفية مساعدة brotli في تقليل حجم تطبيقك بشكلٍ أكبر. إنّ brotli هي خوارزمية ضغط أكثر فعالية من gzip، وذلك في الحالات التي تتوفّر فيها.