مرورگر چگونه صفحه وب را Render می‌کند؟(بررسی Critical Rendering Path)

در این مطلب می‌خواهیم با هم ببینیم از لحظه‌ای که وارد یک سایت می‌شویم چه اتفاقاتی در مرورگر ما رخ میدهد و مراحل render شدن و همچنین Critical Rendering Path را بررسی کنیم. دانستن این مراحل برای توسعه‌ دهنده‌های وب میتواند خیلی مفید باشد و درک درستی به ما بدهد تا بتوانیم منابع خود را بهتر مدیریت کنیم و کارایی(Performance) صفحات وب خود را بهبود بدیم.بنابراین ما بر روی اینکه چه اتفاقاتی برای منابع ما(CSS, JS, HTML,...) در مرورگر رخ می‌دهد متمرکز ‌می‌شویم. اما نکاتی که لازم است در نظر داشته باشید:عملیات ارسال درخواست و دریافت فایل از سرور رو یک roundtrip می‌نامیم.برای مشاهده آنلاین مثال‌ها فیلتر شکن هاتون رو روشن کنید تا با صفحه‌ی403 روبرو نشوید ): .حالا بیایید با یک مثال ساده شروع کنیم. https://gist.github.com/MeHrAd-Deldadeh/7419a14c3617503c98bd4e68fff2439f#file-critical-path-no-style-html دیدن مثالبه قطعه کد بالا نگاه کنید ما تنها یک فایل HTML داریم که در آن یک پیام و یک عکس وجود دارد و هیچ فایل CSS و JS ای وجود ندارد. حالا اگر لینک بالا رو باز کنید با استفاده از developer tools می‌توانید در بخش network ببینید که ابتدا فایل HTML (که در اینجا فایل basic_dom_nostyle.html است) بارگذاری می‌شود و سپس فایل عکسی که ضمیمه آن شده. حالا به بخش waterfall نگاه کنید ناحیه‌ی سبز رنگ TTFB و یا Time To First Byte است که مدت زمانی رو که مرورگر ما منتظر میماند تا اولین Byte داده بهش برسد است و بخش آبی رنگ نیز Content Download یا مدت زمانی که صرف دانلود فایل شده است رو نشان میدهد.و آن خط آبی رنگ عمودی نیز نشان دهنده مدت زمانی است که طول کشیده تا فایل HTML بارگذاری و parse شود و درخت DOM یا همان Document Object Model ساخته شود که به‌ آن DOMContentLoaded گفته می‌شود گه در نوار پایین مدت زمان(446ms) آن مشخص شده است.اما نکته ای که باید به آن توجه کنیم این است که فایل عکس‌ما رویداد DOMContentLoaded را block نکرده و این نشان دهنده این است که ما می‌توانیم render tree رو بدون نیاز به بارگذاری چنین فایل هایی مثل تصاویر و… بسازیم اما بارگذاری عکس رویداد بارگذاری(load event) رو مسدود کرده است که این رویداد با یک خط عمودی قرمز رنگ نمایش داده می‌شود.اضافه کردن CSS و JavaScriptحالا بریم سراغ مثال بعدی که می‌خواهیم شرایطی رو بررسی کنیم که فایل های CSS و JS نیز رو هم در صفحه داریم. به مثال زیر توجه کنید. https://gist.github.com/MeHrAd-Deldadeh/23d617e9b4ef8d30db3154fc4d56e97c#file-critical-path-measure-script دیدن مثالقبل از اضافه کردن CSS:بدون وجود فایل‌های CSS و JS در صفحهبعد از اضافه کردن CSS و JS: با وجود فایل‌های CSS و JS در صفحههمانطور که در تصاویر بالا میبینید بعد از اضافه کردن فایل‌های CSS و JS در بخش waterfall دو request دیگر اضافه شد. نکته مهم‌ای که باید به آن توجه کنیم این است اگر دقت کنید میبینید که زمان فراخوانی رویداد domContentLoaded و رویداد load به هم نزدیکتر شده است.چه اتفاقی افتاد؟نکته اول اینکه برخلاف مثال اول در این بخش ابتدا فایل css ما fetch(واکشی) و parse(تجزیه) میشود و سپس CSSOM و یا همان CSS Object Model ساخته می‌شود، زیرا برای ساخت render tree به DOM و CCSOM نیاز است.نکته دوم اینکه ما یک فایل JS هم در صفحه داریم که آن یک parser blocking file است بنابراین رویداد domContentLoaded متوقف می‌شود تا فایل CSS دانلود و parse شود چونکه ممکن است فایل js به CSSOM نیاز داشته باشد.حالا اگر فایل جاوا اسکریپت external رو بصورت inline در فایل HTML قرار بدهیم چه اتفاقی می افتد؟ باز هم مرورگر قادر به اجرای آن ها نیست و باید ابتدا فایل CSS دانلود و parse شود تا CSSOM ساخته شود. بنابراین کدهای جاوا اسکریپت inline هم، همچنان parser blocking هستند.اما با این وجود بیایید ببینیم که inline کردن کدهای js باعث می‌شود که صفحه سریعتر بارگذاری شود یا خیر؟با فایل جاوا اسکریپت خارجی: external فایل جاوا اسکریپتبا کدهای جاوا اسکریپت به صورت inline: inline کدهای جاوا اسکریپتخب همانطور که میبینید در تصویر دوم ما بخاطر inline کردن فایل js یک درخواست کمتر به سرور داریم اما اگر به مدت زمان load آن ها نگاه کنید میبینید که تقریبا برابر هستند. اما چرا؟ اول اینکه ما می‌دانیم در هر شرایطی مرورگر نیاز دارد که ابتدا CSSOM را بسازد و سپس فایل های جاوا اسکریپت را بارگذاری کند و همچنین به این نکته توجه کنید که در مثال اول مرورگر فایل‌های CSS و JS را به طور موازی دانلود کرده و در واقع در این مثال خیلی تفاوتی احساس نمی‌کنیم اما راه‌های زیادی وجود دارد که بتوانیم سرعت load صفحه را بهتر کنیم.اول اینکه به یاد داشته باشید که جاوا اسکریپت های inline ما نیز parser blocking هستند اما برای فایل های جاوا اسکریپت external می‌توانیم با اضافه کردن پارامتر "async" به صورت زیر آن را به یک فایل تبدیل کنیم که فرایند parse رو متوقف نمی‌کند. https://gist.github.com/MeHrAd-Deldadeh/cc4cdba4ddf0d30bebb2266ee9d2637f#file-critical-path-measure-async-html دیدن مثالفایل جاوا اسکریپت خارجی(Parser-blocking):فایل جاوا اسکریپت خارجی(async):خب همانطور که میبینید خیلی بهتر شد ;) اگر دقت کنید بعد از بارگذاری فایل HTML رویداد domContentLoaded فراخوانی شده و مرورگر متوجه شده که نباید بخاطر فایل جاوا اسکریپت عملیات را متوقف کند و بنابراین بارگذاری فایل CSS و JS را به صورت موازی انجام میدهد.و همچنین میتوانیم CSS و JS رو به صورت inline قرار بدهیم. https://gist.github.com/MeHrAd-Deldadeh/f040fef599ba6eabbb177845dfe425dc#file-critical-path-measure-inlined-html دیدن مثالخب همانطور که میبینید تقریبا domContentLoaded با مثال قبل برابر است. در این روش هرچند حجم فایل HTML زیاد میشود اما دیگر نیازی به این نیست که مرورگر زمانی را صرف واکشی(fetch) فایل‌های CSS و JS بکند.با توجه به مثال هایی که تا به اینجا بررسی کردیم حتما متوجه شده اید که درک درست این فرآیند ها چقدر مهم خواهند بود اما حالا بریم سراغ الگوهای عملکرد تا درک بهتری در خصوص critical rendering path و روش‌های بهینه سازی آن داشته باشیم.الگو‌های عملکرد(Performance patterns)برمیگردیم به مثال اول‌ که داشتیم. ساده ترین حالت این است که صفحه ما یک فایل HTML دارد و هیچ فایل CSS و JS و منابع دیگه ای ندارد. برای render شدن همچین صفحه ای ابتدا مرورگر درخواستی را برای دریافت فایل HTML ارسال می‌کند و سپس منتظر میماند تا فایل برسد بعد از رسیدن آن را parse میکند و DOM را میسازد و در آخر آن را بر روی صفحه render میکند. https://gist.github.com/MeHrAd-Deldadeh/7419a14c3617503c98bd4e68fff2439f#file-critical-path-no-style-html دیدن مثالدر تصویر بالا در مدت زمان بین T0 تا T1 پردازش های شبکه و سرور انجام می‌شود. در بهترین حالت(اگر حجم فایل HTML کم باشد) ما فقط یک roundtrip در شبکه برای واکشی فایل‌ خواهیم داشت. با توجه به نحوه کارکرد پروتکل انتقال TCP فایل‌های بزرگتر ممکن است بیش از یک roundtrip داشته باشند. بنابراین در مثال بالا ما در بهترین حالت یک roundtrip در critical rendering path خواهیم داشت.حالا بیایید همین صفحه رو با وجود یک فایل CSS نیز بررسی کنیم: https://gist.github.com/MeHrAd-Deldadeh/aa031303abe83ac5cb5868b1211a0ef0#file-critical-path-has-style-html دیدن مثالدر این حالت همانطور که در تصویر میبینید ما ابتدا یک roundtrip در شبکه برای دریافت فایل HTML داریم و در هنگام ساخت DOM مرورگر متوجه میشود که به یک فایل CSS نیز احتیاج دارد بنابراین یک بار دیگر باید به سرور مراجعه کند و فایل CSS را نیز بگیرد(این خود یک roundtrip دیگر است) تا بتواند صفحه را نمایش دهد. بنابراین در اینجا ما حداقل دو roundtrip داریم که البته باز هم باید یادآوری کرد که به حجم فایل بستگی دارد و ما در اینجا فقط به حداقل آن اشاره میکنیم.پس باید دقت کنید که فایل‌های HTML و CSS ما critical resources هستند یعنی فایل‌هایی که ممکن است فرآیند render شدن صفحه را متوقف کنند تا بارگذاری شوند.حالا اگر یک فایل JS هم در صفحه داشته باشیم این فرآیند به چه شکل خواهد بود؟ https://gist.github.com/MeHrAd-Deldadeh/798533c0cc85a45d049805b83923b039#file-critical-path-css-js-html دیدن مثالدر این مثال همانطور که میبینید ما یک فایل app.js نیز اضافه کردیم که همانطور که گفتیم فایل های جاوا اسکریپت parser blocking هستند و برای اجرا(execute) شدن می‌بایست منتظر فایل ‌CSS و ساخت CSSOM باشیم. اما نکته دیگری که باید به آن توجه کنید این است که ما در این مثال ۳ فایل داریم اما تنها ۲ roundtrip داریم که در تصویر مشخص است فایل های CSS و JS به صورت موازی واکشی میشوند.در نظر داشته باشید که اگر نیازی به بلاک کردن صفحه برای فایل های JS خود ندارید همانطور که قبل از این توضیح داده شد میتوانید از پارامتر "async" برای فایل جاوا اسکریپت external خود استفاده کنید. https://gist.github.com/MeHrAd-Deldadeh/073bd471e2794b0fe82b39526d691d67#file-critical-path-css-js-async-html دیدن مثالمزیت‌های بارگذاری فایل JS به صورت غیر همزمان(asynchronous):فایل JS ما دیگر parser blocking نیست.چون دیگر فایل اسکریپت ضروری وجود ندارد فایل CSS نیازی به متوقف کردن رویداد domContentLoaded ندارد.در صورت فراخوانی سریعتر رویداد domContentLoad منطق(logic) برنامه نیز سریعتر اجرا میشود.در نتیجه صفحه‌ی ما در بهینه ترین حالت خواهد بود. و نکته‌ای که شاید نیاز باشد ذکر کنیم این است که ممکن است فایل CSS ما فقط برای یک سایز خاص و یا حالت خاص مثل حالت print نیاز باشد که می‌توانیم آن را به صورت زیر با افزودن media="print" از حالتی که فرایند render را متوقف می‌کند خارج کنیم. https://gist.github.com/MeHrAd-Deldadeh/0faaf861be97bd8cf40c971c9c27545e#file-critical-path-css-print-html دیدن مثالدر نتیجه همانطور که در تصویر میبینید بعد از ساخته شدن DOM صفحه render می‌شود و سپس CSSOM ساخته می‌شود و فایل های JS اجرا می‌شوند و ما فقط یک critical resources خواهیم داشت.این مطلب برداشت من(به صورت خلاصه و با ایجاد تغییراتی) از Analyzing Critical Rendering Path Performance بوده و امیدوارم که توانسته باشم مطلب رو به درستی انتقال بدهم و درک خوبی از مفهوم critical rendering path پیدا کرده باشید و بتوانید منابع خود رو به درستی مدیریت کنید و بهینه ترین صفحات وب رو داشته باشید. و همچنین ممنون میشم اگر نقصی در این مطلب میبینید اون رو برای من کامنت کنید تا در کامل‌ کردن این مطلب به من کمک کرده باشید.