با پیش‌بارگذاری‌های ناوبری، کارمند خدمات را تسریع کنید

پیش بارگذاری ناوبری به شما امکان می دهد با درخواست های موازی بر زمان راه اندازی سرویس دهنده غلبه کنید.

پشتیبانی مرورگر

  • 59
  • 18
  • 99
  • 15.4

منبع

خلاصه

مشکل

وقتی به سایتی هدایت می‌شوید که از یک سرویس‌دهنده برای رسیدگی به رویدادها استفاده می‌کند، مرورگر از کارمند سرویس پاسخ می‌خواهد. این شامل راه‌اندازی سرویس‌کار (اگر قبلاً اجرا نشده است) و ارسال رویداد واکشی است.

زمان راه اندازی به دستگاه و شرایط بستگی دارد. معمولاً حدود 50 میلی‌ثانیه است. در تلفن همراه بیشتر از 250 میلی‌ثانیه است. در موارد شدید (دستگاه های کند، CPU در مضیقه) می تواند بیش از 500 میلی ثانیه باشد. با این حال، از آنجایی که کارمند سرویس بین رویدادها برای مدت زمان تعیین شده توسط مرورگر بیدار می ماند، این تاخیر را فقط گاهی اوقات دریافت می کنید، مانند زمانی که کاربر از یک برگه جدید یا سایت دیگری به سایت شما می رود.

اگر از حافظه پنهان پاسخ دهید، زمان راه‌اندازی مشکلی ایجاد نمی‌کند، زیرا فایده پرش از شبکه بیشتر از تأخیر راه‌اندازی است. اما اگر با استفاده از شبکه پاسخ می دهید…

بوت SW
درخواست ناوبری

درخواست شبکه با راه‌اندازی سرویس‌دهنده به تأخیر می‌افتد.

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

فیس بوک تأثیر این مشکل را متوجه ما کرد و راهی برای انجام درخواست های ناوبری به صورت موازی درخواست کرد:

بوت SW
درخواست ناوبری



و ما گفتیم "آره، منصفانه به نظر می رسد".

"پیش بارگذاری ناوبری" برای نجات

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

تاخیر راه اندازی هنوز وجود دارد، اما درخواست شبکه را مسدود نمی کند، بنابراین کاربر زودتر محتوا را دریافت می کند.

در اینجا یک ویدیو از آن در حال انجام است، که در آن به کارگر سرویس با استفاده از یک حلقه while، تاخیر راه اندازی 500 میلی ثانیه ای عمدی داده می شود:

در اینجا خود نسخه ی نمایشی است . برای بهره مندی از مزایای پیش بارگذاری پیمایش، به مرورگری نیاز دارید که از آن پشتیبانی کند .

فعال کردن پیش بارگذاری ناوبری

addEventListener('activate', event => {
  event.waitUntil(async function() {
    // Feature-detect
    if (self.registration.navigationPreload) {
      // Enable navigation preloads!
      await self.registration.navigationPreload.enable();
    }
  }());
});

می توانید هر زمان که بخواهید navigationPreload.enable() فراخوانی کنید یا آن را با navigationPreload.disable() غیرفعال کنید. با این حال، از آنجایی که رویداد fetch شما باید از آن استفاده کند، بهتر است آن را در رویداد activate سرویس کارمند خود فعال/غیرفعال کنید.

با استفاده از پاسخ از پیش بارگذاری شده

اکنون مرورگر بارگذاری‌های پیش‌فرض را برای پیمایش انجام می‌دهد، اما همچنان باید از پاسخ استفاده کنید :

addEventListener('fetch', event => {
  event.respondWith(async function() {
    // Respond from the cache if we can
    const cachedResponse = await caches.match(event.request);
    if (cachedResponse) return cachedResponse;

    // Else, use the preloaded response, if it's there
    const response = await event.preloadResponse;
    if (response) return response;

    // Else try the network.
    return fetch(event.request);
  }());
});

event.preloadResponse قولی است که با یک پاسخ حل می شود، اگر:

  • پیش بارگیری ناوبری فعال است.
  • درخواست یک درخواست GET است.
  • درخواست یک درخواست ناوبری است (که مرورگرها هنگام بارگیری صفحات، از جمله iframe) ایجاد می کنند.

در غیر این صورت event.preloadResponse هنوز وجود دارد، اما با undefined حل می شود.

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

فرض کنید می‌خواهیم مقاله‌ای را نمایش دهیم:

addEventListener('fetch', event => {
  const url = new URL(event.request.url);
  const includeURL = new URL(url);
  includeURL.pathname += 'include';

  if (isArticleURL(url)) {
    event.respondWith(async function() {
      // We're going to build a single request from multiple parts.
      const parts = [
        // The top of the page.
        caches.match('/article-top.include'),
        // The primary content
        fetch(includeURL)
          // A fallback if the network fails.
          .catch(() => caches.match('/article-offline.include')),
        // The bottom of the page
        caches.match('/article-bottom.include')
      ];

      // Merge them all together.
      const {done, response} = await mergeResponses(parts);

      // Wait until the stream is complete.
      event.waitUntil(done);

      // Return the merged response.
      return response;
    }());
  }
});

در بالا، mergeResponses یک تابع کوچک است که جریان های هر درخواست را ادغام می کند. این بدان معنی است که ما می توانیم هدر حافظه پنهان را در حالی که محتوای شبکه در جریان است نمایش دهیم.

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

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

برای پشتیبانی از این، یک هدر با هر درخواست پیش بارگذاری ارسال می شود:

Service-Worker-Navigation-Preload: true

سرور می تواند از این برای ارسال محتوای متفاوت برای درخواست های پیش بارگذاری ناوبری نسبت به درخواست ناوبری معمولی استفاده کند. فقط به یاد داشته باشید که یک هدر Vary: Service-Worker-Navigation-Preload اضافه کنید، تا کش ها بدانند که پاسخ های شما متفاوت است.

اکنون می توانیم از درخواست پیش بارگذاری استفاده کنیم:

// Try to use the preload
const networkContent = Promise.resolve(event.preloadResponse)
  // Else do a normal fetch
  .then(r => r || fetch(includeURL))
  // A fallback if the network fails.
  .catch(() => caches.match('/article-offline.include'));

const parts = [
  caches.match('/article-top.include'),
  networkContent,
  caches.match('/article-bottom')
];

تغییر هدر

به‌طور پیش‌فرض، مقدار هدر Service-Worker-Navigation-Preload true است، اما می‌توانید آن را روی هر چیزی که می‌خواهید تنظیم کنید:

navigator.serviceWorker.ready.then(registration => {
  return registration.navigationPreload.setHeaderValue(newValue);
}).then(() => {
  console.log('Done!');
});

برای مثال می‌توانید آن را روی شناسه آخرین پستی که به صورت محلی ذخیره کرده‌اید تنظیم کنید، بنابراین سرور فقط داده‌های جدیدتر را برمی‌گرداند.

گرفتن دولت

با استفاده از getState می توانید وضعیت پیش بارگذاری ناوبری را جستجو کنید:

navigator.serviceWorker.ready.then(registration => {
  return registration.navigationPreload.getState();
}).then(state => {
  console.log(state.enabled); // boolean
  console.log(state.headerValue); // string
});

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

بخشی از سری جدید قابلیت همکاری