افزایش سرعت بارگذاری وب سایت (قسمت اول)

در این مقاله درباره بارگذاری فونت ها به روش نوین و تاثیر این کار بر روی سرعت بارگذاری وبسایت صحبت می‎کنم


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

  1. چیزی که عوض داره گله نداره. تو ادامه همین مقاله متوجه میشید که ما یک چیزی رو که اونقدر مهم نیست فدا می کنیم تا به چیز های بهتر برسیم. (یه جورایی رابطه متقابل داریم)
  2. هرچی کمتر کد بنویسیم، ماشین کار کمتری برای پردازش انجام میده و سرعت بیشتری داریم. (البته این یه قانونه که اون تیمی برنده هست که گل های بیشتری بزنه! - جواد خیابانی)
  3. انتظار نداشته باشید برنامه‌ایی که وصله پینه شده سَر و تهش معلوم نیست چیه بعد از خوندن ۴ تا پست تو ۱ ساعت بشه بهترین اپ جهان :)

درباره موضوعات مقاله بیشتر سعی می‌کنم تکنیک‌هارو مورد بررسی قرار بدم و قطعا آموزش ویدیویی web pack رو هم خواهیم داشت ولی الان بیشتر تمرکزم رو این موضوعات هستش:

  1. Font Lazy Loading (مقاله)
  2. Lazy Loading (مقاله)
  3. Web Font Optimization (مقاله)
  4. Code Splitting (ویدیو)
  5. Hidden Classes & Optimizing Objects
  6. Monomorphism, Polymorphism, Megamorphism
  7. Caching

و مسلماً مقالات بیشتر بستگی به بازخورد خواننده داره.


خب رسیدیم به اصل مطلب :)

اکثر کاربرا داخل ایران از لحاظ سرعت، اینترنت خوبی دارن (البته کیفیت پایینه فقط کمیت بالاست)، طبق آمار منتشر شده ۵۳٪ کاربران موبایل اگر بعد ۳ ثانیه هیچ چیزی رو صفحه نبینن میرن داخل یک‌وبسایت دیگه. و این یعنی فروش‌کمتر، نمایش تبلیغ کمتر یا هرچیزی که به پلن تجاری شما صدمه میزنه.

از طرفی میشه سرعت رو از ۲ جهت مورد بررسی قرار داد:

  1. بارگذاری فایل‌ها از سرور به مرورگر کاربر
  2. سرعت اجرای فایل‌های بارگذاری شده

شاید شما ۴ خط کد JS بیشتر نداشته باشید که از سرور به مرورگر ارسال بشه اما وقتی کد قراره اجرا بشه ممکنه یک حلقه بینهایت باشه که مرورگر کاربر Crash کنه.

خب پس حالا که اهمیت کیفیت و کمیت کد رو متوجه شدید باید شروع کنیم به بررسی موضوع اصلی این مقاله "لود کردن فونت ها به روش نوین".

ساختار یک فایل HTML ساده
ساختار یک فایل HTML ساده

خب باید ۲ چیز رو مورد بررسی قرار بدیم:

  1. فایل های CSS و JS در چه زمانی load می‌شن
  2. تصاویر و فونت‌ها در چه زمانی load می‌شن

فایل‌های CSS و JS

درباره نکته اول load شدن CSS معمولا داخل head بالای تگ body انجام میشه، اگر در پایین صفحه و در آخر اینکار انجام بشه کاربر ابتدا صفحه بدون ساختار و بد ترکیب رو میبینه و بعد که مرورگر به پایین صفحه رسید و دریافت CSS تموم شد به ظاهر و شکل اصلی خودش برمیگرده. خب معمولا کل CSS های سایت داخل ۱ فایل ذخیره می‌شن و این باعث میشه شما کدهایی که برای صفحه‌های دیگه هستند رو هم برای کاربر بفرستید که زیاد جالب نیست ولی خب جدا از این مباحث ما مجبوریم که CSS رو بالای صفحه لود کنیم و اتفاقی که میوفته اینکه مروگر پردازش مابقی صفحه رو متوقف می‌کنه تا فایل CSS کامل دریافت بشه.
راه حل: دستورات CSS رو ۲ بخش بکنیم، دستوراتی که برای load این صفحه لازمه و دستوراتی که برای این صفحه لازم نیست (البته اگر حجم فایل CSS کم هستش لازم نیست اینکار انجام بشه)
قسمت اول که CSS های لازم هستش همون اول داخل تگ head بارگذاری میشه و قسمت دوم پایین و در آخر... - به این تکنیک code splitting می‌گن که بعدا بهش مفصل می‌پردازیم

همین روند برای فایل‌های JS اتفاق میفوته.

نکته: میشه از دستور defer استفاده کرد تا دستورات در آخر پس از parse داکیومنت دریافت و اجرا بشن ولی مرورگر های قدیمی این دستور رو تو خودشون ندارن و شما فقط به خاطر جابه‌جا نکردن ۲ خط کد از اول قالب به آخرش همه چیز رو خراب می‌کنید.

تصاویر

در لحظه که شما آدرس رو داخل مقدار src تگ img قرار می‌دید، شروع به دانلود شدن می‌کنن و نکته اینجاست که شاید کاربر اون عکسی که باید نیم ساعت scroll بشه تا بهش برسه رو اصلا نبینه، پس میشه فرایندی رو پیاده سازی کرد که وقتی کاربر scroll کرد و به تگ img رسید مرورگر شروع به دانلود عکس بکنه - به این تکنیک lazy loading می‌گن که بعدا بهش مفصل می‌پردازیم

فونت‌ها

برخلاف تمام فایل‌هایی تا الان بررسی کردیم فونت ها تا استفاده نشن مرورگر دانلودشون نمی‌کنه

با تعریف بالا ما فقط فونت رو تعریف کردیم و تا زمانی که، کلاسی تعریف نکنیم و اون کلاس رو به selector اختصاص ندیم اتفاقی نمیوفته.

اگر فونت رو به تگ body اعمال کنیم زمانی که مرورگر به selector مورد نظر برسه شروع به دانلود فونت میکنه و برای چند ثانیه تمامی متون بی‌رنگ هستن

و بازم با این دانلودشدن های اضافه و پشت سرهم سرعت لود وبسایت کمتر و کمتر میشه آیا واقعا کاربر دوست داره که ۱۰ ثانیه صبر کنه تا یک صفحه خیلی قشنگ با فونت‌های باحال ببینه یا فقط می‌خواهد سریع صفحه باز بشه و حالا بعدش که صفحه باز شد فونت‌ها عوض بشه. (خب مسلماً دومی)

حالا که دقیق فهمیدیم موضوع چیه بیاید شروع کنیم به کد زدن :))

نظرتون درباره یک قطعه کد که آخر از همه بعد از parse شدن document اجرا بشه و بشه باهاش ‌فونت‌هارو دانلود کرد چیه :)). با این روش کاربر کل صفحه رو تو زمان خیلی کم دریافت کرده،‌ و فقط فونت‌ها زیاد جذاب نیستن که اونم بعد از ۱ یا ۲ ثانیه (به فارسی سخت) جذاب میشه.

Let's build Something

داخل CSS هر جا که فونت رو صدا کردید حذف کنید یا از web-safe-font استفاده کنید، یک کلاس CSS به نام wf-active (مخفف web font active) بسازید و داخل این کلاس فونت اصلی رو صدا می‌کنیم و میدونیم تا وقتی که این کلاس رو بر روی المانی اعمال نکنیم فونت‌ها شروع به دانلود شدن نمی‌کنن.

خب حالا باید کد جاوااسکریپتی بنویسیم که فایل‌های فونت رو دانلود کنه بعد از اینکه دانلود فایل‌ها تموم شد کلاس ws-active رو به تگ body یا html اضافه کنه.

برای دانلود میشه از یک کتابخونه باحال و جمع و جور به نام FontFaceObserver استفاده کرد. بعد از دانلود و نصب کتابخونه (پیشنهاد میشه از npm نصب کنید و اگر امکانش نیست به github رفته و فایل مورد نظر خودتون رو پیدا کنید) یک فایل به نام fontLoader.js بسازید و برای هریک از فونت‌های خود یک نمونه جدید از شی FontFaceObserver بسازید

حالا باید متد load رو فراخوانی کنیم تا فونت لود بشه و کلاس ws-active رو به تگ html اضافه کنیم:

پارامتر دوم متد load، تعیین می‌کند که حداکثر برای دانلود فونت قبل از خطا دادن باید چقدر صبر کرد
پارامتر دوم متد load، تعیین می‌کند که حداکثر برای دانلود فونت قبل از خطا دادن باید چقدر صبر کرد

به همین راحتی،‌ حالا کافیه فایل fontLoader رو به انتهای html خودمون اضافه کنیم. اما هنوز تموم نشده می‌تونیم فونت‌هارو cache کنیم که تا بازم سریعتر لود بشه =)

خب چند تا سوال ممکنه پیش بیاد

  1. اون پرانتز‌های خط اول و آخر فایل چی هستند ؟
  2. آبجکت Session Storage چی هستش !

در جواب سوال اول باید بگم که ما ۲ مدل فانکشن در جاوااسکریپت داریم، متدی که اسم نداشته باشه به نام anonymous شناخته میشه و به صورت زیر تعریف میشه

function () {
    console.log("hello");
}

اگر قطعه کد بالارو داخل مرورگر اجرا کنید هیج اتفاقی رخ نمیده :) (تیمی که گل بیشتری زده قطعا برنده بازی هستش !)
برای فراخوانی این متد باید بهش اسم تخصیص داد یا اشاره گر به این متد را داخل یک متغیر ذخیره کنیم، اما روش دومی وجود داره که متدرو درجا اجرا می‌کنه (از اونجایی که مقدار بازگشتی متد anonymous اشاره گر به متد هستش) میشه جلوش پرانتز باز و بسته گذاشت که در جا اجرا بشه

(function () {
     console.log("hello"); 
})();

اگر کد بالارو اجرا کنید خروجی شما در کنسول برابر با hello میشه :)

در جواب سوال دوم localStorage یک دیتابیس key, value هستش که اطلاعات رو ذخیره ‌می‌کنه، sessionStorage همون localStorage هستش اما تا زمانی که کاربر tab فعلی رو نبسته باشه اطلاعات داخل ذخیره میشه و بعد از اون همه چیز داخلش پاک میشه. از اصلی ترین تفاوت های storage با cookie اینکه شما سمت سرور به localStorage و sesstionStorage دسترسی ندارید.

خب برگردیم به کد و ببینیم که چه کردیم

// Optimization for Repeat Views
// this is not going to matter in react|vue while we use virtual dom
    if( sessionStorage.foutFontsLoaded ) {
        document.documentElement.classList.add("wf-active");
        return;
    }

تو خط اول چک می‌کنیم که مقدار foutFontsLoaded برابر با true هستش و اگر این اتفاق افتاده بود کلاس wf-active رو به تگ html اضافه می‌کنیم و از متد خارج می‌شویم (دوباره فونت‌هارو دانلود نمی‌کنیم)

و در آخر بعد از اضافه کردن کلاس به تگ html مقدار foutFontsLoaded را برابر true می‌کنیم.

نکات

  1. اگر از npm استفاده نمی‌کنید و polyfill ندارید باید از نسخه مناسب FontFaceObserver استفاده کنید
  2. اگر جاوااسکریپت غیر فعال باشد فونت‌ها لود نمی‌شود
  3. حتماً حتماً مطمئن شوید فونت‌ها بدون اجرا شدن فایل fontLoader در صفحه بارگذاری نمی‌شوند، در غیر اینصورت فونت‌ها ۲ بار دانلود می‌شوند.
    برای دیباگ کردن فایل fontLoader رو بارگذاری نکنید
    با فشردن کلید F12 پنجره Developer Tools رو باز کنید و به تب Network بروید
    و گزینه disable cache را فعال کرده و در قسمت Filter گزینه font را انتخاب کنید
    کلید F5 یا دکمه Reload را فشرده تا صفحه مجدد بارگذاری شود
    توجه داشته باشید نباید فونت‌هایی که در فایل fontLoader تعریف کرده‌اید در لیست نمایش پیدا کند

و در آخر، مثال با سینتکس ES6 و بارگذاری هم زمان چندین فونت رو داریم

خوشحال میشم بدونم این مقاله مفید واقع شده یا نه! و اگر سوالی داشتید در خدمتم :)