بهینه سازی بارگیری منابع

در ماژول قبلی، برخی از تئوری‌های پشت مسیر رندر بحرانی بررسی شد و اینکه چگونه منابع مسدودکننده رندر و مسدودکننده تجزیه می‌توانند رندر اولیه یک صفحه را به تأخیر بیندازند. اکنون که برخی از تئوری‌های پشت این موضوع را درک کرده‌اید، آماده‌اید تا برخی از تکنیک‌های بهینه‌سازی مسیر رندر بحرانی را بیاموزید.

همزمان با بارگذاری یک صفحه، منابع زیادی در HTML آن ارجاع داده می‌شوند که ظاهر و طرح‌بندی صفحه را از طریق CSS و همچنین تعامل آن را از طریق جاوا اسکریپت فراهم می‌کنند. در این ماژول، تعدادی از مفاهیم مهم مرتبط با این منابع و نحوه تأثیر آنها بر زمان بارگذاری صفحه پوشش داده شده است.

مسدود کردن رندر

همانطور که در ماژول قبلی مورد بحث قرار گرفت، CSS یک منبع مسدودکننده رندر است، زیرا مرورگر را از رندر هرگونه محتوایی تا زمان ساخت مدل شیء CSS (CSSOM) باز می‌دارد. مرورگر رندر را مسدود می‌کند تا از Flash of Unstyled Content (FOUC) جلوگیری کند، که از دیدگاه تجربه کاربری نامطلوب است.

در ویدیوی قبلی، یک FOUC کوتاه وجود دارد که در آن می‌توانید صفحه را بدون هیچ استایل‌بندی مشاهده کنید. متعاقباً، تمام استایل‌ها پس از اتمام بارگذاری CSS صفحه از شبکه اعمال می‌شوند و نسخه بدون استایل صفحه بلافاصله با نسخه استایل‌دار جایگزین می‌شود.

به طور کلی، FOUC چیزی است که معمولاً نمی‌بینید، اما درک مفهوم آن مهم است تا بدانید چرا مرورگر رندر صفحه را تا زمانی که CSS دانلود و به صفحه اعمال شود، مسدود می‌کند. مسدود شدن رندر لزوماً نامطلوب نیست، اما شما می‌خواهید با بهینه نگه داشتن CSS خود، مدت زمان آن را به حداقل برسانید.

مسدود کردن تجزیه‌گر

یک منبع مسدودکننده‌ی تجزیه‌گر، تجزیه‌گر HTML را متوقف می‌کند، مانند یک عنصر <script> بدون ویژگی‌های async یا defer . هنگامی که تجزیه‌گر با یک عنصر <script> مواجه می‌شود، مرورگر باید قبل از ادامه‌ی تجزیه‌ی بقیه‌ی HTML، اسکریپت را ارزیابی و اجرا کند. این امر از روی طراحی است، زیرا اسکریپت‌ها ممکن است DOM را در مدتی که هنوز در حال ساخت است، تغییر دهند یا به آن دسترسی پیدا کنند.

<!-- This is a parser-blocking script: -->
<script src="/script.js"></script>

هنگام استفاده از فایل‌های جاوا اسکریپت خارجی (بدون async یا defer — یا type=module که به طور پیش‌فرض defer است)، تجزیه‌کننده از زمان کشف فایل تا زمان دانلود، تجزیه و اجرای آن مسدود می‌شود. هنگام استفاده از جاوا اسکریپت درون‌خطی، تجزیه‌کننده به طور مشابه تا زمانی که اسکریپت درون‌خطی تجزیه و اجرا شود، مسدود می‌شود.

اسکنر پیش بارگذاری

اسکنر پیش‌بارگذاری ، یک بهینه‌سازی مرورگر در قالب یک تجزیه‌گر HTML ثانویه است که پاسخ خام HTML را اسکن می‌کند تا منابع را قبل از اینکه تجزیه‌گر HTML اولیه آنها را کشف کند، پیدا و به صورت حدسی دریافت کند. به عنوان مثال، اسکنر پیش‌بارگذاری به مرورگر اجازه می‌دهد تا دانلود منبعی را که در یک عنصر <img> مشخص شده است، شروع کند، حتی زمانی که تجزیه‌گر HTML هنگام دریافت و پردازش منابعی مانند CSS و جاوا اسکریپت مسدود شده باشد.

برای استفاده از اسکنر پیش‌بارگذاری، منابع حیاتی باید در نشانه‌گذاری HTML ارسالی توسط سرور گنجانده شوند. الگوهای بارگذاری منابع زیر توسط اسکنر پیش‌بارگذاری قابل کشف نیستند:

  • تصاویری که توسط CSS و با استفاده از ویژگی background-image بارگذاری می‌شوند. این ارجاعات تصویر در CSS هستند و توسط اسکنر پیش‌بارگذاری قابل شناسایی نیستند.
  • اسکریپت‌های بارگذاری‌شده به صورت پویا در قالب نشانه‌گذاری عنصر <script> که با استفاده از جاوا اسکریپت به DOM تزریق می‌شوند یا ماژول‌هایی که با استفاده از import() بارگذاری می‌شوند.
  • HTML که با استفاده از جاوا اسکریپت در کلاینت رندر می‌شود. چنین نشانه‌گذاری‌هایی درون رشته‌هایی در منابع جاوا اسکریپت قرار دارند و توسط اسکنر پیش‌بارگذاری قابل شناسایی نیستند.
  • اعلان‌های @import در CSS.

این الگوهای بارگذاری منابع، همگی منابعی هستند که دیر کشف شده‌اند و بنابراین از اسکنر پیش‌بارگذاری سودی نمی‌برند. هر زمان که ممکن است از آنها اجتناب کنید. با این حال، اگر اجتناب از چنین الگوهایی امکان‌پذیر نیست ، می‌توانید از یک راهنمای preload برای جلوگیری از تأخیر در کشف منابع استفاده کنید.

سی‌اس‌اس

CSS نحوه نمایش و طرح‌بندی یک صفحه را تعیین می‌کند. همانطور که قبلاً توضیح داده شد، CSS یک منبع مسدودکننده رندر است، بنابراین بهینه‌سازی CSS شما می‌تواند تأثیر قابل توجهی بر زمان کلی بارگذاری صفحه داشته باشد.

کوچک‌سازی

کوچک‌سازی فایل‌های CSS، حجم فایل یک منبع CSS را کاهش می‌دهد و دانلود آنها را سریع‌تر می‌کند. این کار در درجه اول با حذف محتوا از یک فایل CSS منبع مانند فاصله‌ها و سایر کاراکترهای نامرئی و خروجی گرفتن نتیجه در یک فایل بهینه‌سازی شده جدید انجام می‌شود:

/* Unminified CSS: */

/* Heading 1 */
h1 {
  font-size: 2em;
  color: #000000;
}

/* Heading 2 */
h2 {
  font-size: 1.5em;
  color: #000000;
}
/* Minified CSS: */
h1,h2{color:#000}h1{font-size:2em}h2{font-size:1.5em}

در ساده‌ترین شکل خود، کوچک‌سازی CSS یک بهینه‌سازی مؤثر است که می‌تواند FCP وب‌سایت شما و شاید حتی در برخی موارد LCP را بهبود بخشد. ابزارهایی مانند bundlerها می‌توانند به طور خودکار این بهینه‌سازی را برای شما در نسخه‌های عملیاتی انجام دهند.

حذف CSS های استفاده نشده

قبل از رندر کردن هرگونه محتوا، مرورگر باید تمام برگه‌های استایل را دانلود و تجزیه کند. زمان لازم برای تکمیل تجزیه، شامل استایل‌هایی که در صفحه فعلی استفاده نشده‌اند نیز می‌شود. اگر از یک bundler استفاده می‌کنید که تمام منابع CSS را در یک فایل واحد ترکیب می‌کند، کاربران شما احتمالاً CSS بیشتری نسبت به آنچه برای رندر کردن صفحه فعلی لازم است، دانلود می‌کنند.

برای کشف CSS استفاده نشده برای صفحه فعلی، از ابزار Coverage در Chrome DevTools استفاده کنید.

تصویری از ابزار پوشش در Chrome DevTools. یک فایل CSS در پنل پایین آن انتخاب شده است که مقدار قابل توجهی از CSS استفاده نشده توسط طرح‌بندی صفحه فعلی را نشان می‌دهد.
ابزار پوشش در Chrome DevTools برای تشخیص CSS (و جاوا اسکریپت) استفاده نشده توسط صفحه فعلی مفید است. می‌توان از آن برای تقسیم فایل‌های CSS به چندین منبع برای بارگذاری توسط صفحات مختلف استفاده کرد، برخلاف ارسال یک بسته CSS بسیار بزرگتر که می‌تواند رندر صفحه را به تأخیر بیندازد.

حذف CSSهای استفاده نشده دو فایده دارد: علاوه بر کاهش زمان دانلود، شما ساختار درخت رندر را بهینه می‌کنید، زیرا مرورگر باید قوانین CSS کمتری را پردازش کند.

از اعلان‌های @import در CSS خودداری کنید

اگرچه ممکن است راحت به نظر برسد، اما باید از اعلان‌های @import در CSS اجتناب کنید:

/* Don't do this: */
@import url('style.css');

مشابه نحوه عملکرد عنصر <link> در HTML، اعلان @import در CSS به شما امکان می‌دهد یک منبع CSS خارجی را از درون یک صفحه استایل وارد کنید. تفاوت اصلی بین این دو رویکرد این است که عنصر <link> در HTML بخشی از پاسخ HTML است و بنابراین خیلی زودتر از یک فایل CSS که توسط اعلان @import دانلود می‌شود، کشف می‌شود.

دلیل این امر این است که برای کشف یک اعلان @import ، ابتدا باید فایل CSS حاوی آن دانلود شود. این منجر به چیزی می‌شود که به عنوان یک زنجیره درخواست شناخته می‌شود که - در مورد CSS - مدت زمان لازم برای رندر اولیه یک صفحه را به تأخیر می‌اندازد. اشکال دیگر این است که شیوه‌نامه‌های بارگذاری شده با استفاده از اعلان @import نمی‌توانند توسط اسکنر پیش‌بارگذاری کشف شوند و بنابراین به منابع مسدودکننده رندر با تأخیر کشف شده تبدیل می‌شوند.

<!-- Do this instead: -->
<link rel="stylesheet" href="style.css">

در بیشتر موارد، می‌توانید @import را با استفاده از عنصر <link rel="stylesheet"> جایگزین کنید. عناصر <link> امکان دانلود همزمان style sheetها را فراهم می‌کنند و زمان بارگذاری کلی را کاهش می‌دهند، برخلاف اعلان‌های @import که style sheetها را به صورت متوالی دانلود می‌کنند.

CSS انتقادی درون خطی

مدت زمان لازم برای دانلود فایل‌های CSS می‌تواند FCP یک صفحه را افزایش دهد. درج استایل‌های مهم در سند <head> درخواست شبکه برای منبع CSS را حذف می‌کند و - هنگامی که به درستی انجام شود - می‌تواند زمان بارگذاری اولیه را هنگامی که حافظه پنهان مرورگر کاربر آماده نشده است، بهبود بخشد. CSS باقی مانده را می‌توان به صورت غیرهمزمان بارگذاری کرد یا در انتهای عنصر <body> اضافه کرد.

<head>
  <title>Page Title</title>
  <!-- ... -->
  <style>h1,h2{color:#000}h1{font-size:2em}h2{font-size:1.5em}</style>
</head>
<body>
  <!-- Other page markup... -->
  <link rel="stylesheet" href="non-critical.css">
</body>

از جنبه‌های منفی، درون‌خطی کردن مقدار زیادی CSS، بایت‌های بیشتری را به پاسخ اولیه HTML اضافه می‌کند. از آنجا که منابع HTML اغلب نمی‌توانند برای مدت طولانی - یا اصلاً - ذخیره شوند، این بدان معناست که CSS درون‌خطی شده برای صفحات بعدی که ممکن است از همان CSS در برگه‌های سبک خارجی استفاده کنند، ذخیره نمی‌شود. عملکرد صفحه خود را آزمایش و اندازه‌گیری کنید تا مطمئن شوید که این تغییرات ارزش تلاش را دارند.

دموهای CSS

جاوا اسکریپت

جاوا اسکریپت بخش عمده‌ای از تعامل در وب را هدایت می‌کند، اما هزینه‌ای هم دارد. استفاده بیش از حد از جاوا اسکریپت می‌تواند باعث شود صفحه وب شما در هنگام بارگذاری صفحه، پاسخ‌دهی کندی داشته باشد و حتی ممکن است باعث مشکلات پاسخگویی شود که تعاملات را کند می‌کند - که هر دو می‌توانند برای کاربران ناامیدکننده باشند.

جاوا اسکریپت مسدودکننده رندر

هنگام بارگذاری عناصر <script> بدون ویژگی‌های defer یا async ، مرورگر تجزیه و رندر را تا زمانی که اسکریپت دانلود، تجزیه و اجرا شود، مسدود می‌کند. به طور مشابه، اسکریپت‌های درون‌خطی تجزیه‌کننده را تا زمانی که اسکریپت تجزیه و اجرا شود، مسدود می‌کنند.

async در مقابل defer

async و defer به اسکریپت‌های خارجی اجازه می‌دهند بدون مسدود کردن تجزیه‌کننده HTML بارگذاری شوند، در حالی که اسکریپت‌ها (از جمله اسکریپت‌های درون‌خطی) با type="module" به طور خودکار به تعویق می‌افتند. با این حال، async و defer تفاوت‌هایی دارند که درک آنها مهم است.

تصویری از مکانیسم‌های مختلف بارگذاری اسکریپت، که همگی جزئیات نقش‌های تجزیه‌کننده، واکشی و اجرا را بر اساس ویژگی‌های مختلف مورد استفاده مانند async، defer، type='module' و ترکیبی از هر سه نشان می‌دهند.
منبع از https://html.spec.whatwg.org/multipage/scripting.html

اسکریپت‌هایی که با async بارگذاری می‌شوند، بلافاصله پس از دانلود تجزیه و اجرا می‌شوند، در حالی که اسکریپت‌هایی که با defer بارگذاری می‌شوند، پس از پایان تجزیه سند HTML اجرا می‌شوند - این اتفاق همزمان با رویداد DOMContentLoaded مرورگر رخ می‌دهد. علاوه بر این، اسکریپت‌های async ممکن است خارج از ترتیب اجرا شوند، در حالی که اسکریپت‌های defer به ترتیبی که در نشانه‌گذاری ظاهر می‌شوند، اجرا می‌شوند.

رندر سمت کلاینت

به طور کلی، شما باید از استفاده از جاوا اسکریپت برای رندر کردن هرگونه محتوای مهم یا عنصر LCP صفحه خودداری کنید. این به عنوان رندر سمت کلاینت شناخته می‌شود و تکنیکی است که به طور گسترده در برنامه‌های تک صفحه‌ای (SPA) استفاده می‌شود.

نشانه‌گذاری رندر شده توسط جاوا اسکریپت، اسکنر پیش‌بارگذاری را دور می‌زند، زیرا منابع موجود در نشانه‌گذاری رندر شده توسط کلاینت توسط آن قابل کشف نیستند . این می‌تواند دانلود منابع حیاتی مانند تصویر LCP را به تأخیر بیندازد. مرورگر تنها پس از اجرای اسکریپت، دانلود تصویر LCP را آغاز می‌کند و عنصر را به DOM اضافه می‌کند. در عوض، اسکریپت تنها پس از کشف، دانلود و تجزیه شدن می‌تواند اجرا شود. این به عنوان یک زنجیره درخواست بحرانی شناخته می‌شود و باید از آن اجتناب شود.

علاوه بر این، رندر کردن نشانه‌گذاری با استفاده از جاوا اسکریپت، نسبت به نشانه‌گذاری دانلود شده از سرور در پاسخ به درخواست ناوبری، احتمال بیشتری برای تولید وظایف طولانی دارد. استفاده گسترده از رندر کردن HTML در سمت کلاینت می‌تواند تأثیر منفی بر تأخیر تعامل داشته باشد . این امر به ویژه در مواردی که DOM یک صفحه بسیار بزرگ است ، صادق است که باعث می‌شود هنگام تغییر DOM توسط جاوا اسکریپت، کار رندر قابل توجهی انجام شود.

کوچک‌سازی

مشابه CSS، کوچک‌سازی جاوا اسکریپت، اندازه فایل منبع اسکریپت را کاهش می‌دهد. این می‌تواند منجر به دانلود سریع‌تر شود و به مرورگر اجازه می‌دهد تا سریع‌تر به فرآیند تجزیه و کامپایل جاوا اسکریپت بپردازد.

علاوه بر این، کوچک‌سازی جاوا اسکریپت یک قدم فراتر از کوچک‌سازی سایر دارایی‌ها، مانند CSS، می‌رود. وقتی جاوا اسکریپت کوچک‌سازی می‌شود، نه تنها چیزهایی مانند فاصله‌ها، تب‌ها و نظرات از آن حذف می‌شوند، بلکه نمادهای موجود در کد جاوا اسکریپت اصلی نیز کوتاه می‌شوند. این فرآیند گاهی اوقات به عنوان uglification شناخته می‌شود. برای مشاهده تفاوت، کد منبع جاوا اسکریپت زیر را در نظر بگیرید:

// Unuglified JavaScript source code:
export function injectScript () {
  const scriptElement = document.createElement('script');
  scriptElement.src = '/js/scripts.js';
  scriptElement.type = 'module';

  document.body.appendChild(scriptElement);
}

وقتی کد منبع جاوا اسکریپت قبلی زشت‌سازی شود، نتیجه ممکن است چیزی شبیه به قطعه کد زیر باشد:

// Uglified JavaScript production code:
export function injectScript(){const t=document.createElement("script");t.src="/js/scripts.js",t.type="module",document.body.appendChild(t)}

در قطعه کد قبلی، می‌توانید ببینید که متغیر scriptElement که برای انسان قابل خواندن است در منبع به t خلاصه شده است. هنگامی که این تغییر در مجموعه‌ای بزرگ از اسکریپت‌ها اعمال شود، صرفه‌جویی می‌تواند بسیار قابل توجه باشد، بدون اینکه بر ویژگی‌هایی که جاوا اسکریپت تولید شده توسط یک وب‌سایت ارائه می‌دهد، تأثیر بگذارد.

اگر از یک bundler برای پردازش کد منبع وب‌سایت خود استفاده می‌کنید، uglification اغلب برای نسخه‌های نهایی به صورت خودکار انجام می‌شود. uglifierها - به عنوان مثال Terser - نیز بسیار قابل تنظیم هستند که به شما امکان می‌دهد میزان شدت الگوریتم uglification را برای دستیابی به حداکثر صرفه‌جویی تنظیم کنید. با این حال، پیش‌فرض‌های هر ابزار uglification معمولاً برای ایجاد تعادل مناسب بین اندازه خروجی و حفظ قابلیت‌ها کافی است.

نسخه‌های نمایشی جاوا اسکریپت

دانش خود را بیازمایید

بهترین راه برای بارگذاری چندین فایل CSS در مرورگر چیست؟

اعلان @import در CSS.
دوباره امتحان کنید.
چندین عنصر <link>
درست است!

اسکنر پیش‌بارگذاری مرورگر چه کاری انجام می‌دهد؟

این یک تجزیه‌گر HTML ثانویه است که نشانه‌گذاری خام را بررسی می‌کند تا منابع را قبل از تجزیه‌گر DOM کشف کند تا آنها را زودتر کشف کند.
درست است!
عناصر <link rel="preload"> را در یک منبع HTML تشخیص می‌دهد.
دوباره امتحان کنید.

چرا مرورگر هنگام دانلود منابع جاوا اسکریپت، تجزیه HTML را به طور پیش‌فرض موقتاً مسدود می‌کند؟

برای جلوگیری از نمایش ناگهانی محتوای بدون استایل (FOUC).
دوباره امتحان کنید.
زیرا ارزیابی جاوا اسکریپت یک کار بسیار فشرده از CPU است و متوقف کردن تجزیه HTML پهنای باند بیشتری را در اختیار CPU قرار می‌دهد تا بارگذاری اسکریپت‌ها را به پایان برساند.
دوباره امتحان کنید.
زیرا اسکریپت‌ها می‌توانند DOM را تغییر دهند یا به هر نحوی به آن دسترسی داشته باشند.
درست است!

بعدی: کمک به مرورگر با نکات مربوط به منابع

حالا که با نحوه‌ی تأثیر منابع بارگذاری‌شده در عنصر <head> بر بارگذاری اولیه‌ی صفحه و معیارهای مختلف آشنا شدید، وقت آن رسیده که به سراغ ادامه‌ی مطلب برویم. در ماژول بعدی، به بررسی نکات مربوط به منابع و نحوه‌ی ارائه‌ی نکات ارزشمند به مرورگر برای شروع بارگذاری منابع و باز کردن اتصالات به سرورهای بین‌مربعی، زودتر از زمانی که مرورگر بدون آنها می‌توانست، می‌پردازیم.