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

Michael DiBlasio
Michael DiBlasio

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

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

القياس

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

  1. انقر على إنشاء ريمكس للتعديل لجعل المشروع قابلاً للتعديل.
  2. لمعاينة الموقع الإلكتروني، انقر على عرض التطبيق، ثم انقر على ملء الشاشة ملء الشاشة.

في درس Minify and compress network payloads البرمجي السابق، قلّلنا حجم 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: تتم معالجة جميع مواد العرض التي تتطابق مع هذا التعبير العادي (أي مواد عرض 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 متوافق قبل عرض الملف المضغوط باستخدام 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.