تاريخ النشر: 31 آذار (مارس) 2014
يتطلّب تحديد نقاط الاختناق في أداء مسار التقديم المهم وحلّها معرفة جيدة بالأخطاء الشائعة. ستساعدك جولة إرشادية لتحديد أنماط الأداء الشائعة في تحسين صفحاتك.
من خلال تحسين مسار العرض المهم، يمكن للمتصفّح عرض الصفحة في أسرع وقت ممكن، ما يؤدي إلى زيادة التفاعل وعرض المزيد من الصفحات وتحسين الإحالات الناجحة. لتقليل الوقت الذي يقضيه الزائر في مشاهدة شاشة فارغة، علينا تحسين الموارد التي يتم تحميلها والترتيب الذي يتم تحميلها به.
للمساعدة في توضيح هذه العملية، ابدأ بأبسط حالة ممكنة وطوِّر صفحتنا تدريجيًا لتضمين موارد وأنماط ومنطق تطبيق إضافي. وخلال هذه العملية، سنحسِّن كل حالة، وسنحدِّد أيضًا المشاكل المحتملة.
حتى الآن، ركّزنا حصريًا على ما يحدث في المتصفّح بعد توفّر المورد (ملف CSS أو JS أو HTML) للمعالجة. لقد تجاهلنا الوقت الذي يستغرقه جلب المورد من ذاكرة التخزين المؤقت أو من الشبكة. سنفترض ما يلي:
- تستغرق رحلة الذهاب والعودة للشبكة (وقت استجابة الانتشار) إلى الخادم 100 ملي ثانية.
- يبلغ وقت استجابة الخادم 100 ملي ثانية لمستند HTML و10 ملي ثانية لجميع الملفات الأخرى.
تجربة "مرحبًا بك"
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Critical Path: No Style</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
</body>
</html>
ابدأ باستخدام علامات HTML الأساسية وصورة واحدة، بدون استخدام CSS أو JavaScript. بعد ذلك، افتح لوحة "الشبكة" في "أدوات مطوّري البرامج في Chrome" وتحقّق من تدفق الموارد الناتج:
كما هو متوقّع، استغرق تنزيل ملف HTML حوالي 200 ملي ثانية. يُرجى العِلم أنّ الجزء الشفاف من الخطّ الأزرق يمثّل المدّة التي ينتظر فيها المتصفّح على الشبكة بدون تلقّي أي وحدات بايت من الاستجابة، في حين يعرض الجزء المُكتَمِل الوقت الذي يستغرقه إكمال عملية التنزيل بعد تلقّي وحدات البايت الأولى من الاستجابة. حجم تنزيل HTML صغير (<4K)، لذا كل ما نحتاجه هو رحلة ذهاب وإياب واحدة لجلب الملف الكامل. ونتيجةً لذلك، يستغرق جلب مستند HTML حوالي 200 ملي ثانية، ويقضي نصف هذا الوقت في الانتظار على الشبكة والنصف الآخر في الانتظار على استجابة الخادم.
عندما يصبح محتوى HTML متاحًا، يحلّل المتصفّح وحدات البايت ويحوّلها إلى وحدات ترميز وينشئ شجرة نموذج كائن المستند (DOM). يُرجى ملاحظة أنّ "أدوات المطوّر" تسجِّل وقت حدث DOMContentLoaded في أسفل الصفحة (216 ملي ثانية) بشكلٍ ملائم، وهو ما يتوافق أيضًا مع الخط العمودي الأزرق. الفجوة بين نهاية تنزيل HTML والخط العمودي الأزرق (DOMContentLoaded) هي الوقت الذي يستغرقه المتصفّح لإنشاء شجرة DOM، وفي هذه الحالة، لا تتجاوز بضع مللي ثوانٍ.
يُرجى ملاحظة أنّ "الصورة الرائعة" لم تحظر الحدث domContentLoaded
. تبيّن لنا أنّه يمكننا إنشاء شجرة العرض وحتى عرض الصفحة بدون انتظار كل مادة عرض على الصفحة: ليست كل الموارد مهمة لتقديم سرعة عرض الصفحة الأولى. في الواقع، عندما نتحدث عن مسار العرض المهم، نقصد عادةً ترميز HTML وCSS وJavaScript. لا تحظر الصور العرض الأوّلي للصفحة، ولكن يجب أيضًا محاولة عرض الصور في أقرب وقت ممكن.
ومع ذلك، تم حظر حدث load
(المعروف أيضًا باسم onload
) في الصورة: تُبلغ "أدوات مطوّري البرامج" عن حدث onload
بعد 335 ملي ثانية. يُرجى تذكُّر أنّ الحدث onload
يشير إلى النقطة التي تم فيها تنزيل جميع الموارد التي تتطلّبها الصفحة ومعالجتها. وفي هذه المرحلة، يمكن أن يتوقف مؤشر التحميل عن الدوران في المتصفّح (الخطّ العمودي الأحمر في المخطّط البياني).
إضافة JavaScript وCSS إلى المحتوى
قد تبدو صفحة "تجربة Hello World" بسيطة، ولكن يتم تنفيذ الكثير من الإجراءات في الخلفية. في الواقع، سنحتاج إلى أكثر من صفحات HTML فقط: من المحتمل أن يكون لدينا ورقة أنماط CSS ونص برمجي واحد أو أكثر لإضافة بعض التفاعل إلى صفحتنا. أضِف كلاهما إلى المحتوى لمعرفة النتيجة:
<!DOCTYPE html>
<html>
<head>
<title>Critical Path: Measure Script</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
</head>
<body onload="measureCRP()">
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script src="timing.js"></script>
</body>
</html>
قبل إضافة JavaScript وCSS:
باستخدام JavaScript وCSS:
تؤدي إضافة ملفات CSS وJavaScript الخارجية إلى إضافة طلبَين إضافيَين إلى العرض المعروض بشكلٍ متواصل، ويرسل المتصفّح جميع هذه الطلبات في الوقت نفسه تقريبًا. ومع ذلك، يُرجى العِلم أنّ هناك الآن فرقًا زمنيًا أصغر بكثير بين حدثَي domContentLoaded
وonload
.
ما السبب؟
- على عكس مثال HTML العادي، نحتاج أيضًا إلى جلب ملف CSS وتحليله لإنشاء CSSOM، ونحتاج إلى كلّ من DOM وCSSOM لإنشاء شجرة العرض.
- بما أنّ الصفحة تحتوي أيضًا على ملف JavaScript يحظر المُحلِّل، يتم حظر الحدث
domContentLoaded
إلى أن يتم تنزيل ملف CSS وتحليله: لأنّ JavaScript قد يطلب بيانات من CSSOM، يجب حظر ملف CSS إلى أن يتم تنزيله قبل أن نتمكّن من تنفيذ JavaScript.
ماذا لو استبدلنا النص البرمجي الخارجي بنص برمجي مضمّن؟ حتى إذا تم تضمين البرنامج النصي مباشرةً في الصفحة، لا يمكن للمتصفّح تنفيذه إلى أن يتم إنشاء CSSOM. بعبارة أخرى، يؤدي تضمين JavaScript إلى حظر المُحلِّل اللغوي أيضًا.
ومع ذلك، على الرغم من الحظر على CSS، هل يؤدي تضمين النص البرمجي إلى عرض الصفحة بشكل أسرع؟ جرِّب ذلك وراقِب النتائج.
JavaScript الخارجية:
JavaScript المضمّنة:
نُجري طلبًا واحدًا أقل، ولكنّ وقتَي onload
وdomContentLoaded
متطابقان بشكلٍ أساسي. لماذا؟ نعلم أنّه لا يهمّ ما إذا كان JavaScript مضمّنًا أو خارجيًا، لأنّه بمجرد أن يصل المتصفّح إلى علامة النص البرمجي، يتم حظره وينتظر إلى أن يتم إنشاء CSSOM. بالإضافة إلى ذلك، في المثال الأول، ينزِّل المتصفّح كلّ من CSS وJavaScript بشكل موازٍ وينتهي من تنزيلهما في الوقت نفسه تقريبًا. في هذه الحالة، لا يساعدنا تضمين رمز JavaScript كثيرًا. ولكن هناك عدة استراتيجيات يمكنها تسريع عرض صفحتنا.
أولاً، تذكَّر أنّ جميع النصوص البرمجية المضمّنة تحظر المُحلِّل، ولكن بالنسبة إلى النصوص البرمجية الخارجية، يمكننا إضافة السمة async
لإزالة حظر المُحلِّل. يمكنك إلغاء عملية التضمين وتجربة ما يلي:
<!DOCTYPE html>
<html>
<head>
<title>Critical Path: Measure Async</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
</head>
<body onload="measureCRP()">
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script async src="timing.js"></script>
</body>
</html>
JavaScript (الخارجية) التي تحظر المُحلِّل:
JavaScript غير المتزامنة (الخارجية):
أفضل بكثير. يتم تشغيل الحدث domContentLoaded
بعد وقت قصير من تحليل HTML، ويعرف المتصفّح عدم حظر JavaScript، وبما أنّه لا تتوفّر نصوص برمجية أخرى تحظر التحليل، يمكن أيضًا مواصلة إنشاء CSSOM بشكل متزامن.
بدلاً من ذلك، يمكننا تضمين كلّ من CSS وJavaScript:
<!DOCTYPE html>
<html>
<head>
<title>Critical Path: Measure Inlined</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<style>
p {
font-weight: bold;
}
span {
color: red;
}
p span {
display: none;
}
img {
float: right;
}
</style>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script>
var span = document.getElementsByTagName('span')[0];
span.textContent = 'interactive'; // change DOM text content
span.style.display = 'inline'; // change CSSOM property
// create a new element, style it, and append it to the DOM
var loadTime = document.createElement('div');
loadTime.textContent = 'You loaded this page on: ' + new Date();
loadTime.style.color = 'blue';
document.body.appendChild(loadTime);
</script>
</body>
</html>
يُرجى ملاحظة أنّ وقت domContentLoaded
هو نفسه في المثال السابق، ولكن بدلاً من وضع علامة على JavaScript على أنّه غير متزامن، أضفنا كلّ من CSS وJS إلى الصفحة نفسها. يؤدي ذلك إلى زيادة حجم صفحة HTML بشكل كبير، ولكن الجانب الإيجابي هو أنّ المتصفّح لا يحتاج إلى الانتظار لجلب أي موارد خارجية، فكل شيء متوفّر في الصفحة.
كما يمكنك أن ترى، حتى مع استخدام صفحة أساسية جدًا، فإنّ تحسين مسار العرض المهمّ ليس عملية بسيطة: علينا فهم الرسم البياني للتبعية بين الموارد المختلفة، وتحديد الموارد "المهمة"، واختيار استراتيجية من بين الاستراتيجيات المختلفة لكيفية تضمين هذه الموارد في الصفحة. لا يوجد حل واحد لهذه المشكلة، لأنّ كل صفحة مختلفة. عليك اتّباع عملية مماثلة بنفسك لمعرفة الاستراتيجية المثلى.
مع ذلك، لنلقِ نظرة على بعض أنماط الأداء العامة.
أنماط الأداء
تتألف أبسط صفحة ممكنة من ترميز HTML فقط، بدون CSS أو JavaScript أو أنواع أخرى من الموارد. لعرض هذه الصفحة، على المتصفّح بدء الطلب، والانتظار حتى يصل مستند HTML، وتحليله، وإنشاء عنصر DOM، ثم عرضه أخيرًا على الشاشة:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Critical Path: No Style</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
</body>
</html>
يُسجِّل الوقت بين T0 وT1 أوقات معالجة الشبكة والخادم. في أفضل الأحوال (إذا كان ملف HTML صغيرًا)، يمكن جلب المستند بأكمله من خلال رحلة واحدة فقط على الشبكة. بسبب طريقة عمل بروتوكولات النقل في بروتوكول TCP، قد تتطلّب الملفات الأكبر حجمًا المزيد من عمليات النقل ذهابًا وإيابًا. نتيجةً لذلك، في أفضل الأحوال، تحتوي الصفحة أعلاه على مسار عرض مهم (بحد أدنى) يتضمن رحلة ذهاب وإياب واحدة.
لنلقِ نظرة الآن على الصفحة نفسها، ولكن مع ملف CSS خارجي:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
</body>
</html>
مرة أخرى، نحتاج إلى جولة ذهاب وإياب على الشبكة لاسترداد مستند HTML، ثم يُعلمنا الترميز الذي تم استرجاعه بأنّنا نحتاج أيضًا إلى ملف CSS، ما يعني أنّه على المتصفّح الرجوع إلى الخادم والحصول على CSS قبل أن يتمكّن من عرض الصفحة على الشاشة. نتيجةً لذلك، تتطلّب هذه الصفحة إجراء عمليتين ذهابًا وإيابًا على الأقل قبل أن يتم عرضها. مرة أخرى، قد يستغرق ملف CSS عدة عمليات ذهاب وإياب، وبالتالي التركيز على "الحد الأدنى".
في ما يلي بعض المصطلحات التي نستخدمها لوصف مسار العرض الحرج:
- المورد المهم: هو المورد الذي يمكن أن يؤدي إلى حظر العرض الأوّلي للصفحة.
- طول المسار الحرج: عدد عمليات التنقّل ذهابًا وإيابًا، أو إجمالي الوقت المطلوب لجلب جميع الموارد الحرجة
- وحدات البايت المهمة: إجمالي عدد وحدات البايت المطلوبة للوصول إلى العرض الأول للصفحة، وهو مجموع أحجام ملفات النقل لجميع الموارد المهمة. في المثال الأول الذي يتضمّن صفحة HTML واحدة، كان هناك مورد مهم واحد (مستند HTML)، وكان طول المسار المهم أيضًا مساويًا لرحلة ذهاب وإياب واحدة على الشبكة (على افتراض أنّ الملف صغير)، وكان إجمالي عدد البايتات المهمة هو حجم نقل مستند HTML نفسه فقط.
قارِن ذلك الآن بخصائص المسار الحرج في مثال HTML وCSS السابق:
- موردَان مهمّان
- عمليتان أو أكثر من تنقّلات ذهاباً وإيابًا للحد الأدنى لطول المسار الحرج
- 9 كيلوبايت من البايتات المهمة
نحتاج إلى HTML وCSS لإنشاء شجرة العرض. نتيجةً لذلك، يُعدّ كلّ من HTML وCSS موردَين مهمّين: لا يتمّ جلب CSS إلا بعد أن يحصل المتصفّح على مستند HTML، وبالتالي يبلغ طول المسار الحرج جولتين على الأقل. يضيف كلا الموردَين ما مجموعه 9 كيلوبايت من البايتات المهمة.
الآن أضِف ملف JavaScript إضافيًا إلى المجموعة.
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script src="app.js"></script>
</body>
</html>
أضفنا app.js
، وهي مادة عرض JavaScript خارجية على الصفحة ومورد حظر (أي مهم) للمحلِّل. والأسوأ من ذلك، أنّه لتنفيذ ملف JavaScript، علينا حظر CSSOM والانتظار إلى أن يتم تنزيل style.css
وإنشاء CSSOM.
ومع ذلك، من الناحية العملية، إذا اطّلعنا على "العرض المرئي للشبكة" لهذه الصفحة، ستلاحظ أنّه يتم بدء طلبَي CSS وJavaScript في الوقت نفسه تقريبًا، ويحصل المتصفّح على ملف HTML، ويرصد كلا الموردَين، ويُجري كلا الطلبَين. نتيجةً لذلك، تحتوي الصفحة المعروضة في الصورة السابقة على السمات التالية للمسار الحرج:
- 3 موارد مهمة
- عمليتان أو أكثر من تنقّلات ذهاباً وإيابًا للحد الأدنى لطول المسار الحرج
- 11 كيلوبايت من البايتات المهمة
لدينا الآن ثلاثة موارد مهمة تبلغ مجموعها 11 كيلوبايت من البايتات المهمة، ولكن لا يزال طول المسار الحرج هو جولتان لأنّه يمكننا نقل CSS وJavaScript بشكل موازٍ. إنّ معرفة خصائص مسار العرض الحرج يعني أنّه يمكنك تحديد الموارد الحرجة وفهم كيفية جدولة المتصفّح لعمليات جلبها.
بعد التواصل مع مطوّري الموقع الإلكتروني، تبيّن لنا أنّه ليس من الضروري حظر JavaScript الذي أدرجناه في صفحتنا، لأنّنا نستخدم بعض الإحصاءات والرموز البرمجية الأخرى التي لا تحتاج إلى حظر عرض صفحتنا. بعد معرفة ذلك، يمكننا إضافة السمة async
إلى العنصر <script>
لإزالة حظر المُحلِّل:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script src="app.js" async></script>
</body>
</html>
للنص البرمجي غير المتزامن عدة مزايا:
- لم يعُد النص البرمجي يعرقل عمل المُحلِّل، كما أنّه ليس جزءًا من مسار المعالجة الحرج.
- ولأنّه لا تتوفّر نصوص برمجية أخرى مهمة، لا تحتاج CSS إلى حظر الحدث
domContentLoaded
. - وكلما كان بدء الحدث
domContentLoaded
أسرع، كان بإمكان منطق التطبيق الآخر بدء التنفيذ بشكل أسرع.
نتيجةً لذلك، عادت صفحتنا المحسّنة الآن إلى موردَين مهمّين (HTML وCSS)، مع الحد الأدنى لطول المسار المهمّ الذي يتطلّب رحلتَي ذهاب وإياب، وإجمالي 9 كيلوبايت من وحدات البايت المهمة.
أخيرًا، إذا كانت ورقة أنماط CSS مطلوبة للطباعة فقط، كيف ستبدو؟
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" media="print" />
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script src="app.js" async></script>
</body>
</html>
وبما أنّ المورد style.css لا يُستخدَم إلا للطباعة، لا يحتاج المتصفّح إلى حظره لعرض الصفحة. وبالتالي، بعد اكتمال إنشاء DOM، يحصل المتصفّح على معلومات كافية لعرض الصفحة. نتيجةً لذلك، تحتوي هذه الصفحة على مورد واحد فقط مهم (مستند HTML)، والحد الأدنى لطول مسار العرض المهم هو رحلة ذهاب وإياب واحدة.