تسریع کارگر سرویس با پیش‌بارگیری‌های ناوبری

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

Browser Support

  • Chrome: 59.
  • Edge: 18.
  • Firefox: 99.
  • Safari: 15.4.

Source

خلاصه

مشکل

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

زمان راه‌اندازی به دستگاه و شرایط بستگی دارد. معمولاً حدود ۵۰ میلی‌ثانیه است. در تلفن همراه، این زمان حدود ۲۵۰ میلی‌ثانیه است. در موارد شدید (دستگاه‌های کُند، CPU در وضعیت بحرانی) می‌تواند بیش‌از ۵۰۰ میلی‌ثانیه باشد. اما چون کارگر سرویس برای مدت‌زمان تعیین‌شده توسط مرورگر بین رویدادها بیدار می‌ماند، این تأخیر را فقط گاهی اوقات دریافت می‌کنید، مثلاً وقتی کاربر از یک برگه جدید یا سایت دیگری به سایت شما پیمایش می‌کند.

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

راه‌اندازی نرم‌افزار
درخواست پیمایش

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

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

Facebook تأثیر این مشکل را به ما اطلاع داد و خواستار روشی برای انجام موازی درخواست‌های پیمایش شد:

راه‌اندازی نرم‌افزار
درخواست پیمایش

پیش‌بارگیری ناوبری برای نجات

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

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

در اینجا ویدیویی از آن درحال اجرا وجود دارد که در آن عامل خدماتی بااستفاده از حلقه while، تأخیر راه‌اندازی عمدی ۵۰۰ میلی‌ثانیه‌ای دارد:

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

فعال کردن پیش‌بارگیری پیمایش

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 با زمان راه‌اندازی کارگر سرویس به‌تأخیر خواهد افتاد. برای رفع این مشکل هم می‌توانیم از پیش‌بارگیری پیمایش استفاده کنیم، اما در این مورد نمی‌خواهیم کل صفحه را پیش‌بارگیری کنیم، بلکه می‌خواهیم یک include را پیش‌بارگیری کنیم.

برای پشتیبانی از این مورد، سرصفحه‌ای با هر درخواست پیش‌بارگیری ارسال می‌شود:

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
});

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