تصغير حمولات الشبكة وضغطها باستخدام 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 وJavaScript و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 الثابت

تم حفظ نسخة brotli المضغوطة من الحزمة، main.bundle.js.br، هنا أيضًا كما أصبح حجمها أصغر بنسبة 76% تقريبًا (225 كيلوبايت مقابل 53 كيلوبايت) من 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'));

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

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

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

الخاتمة

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