يستكشف هذا الدرس التطبيقي حول الترميز كيف يؤدي تصغير حزمة JavaScript وضغطها للتطبيق التالي إلى تحسين أداء الصفحة عن طريق تقليل حجم طلب التطبيق.
القياس
قبل التعمق في إضافة التحسينات، يُفضل دائمًا تحليل الحالة الحالية للتطبيق أولاً.
- لمعاينة الموقع الإلكتروني، اضغط على عرض التطبيق، ثم اضغط على ملء الشاشة .
يتيح لك هذا التطبيق، الذي تم تناوله أيضًا في الدرس التطبيقي "إزالة الرمز المبرمَج غير المستخدَم"، التصويت على قطّتك المفضّلة. 🐈
اطّلِع الآن على حجم هذا التطبيق:
- اضغط على "Control+Shift+J" (أو "Command+Option+J" على نظام التشغيل Mac) لفتح "أدوات مطوري البرامج".
- انقر على علامة التبويب الشبكة.
- ضَع علامة في مربّع الاختيار إيقاف ذاكرة التخزين المؤقت.
- أعِد تحميل التطبيق.
على الرغم من أنّه تم إحراز الكثير من التقدّم في "إزالة الرموز غير المستخدَمة" في ورشة العمل البرمجية لتقليل حجم هذه الحِزمة، لا يزال حجمها 225 كيلوبايت كبيرًا جدًا.
تصغير
فكِّر في مجموعة الرموز البرمجية التالية.
function soNice() {
let counter = 0;
while (counter < 100) {
console.log('nice');
counter++;
}
}
إذا تم حفظ هذه الدالة في ملف خاص بها، يكون حجم الملف حوالي 112 بايت (بايت).
في حال إزالة جميع المسافات البيضاء، سيظهر الرمز الناتج على النحو التالي:
function soNice(){let counter=0;while(counter<100){console.log("nice");counter++;}}
سيكون حجم الملف الآن حوالي 83 بايت. إذا تم تشويهه أكثر من خلال تقليل طول اسم المتغيّر وتعديل بعض التعبيرات، قد ينتهي الأمر بأن يظهر الرمز النهائي بالشكل التالي:
function soNice(){for(let i=0;i<100;)console.log("nice"),i++}
يصل حجم الملف الآن إلى 62 بايت.
مع كل خطوة، يصبح من الصعب قراءة التعليمة البرمجية. ومع ذلك، يفسّر محرّك JavaScript في المتصفّح كلّ عنصر من هذه العناصر بالطريقة نفسها تمامًا. إنّ فائدة تشويش الرمز بهذه الطريقة هي تقليل حجم الملفات. لم يكن 112 B كثيرًا في البداية، ولكن كان لا يزال هناك انخفاض في الحجم بنسبة 50٪!
في هذا التطبيق، يتم استخدام الإصدار 4 من webpack كأداة لحِزم الوحدات. يمكن الاطّلاع على الإصدار المحدّد في package.json
.
"devDependencies": {
//...
"webpack": "^4.16.4",
//...
}
يعمل الإصدار 4 على تصغير الحزمة تلقائيًا أثناء وضع الإنتاج. ويستخدم
TerserWebpackPlugin
مكوّنًا إضافيًا Terser.
وTerser هي أداة شائعة تُستخدم لضغط رمز JavaScript.
للحصول على فكرة عن شكل الرمز البرمجي المُكثَّف، انقر على
main.bundle.js
أثناء التواجد في لوحة الشبكة في DevTools. انقر الآن على علامة التبويب
الرد.
يظهر الرمز في شكله النهائي، مُصغّرًا ومُعدَّلاً، في نص الاستجابة.
لمعرفة حجم الحزمة التي كانت ستبلغه في حال عدم تصغيرها، افتحملف webpack.config.js
وعدِّل إعدادات mode
.
module.exports = {
mode: 'production',
mode: 'none',
//...
أعِد تحميل التطبيق واطّلِع على حجم الحزمة مرة أخرى من خلال لوحة الشبكة في IDE.
هذا فرق كبير جدًا. 😅
احرص على التراجع عن التغييرات هنا قبل المتابعة.
module.exports = {
mode: 'production',
mode: 'none',
//...
يعتمد تضمين عملية تصغير الرمز في تطبيقك على الأدوات التي تستخدمها:
- في حال استخدام الإصدار 4 من webpack أو إصدار أحدث، ليس عليك إجراء أي عمل إضافي، لأنّه يتم تصغير الرمز تلقائيًا في وضع الإنتاج. 👍
- في حال استخدام إصدار قديم من webpack، يجب تثبيت
TerserWebpackPlugin
وتضمينها في عملية إنشاء webpack. تشرح المستندات ذلك بالتفصيل. - وتتوفر أيضًا مكونات إضافية أخرى للتصغير ويمكن استخدامها بدلاً منها، مثل BabelMinifyWebpackPlugin وClosureCompilerPlugin.
- إذا لم يتم استخدام أداة تجميع وحدات على الإطلاق، استخدِم Terser كأداة لواجهة سطر أوامر أو أدرِجه مباشرةً كعنصر تابع.
الضغط
على الرغم من أنّ المصطلح "الضغط" يُستخدَم أحيانًا بشكل فضفاض لشرح كيفية تقليل حجم الرمز البرمجي أثناء عملية التصغير، إلا أنّه لا يتم ضغطه في الواقع بالمعنى المقصود.
يشير الضغط عادةً إلى رمز تم تعديله باستخدام خوارزمية ضغط البيانات. على عكس التصغير الذي ينتهي بتوفير رمز برمجي صالح تمامًا، يجب فك ضغط الرمز البرمجي المضغوط قبل استخدامه.
مع كل طلب واستجابة HTTP، يمكن للمتصفّحات وخوادم الويب إضافة رؤوس لتضمين معلومات إضافية عن مادة العرض التي يتم جلبها أو استلامها. ويمكن الاطّلاع على ذلك
في علامة التبويب Headers
ضمن لوحة "شبكة أدوات مطوّري البرامج" حيث تظهر ثلاثة أنواع:
- تمثل القيمة العامة العناوين العامة ذات الصلة بتفاعل الطلب والاستجابة الكامل.
- تعرض عناوين الاستجابة قائمة بالعناوين الخاصة بالاستجابة الفعلية من الخادم.
- تعرِض عناوين الطلبات قائمة بالعناوين التي أرفقها العميل بالطلب.
يمكنك إلقاء نظرة على عنوان accept-encoding
في Request Headers
.
يستخدم المتصفّح accept-encoding
لتحديد تنسيقات ترميز المحتوى أو خوارزميات الضغط المتوافقة. هناك العديد من
خوارزميات ضغط النصوص، ولكن هناك ثلاث خوارزميات فقط
متوافقة هنا لضغط (وفك ضغط) طلبات شبكة HTTP:
- Gzip (
gzip
): تنسيق الضغط الأكثر استخدامًا لتفاعلات الخادم والعميل. وتستند هذه الميزة إلى خوارزمية Deflate، وهي متوافقة مع جميع المتصفّحات الحالية. - Deflate (
deflate
): لا يتم استخدامه بشكل شائع. - Brotli (
br
): خوارزمية ضغط أحدث تهدف إلى تحسين نسب الضغط بشكلٍ أكبر، ما يمكن أن يؤدي إلى loadingتحميل الصفحات بشكلٍ أسرع. وهو متاح في أحدث إصدارات معظم المتصفّحات.
نموذج التطبيق في هذا الدليل التعليمي مطابق للتطبيق الذي أكملته في الدرس التطبيقي حول الترميز بعنوان "إزالة الرمز البرمجي غير المستخدَم"، باستثناء أنّه يتم الآن استخدام مكتبة Express كإطار عمل لخادم. في القسمين التاليين، تتم مناقشة كل من الضغط الثابت والديناميكي.
الضغط الديناميكي
يتضمن الضغط الديناميكي ضغط مواد العرض بشكل فوري عند طلبها من المتصفّح.
الإيجابيات
- لا حاجة إلى إنشاء وتحديث النُسخ المضغوطة المحفوظة من مواد العرض.
- يعمل الضغط أثناء التنقل بشكلٍ جيد بشكلٍ خاص مع صفحات الويب التي يتم إنشاؤها ديناميكيًا.
السلبيات
- إنّ ضغط الملفات بمستويات أعلى لتحقيق نسب ضغط أفضل يستغرق وقتًا أطول. وقد يؤدّي ذلك إلى نتيجة أداء بينما ينتظر المستخدم ضغط مواد العرض قبل إرسالها من خلال الخادم.
الضغط الديناميكي باستخدام Node/Express
يكون الملف server.js
مسؤولاً عن إعداد خادم العُقدة الذي يستضيف التطبيق.
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/
(وتنشئ أداة webpack هذه الملفات مع كل عملية إنشاء).
للتأكّد من ضغط جميع مواد العرض في كل مرة يتم فيها طلبها، يمكن استخدام مكتبة البرمجيات الوسيطة compression. ابدأ بإضافة المنتج كـ devDependency
في package.json
:
"devDependencies": {
//...
"compression": "^1.7.3"
},
واستورِده إلى ملف الخادم server.js
:
const express = require('express');
const compression = require('compression');
يمكنك إضافتها كبرمجية وسيطة قبل تثبيت express.static
:
//...
const app = express();
app.use(compression());
app.use(express.static('public'));
//...
يجب الآن إعادة تحميل التطبيق وإلقاء نظرة على حجم الحزمة في لوحة الشبكة.
من 225 كيلوبايت إلى 61.6 كيلوبايت في Response Headers
الآن، يعرض عنوان content-encoding
أنّ الخادم يُرسِل هذا الملف المشفَّر باستخدام gzip
.
الضغط الثابت
إنّ الفكرة من ضغط المواد الثابتة هي ضغط مواد العرض وحفظها مسبقًا.
الإيجابيات
- لم يعُد وقت الاستجابة بسبب مستويات الضغط العالية مصدر قلق. ولا يلزم إجراء أيّ عملية أثناء ضغط الملفات، إذ يمكن جلبها مباشرةً.
السلبيات
- يجب ضغط مواد العرض مع كل إصدار. يمكن أن تزيد مدّة الإنشاء بشكل كبير في حال استخدام مستويات ضغط عالية.
الضغط الثابت باستخدام Node/Express وWebpack
بما أنّ ميزة "الضغط الثابت" تتضمن ضغط الملفات مسبقًا، يمكن تعديل إعدادات webpack
لضغط مواد العرض كجزء من خطوة الإنشاء.
يمكن استخدام CompressionPlugin
لهذا الغرض.
ابدأ بإضافة المنتج كـ devDependency
في package.json
:
"devDependencies": {
//...
"compression-webpack-plugin": "^1.1.11"
},
استورِده في ملف الإعدادات، مثل أي مكوّن إضافي آخر من webpack،
webpack.config.js:
const path = require("path");
//...
const CompressionPlugin = require("compression-webpack-plugin");
وأدرِج هذا العنصر في مصفوفة plugins
:
module.exports = {
//...
plugins: [
//...
new CompressionPlugin()
]
}
يضغط المكوّن الإضافي ملفات التصميم تلقائيًا باستخدام gzip
. اطّلِع على المستندات
لمعرفة كيفية إضافة خيارات لاستخدام خوارزمية مختلفة أو تضمين/استبعاد
ملفات معيّنة.
عند إعادة تحميل التطبيق وإعادة إنشائه، يتم الآن إنشاء نسخة مضغوطة من الحزمة الرئيسية. افتح Glitch Console للاطّلاع على ما بداخل directory
public/
النهائي الذي يعرضه خادم Node.
- انقر على زر الأدوات.
- انقر على الزر وحدة التحكّم.
- في وحدة التحكّم، نفِّذ الأوامر التالية للانتقال إلى الدليل
public
والاطّلاع على جميع ملفاته:
cd public
ls
تم الآن حفظ الإصدار المضغوط من الحزمة main.bundle.js.gz
المضغوط بتنسيق gzip
هنا أيضًا. يضغط CompressionPlugin
أيضًا index.html
تلقائيًا.
إن الشيء التالي الذي يجب القيام به هو إخبار الخادم بإرسال هذه الملفات
المضغوطة بطريقة gzip كلما تم طلب إصدارات JS الأصلية. ويمكن إجراء ذلك
من خلال تحديد مسار جديد في server.js
قبل عرض الملفات باستخدام
express.static
.
const express = require('express'); const app = express(); app.get('*.js', (req, res, next) => { req.url = req.url + '.gz'; res.set('Content-Encoding', 'gzip'); next(); }); app.use(express.static('public')); //...
يتم استخدام app.get
لإعلام الخادم بطريقة الردّ على طلب GET لنقطة نهاية
معيّنة. بعد ذلك، يتم استخدام دالة ردّ اتصال لتحديد كيفية معالجة
هذا الطلب. ويعمل المسار على النحو التالي:
- عند تحديد
'*.js'
كوسيطة أولى، يتم استخدام هذه الطريقة مع كل نقطة نهاية يتم تنشيطها لاسترجاع ملف JS. - ضمن طلب معاودة الاتصال، يتم إرفاق
.gz
بعنوان URL للطلب ويتم ضبط عنوان الاستجابةContent-Encoding
علىgzip
. - وأخيرًا، يضمن
next()
استمرار التسلسل في أي استدعاء قد يكون التالي.
بعد إعادة تحميل التطبيق، اطّلِع على لوحة Network
مرة أخرى.
وكما حدث من قبل، حدث انخفاض كبير في حجم الحزمة.
الخاتمة
تناول هذا الدرس التطبيقي حول الترميز عملية تصغير رمز المصدر وضغطه. أصبحت هاتان الطريقتان افتراضيًا في العديد من الأدوات المتاحة اليوم، لذا من المهم معرفة ما إذا كانت سلسلة الأدوات لديك تدعمهما بالفعل أو ما إذا كان يجب عليك البدء في تطبيق كلتا العمليتين بنفسك.