แอปพลิเคชันหลายหน้าที่เร็วยิ่งขึ้นด้วยสตรีม

ในปัจจุบัน เว็บไซต์ หรือแอปเว็บหากคุณต้องการมีแนวโน้มที่จะใช้รูปแบบการนำทางแบบใดแบบหนึ่งจาก 2 แบบดังนี้

  • เบราว์เซอร์รูปแบบการนำทางจะจัดเตรียมไว้ให้โดยค่าเริ่มต้น กล่าวคือ คุณจะป้อน URL ในแถบที่อยู่ของเบราว์เซอร์ แล้วคำขอการนำทางจะแสดงเอกสารเป็นการตอบกลับ จากนั้นคุณคลิกที่ลิงก์ ซึ่งจะยกเลิกการโหลดเอกสารปัจจุบันของอีกเอกสารหนึ่ง นั่นคือ ad infinitum
  • รูปแบบแอปพลิเคชันหน้าเว็บเดียวซึ่งเกี่ยวข้องกับคำขอการนำทางเริ่มต้นเพื่อโหลด Application Shell และอาศัย JavaScript เพื่อป้อนข้อมูล Application Shell ด้วยมาร์กอัปที่แสดงผลโดยไคลเอ็นต์ด้วยเนื้อหาจาก API แบ็กเอนด์สำหรับ "การนำทาง" แต่ละรายการ

ผู้เสนอได้แนะนำประโยชน์ของแต่ละแนวทางไว้ดังนี้

  • รูปแบบการนำทางที่เบราว์เซอร์มีให้โดยค่าเริ่มต้นนั้นมีความยืดหยุ่น เนื่องจากเส้นทางไม่จําเป็นต้องมี JavaScript จึงจะเข้าถึงได้ การแสดงมาร์กอัปโดยไคลเอ็นต์ผ่าน JavaScript ก็อาจเป็นขั้นตอนที่อาจมีค่าใช้จ่ายสูงเช่นกัน ซึ่งหมายความว่าอุปกรณ์ระดับล่างอาจอยู่ในสถานการณ์ที่เนื้อหาล่าช้าเนื่องจากอุปกรณ์ถูกบล็อกสคริปต์ในการประมวลผลเนื้อหา
  • ในทางกลับกัน แอปพลิเคชันหน้าเว็บเดียว (SPA) อาจให้การนำทางที่รวดเร็วขึ้นหลังจากการโหลดครั้งแรก เบราว์เซอร์สามารถนำเสนอประสบการณ์ "คล้ายแอป" ที่รวดเร็วและคล้ายแอป แทนที่จะอาศัยเบราว์เซอร์ในการยกเลิกการโหลดเอกสารใหม่ทั้งหมด (และทำซ้ำๆ สำหรับทุกการนำทาง) แม้จะต้องใช้ JavaScript ในการทำงานก็ตาม

ในโพสต์นี้ เราจะพูดถึงวิธีที่ 3 ที่สร้างสมดุลระหว่าง 2 แนวทางที่อธิบายไว้ข้างต้น นั่นคือการใช้ Service Worker เพื่อแคชองค์ประกอบทั่วไปของเว็บไซต์ไว้ล่วงหน้า เช่น มาร์กอัปส่วนหัวและส่วนท้าย และใช้สตรีมเพื่อให้การตอบสนอง HTML แก่ลูกค้าโดยเร็วที่สุดเท่าที่จะเป็นไปได้ ในขณะที่ยังคงใช้รูปแบบการนำทางเริ่มต้นของเบราว์เซอร์

เหตุใดจึงควรสตรีมคำตอบ HTML ใน Service Worker

สตรีมมิงเป็นสิ่งที่เว็บเบราว์เซอร์ของคุณทำอยู่แล้วเมื่อส่งคำขอ ขั้นตอนนี้มีความสำคัญมากในบริบทของคำขอการนำทาง เนื่องจากจะทำให้มั่นใจได้ว่าเบราว์เซอร์จะไม่ถูกบล็อกเพื่อรอการตอบกลับทั้งหมด จึงจะสามารถเริ่มต้นแยกวิเคราะห์มาร์กอัปเอกสารและแสดงหน้าเว็บ

แผนภาพแสดง HTML ที่ไม่ใช่สตรีมมิงเทียบกับ HTML สตรีมมิง ในกรณีเดิม เพย์โหลดมาร์กอัปทั้งหมดจะไม่ได้รับการประมวลผลจนกว่าจะมาถึง ในชั้นหลัง มาร์กอัปจะได้รับการประมวลผลทีละส่วนเมื่อมาถึงเป็นส่วนๆ จากเครือข่าย

สำหรับโปรแกรมทำงานของบริการ สตรีมมิงจะแตกต่างกันเล็กน้อยเนื่องจากใช้ Streams API ของ JavaScript งานที่สำคัญที่สุดที่โปรแกรมทำงานของบริการต้องดำเนินการคือการสกัดกั้นและตอบกลับคำขอ ซึ่งรวมถึงคำขอการนำทาง

คำขอเหล่านี้อาจโต้ตอบกับแคชได้หลายวิธี แต่รูปแบบการแคชที่พบบ่อยสำหรับมาร์กอัปคือควรใช้การตอบกลับจากเครือข่ายก่อนแต่จะกลับไปที่แคชหากมีสำเนาเก่า และระบุการตอบสนองสำรองทั่วไปหากการตอบสนองที่ใช้งานได้ไม่อยู่ในแคช (ไม่บังคับ)

นี่เป็นรูปแบบที่ทดสอบมาเป็นระยะเวลาแล้วสำหรับมาร์กอัปที่ทำงานได้ดี แต่ถึงแม้ว่าจะช่วยเพิ่มความน่าเชื่อถือในแง่ของการเข้าถึงแบบออฟไลน์ แต่ก็ไม่ได้ให้ข้อได้เปรียบด้านประสิทธิภาพที่มีอยู่ตามธรรมชาติสำหรับคำขอการนำทางที่อาศัยกลยุทธ์แบบเครือข่ายเป็นหลักหรือเครือข่ายเท่านั้น นี่เป็นที่มาของการสตรีม และเราจะศึกษาวิธีใช้โมดูล workbox-streams ที่ขับเคลื่อนโดย Streams API ในโปรแกรม Workbox เพื่อเร่งคำขอการนำทางในเว็บไซต์ที่มีหลายหน้าของคุณ

รายละเอียดหน้าเว็บทั่วไป

หากกล่าวอย่างตรงไปตรงมา เว็บไซต์มีแนวโน้มที่จะมีองค์ประกอบทั่วไปที่มีอยู่ในทุกหน้า การจัดเรียงองค์ประกอบของหน้าตามปกติมักมีลักษณะดังนี้

  • ส่วนหัว
  • เนื้อหา
  • ส่วนท้าย

เมื่อใช้ web.dev เป็นตัวอย่าง รายละเอียดขององค์ประกอบทั่วไปจะมีลักษณะดังนี้

รายละเอียดองค์ประกอบทั่วไปในเว็บไซต์ web.dev พื้นที่ส่วนกลางที่ระบุไว้จะมีการทำเครื่องหมายเป็น "ส่วนหัว" "เนื้อหา" และ "ส่วนท้าย"

เป้าหมายที่อยู่เบื้องหลังการระบุส่วนต่างๆ ของหน้าเว็บคือ เรากำหนดสิ่งที่สามารถแคชล่วงหน้าและดึงข้อมูลได้โดยไม่ต้องไปที่เครือข่าย ซึ่งได้แก่ มาร์กอัปส่วนหัวและส่วนท้ายที่ใช้กันโดยทั่วไปในทุกหน้า และส่วนหนึ่งของหน้าเว็บที่เราจะไปที่เครือข่ายก่อนเสมอ ซึ่งในกรณีนี้คือมาร์กอัปของส่วนหัวและส่วนท้ายที่ใช้กันทั่วไป

เมื่อรู้วิธีแบ่งส่วนของหน้าเว็บและระบุองค์ประกอบทั่วไปแล้ว เราสามารถเขียน Service Worker ที่ดึงมาร์กอัปส่วนหัวและส่วนท้ายทันทีจากแคชทันทีในขณะที่ขอเฉพาะเนื้อหาจากเครือข่าย

จากนั้น เราจึงใช้ Streams API ผ่าน workbox-streams ต่อส่วนต่างๆ เหล่านี้เข้าด้วยกันและตอบกลับคำขอการนำทางได้ทันที ขณะขอมาร์กอัปในจำนวนขั้นต่ำที่จำเป็นจากเครือข่าย

การสร้างโปรแกรมทำงานของบริการสตรีมมิง

เมื่อพูดถึงการสตรีมเนื้อหาบางส่วนในโปรแกรมทำงานของบริการ (Service Worker) ก็มีปัจจัยหลายอย่าง แต่จะต้องสำรวจดูกระบวนการในแต่ละขั้นตอนอย่างละเอียดโดยเริ่มจากวิธีจัดโครงสร้างเว็บไซต์

การแบ่งเว็บไซต์ของคุณออกเป็นส่วนๆ

ก่อนเริ่มเขียนโปรแกรมทำงานของบริการสตรีมมิง คุณต้องทำ 3 สิ่งต่อไปนี้

  1. สร้างไฟล์ที่มีเฉพาะมาร์กอัปส่วนหัวของเว็บไซต์
  2. สร้างไฟล์ที่มีเฉพาะมาร์กอัปส่วนท้ายของเว็บไซต์
  3. ดึงเนื้อหาหลักของแต่ละหน้าออกมาในไฟล์แยกต่างหาก หรือตั้งค่าให้ระบบแสดงเฉพาะเนื้อหาของหน้าตามเงื่อนไขตามส่วนหัวของคำขอ HTTP

อย่างที่คุณอาจคาดหวังไว้ ขั้นตอนสุดท้ายคือส่วนที่ยากที่สุด โดยเฉพาะหากเว็บไซต์ของคุณเป็นแบบคงที่ หากเป็นเช่นนั้น คุณจะต้องสร้างหน้าเว็บแต่ละหน้า 2 เวอร์ชัน โดยเวอร์ชันหนึ่งจะมีมาร์กอัปหน้าแบบเต็ม ขณะที่อีกเวอร์ชันจะมีเฉพาะเนื้อหา

การแต่งโปรแกรมทำงานของบริการสตรีมมิง

หากคุณยังไม่ได้ติดตั้งโมดูล workbox-streams คุณจะต้องติดตั้งเพิ่มเติมนอกเหนือจากโมดูล Workbox ใดก็ตามที่คุณติดตั้งอยู่ในปัจจุบัน ในตัวอย่างเฉพาะนี้ที่เกี่ยวข้องกับแพ็กเกจต่อไปนี้

npm i workbox-navigation-preload workbox-strategies workbox-routing workbox-precaching workbox-streams --save

จากตรงนี้ ขั้นตอนถัดไปคือการสร้าง Service Worker ใหม่และแคชส่วนต่างๆ ของส่วนหัวและส่วนท้ายไว้ล่วงหน้า

กำลังแคชบางส่วนล่วงหน้า

สิ่งแรกที่คุณจะทำคือสร้าง Service Worker ในรูทของโปรเจ็กต์ชื่อ sw.js (หรือชื่อไฟล์ใดก็ได้ที่คุณต้องการ) ซึ่งคุณจะเริ่มต้นด้วยสิ่งต่อไปนี้

// sw.js
import * as navigationPreload from 'workbox-navigation-preload';
import {NetworkFirst} from 'workbox-strategies';
import {registerRoute} from 'workbox-routing';
import {matchPrecache, precacheAndRoute} from 'workbox-precaching';
import {strategy as composeStrategies} from 'workbox-streams';

// Enable navigation preload for supporting browsers
navigationPreload.enable();

// Precache partials and some static assets
// using the InjectManifest method.
precacheAndRoute([
  // The header partial:
  {
    url: '/partial-header.php',
    revision: __PARTIAL_HEADER_HASH__
  },
  // The footer partial:
  {
    url: '/partial-footer.php',
    revision: __PARTIAL_FOOTER_HASH__
  },
  // The offline fallback:
  {
    url: '/offline.php',
    revision: __OFFLINE_FALLBACK_HASH__
  },
  ...self.__WB_MANIFEST
]);

// To be continued...

โค้ดนี้จะทำงาน 2 ประการดังนี้

  1. เปิดใช้การโหลดการนำทางล่วงหน้าสำหรับเบราว์เซอร์ที่รองรับ
  2. แคชมาร์กอัปส่วนหัวและส่วนท้ายไว้ล่วงหน้า ซึ่งหมายความว่าระบบจะดึงข้อมูลมาร์กอัปส่วนหัวและส่วนท้ายของทุกหน้าทันที เนื่องจากจะไม่ถูกบล็อกโดยเครือข่าย
  3. แคชเนื้อหาแบบคงที่ล่วงหน้าในตัวยึดตำแหน่ง __WB_MANIFEST ที่ใช้เมธอด injectManifest

การตอบกลับสตรีมมิง

การทำให้ Service Worker สตรีมคำตอบแบบต่อกันเป็นงานใหญ่ที่สุดในความพยายามนี้ แต่ถึงอย่างนั้น Workbox และ workbox-streams ก็ทำให้เรื่องนี้กระชับขึ้นมากเมื่อคุณทำทั้งหมดนี้ด้วยตัวเอง

// sw.js
import * as navigationPreload from 'workbox-navigation-preload';
import {NetworkFirst} from 'workbox-strategies';
import {registerRoute} from 'workbox-routing';
import {matchPrecache, precacheAndRoute} from 'workbox-precaching';
import {strategy as composeStrategies} from 'workbox-streams';

// ...
// Prior navigation preload and precaching code omitted...
// ...

// The strategy for retrieving content partials from the network:
const contentStrategy = new NetworkFirst({
  cacheName: 'content',
  plugins: [
    {
      // NOTE: This callback will never be run if navigation
      // preload is not supported, because the navigation
      // request is dispatched while the service worker is
      // booting up. This callback will only run if navigation
      // preload is _not_ supported.
      requestWillFetch: ({request}) => {
        const headers = new Headers();

        // If the browser doesn't support navigation preload, we need to
        // send a custom `X-Content-Mode` header for the back end to use
        // instead of the `Service-Worker-Navigation-Preload` header.
        headers.append('X-Content-Mode', 'partial');

        // Send the request with the new headers.
        // Note: if you're using a static site generator to generate
        // both full pages and content partials rather than a back end
        // (as this example assumes), you'll need to point to a new URL.
        return new Request(request.url, {
          method: 'GET',
          headers
        });
      },
      // What to do if the request fails.
      handlerDidError: async ({request}) => {
        return await matchPrecache('/offline.php');
      }
    }
  ]
});

// Concatenates precached partials with the content partial
// obtained from the network (or its fallback response).
const navigationHandler = composeStrategies([
  // Get the precached header markup.
  () => matchPrecache('/partial-header.php'),
  // Get the content partial from the network.
  ({event}) => contentStrategy.handle(event),
  // Get the precached footer markup.
  () => matchPrecache('/partial-footer.php')
]);

// Register the streaming route for all navigation requests.
registerRoute(({request}) => request.mode === 'navigate', navigationHandler);

// Your service worker can end here, or you can add more
// logic to suit your needs, such as runtime caching, etc.

โค้ดนี้ประกอบด้วยส่วนหลักๆ 3 ส่วนที่เป็นไปตามข้อกำหนดต่อไปนี้

  1. กลยุทธ์ NetworkFirst ใช้เพื่อจัดการกับคำขอบางส่วนของเนื้อหา เมื่อใช้กลยุทธ์นี้ จะมีการระบุชื่อแคชที่กำหนดเองของ content ให้มีเนื้อหาบางส่วน รวมทั้งปลั๊กอินที่กำหนดเองซึ่งกำหนดว่าจะตั้งค่าส่วนหัวของคำขอ X-Content-Mode สำหรับเบราว์เซอร์ที่ไม่รองรับการโหลดการนำทางล่วงหน้าหรือไม่ (ดังนั้นจึงไม่ส่งส่วนหัว Service-Worker-Navigation-Preload) ปลั๊กอินนี้ยังคำนวณด้วยว่าจะส่งเวอร์ชันที่แคชไว้ล่าสุดของเนื้อหาบางส่วน หรือส่งหน้าสำรองแบบออฟไลน์ในกรณีที่ไม่มีเวอร์ชันที่แคชไว้สำหรับคำขอปัจจุบัน
  2. เมธอด strategy ใน workbox-streams (ใช้นามแฝงเป็น composeStrategies ที่นี่) เพื่อเชื่อมโยงส่วนหัวและส่วนท้ายที่ทำแคชล่วงหน้าไว้กับเนื้อหาบางส่วนที่มีการร้องขอจากเครือข่าย
  3. ระบบจะปรับเปลี่ยนรูปแบบทั้งหมดผ่าน registerRoute สำหรับคำขอการนำทาง

เราจึงตั้งค่าการสตรีมคำตอบไว้แล้วพร้อมกับใช้ตรรกะนี้ อย่างไรก็ตาม อาจมีการดำเนินการบางอย่างที่คุณจะต้องดำเนินการในส่วนหลัง เพื่อให้แน่ใจว่าเนื้อหาจากเครือข่ายเป็นหน้าเว็บบางส่วนที่คุณสามารถรวมเข้ากับบางส่วนที่เก็บไว้ล่วงหน้าได้

หากเว็บไซต์ของคุณมีระบบแบ็กเอนด์

คุณจะต้องทราบว่าเมื่อเปิดใช้การโหลดการนำทางล่วงหน้า เบราว์เซอร์จะส่งส่วนหัว Service-Worker-Navigation-Preload ที่มีค่าเป็น true อย่างไรก็ตาม ในตัวอย่างโค้ดข้างต้น เราได้ไม่รองรับส่วนหัวที่กำหนดเองของ X-Content-Mode ในการโหลดล่วงหน้าสำหรับการนำทางของเหตุการณ์ในเบราว์เซอร์ ที่แบ็กเอนด์ คุณจะต้องเปลี่ยนการตอบสนองตามการมีส่วนหัวเหล่านี้ ในเบื้องหลัง PHP ของหน้าเว็บหนึ่งๆ อาจมีลักษณะดังนี้

<?php
// Check if we need to render a content partial
$navPreloadSupported = isset($_SERVER['HTTP_SERVICE_WORKER_NAVIGATION_PRELOAD']) && $_SERVER['HTTP_SERVICE_WORKER_NAVIGATION_PRELOAD'] === 'true';
$partialContentMode = isset($_SERVER['HTTP_X_CONTENT_MODE']) && $_SERVER['HTTP_X_CONTENT_MODE'] === 'partial';
$isPartial = $navPreloadSupported || $partialContentMode;

// Figure out whether to render the header
if ($isPartial === false) {
  // Get the header include
  require_once($_SERVER['DOCUMENT_ROOT'] . '/includes/site-header.php');

  // Render the header
  siteHeader();
}

// Get the content include
require_once('./content.php');

// Render the content
content($isPartial);

// Figure out whether to render the footer
if ($isPartial === false) {
  // Get the footer include
  require_once($_SERVER['DOCUMENT_ROOT'] . '/includes/site-footer.php');

  // Render the footer
  siteFooter();
}
?>

ในตัวอย่างข้างต้น จะมีการเรียกส่วนของเนื้อหาเป็นฟังก์ชัน ซึ่งจะนำค่าของ $isPartial มาเปลี่ยนวิธีแสดงผลส่วนดังกล่าว เช่น ฟังก์ชันแสดงผล content อาจรวมมาร์กอัปบางอย่างในเงื่อนไขเมื่อดึงข้อมูลเป็นบางส่วนเท่านั้น ซึ่งเป็นสิ่งที่จะครอบคลุมในเร็วๆ นี้

ข้อควรพิจารณา

ก่อนที่จะทำให้ Service Worker ใช้งานได้เพื่อสตรีมและเชื่อมต่อส่วนต่างๆ เข้าด้วยกัน มีบางสิ่งที่คุณควรคำนึงถึง แม้ว่าจะเป็นความจริงที่ว่าการใช้โปรแกรมทำงานของบริการในลักษณะนี้ไม่ได้เปลี่ยนแปลงลักษณะการนำทางเริ่มต้นของเบราว์เซอร์ไปโดยพื้นฐาน แต่ก็มีบางสิ่งที่คุณอาจต้องแก้ไข

การอัปเดตองค์ประกอบของหน้าเมื่อไปยังที่ต่างๆ

ส่วนที่ยากที่สุดของวิธีการนี้คือต้องมีการอัปเดตบางอย่างกับลูกค้า เช่น มาร์กอัปส่วนหัวล่วงหน้าหมายความว่าหน้าจะมีเนื้อหาเดียวกันในองค์ประกอบ <title> หรือแม้แต่การจัดการสถานะเปิด/ปิดสำหรับรายการการนำทางจะต้องอัปเดตทุกครั้งที่ไปยังส่วนต่างๆ สิ่งเหล่านี้หรือสิ่งอื่นๆ อาจต้องมีการอัปเดตไคลเอ็นต์สำหรับคำขอการนำทางแต่ละรายการ

วิธีหลีกเลี่ยงปัญหานี้คือการวางองค์ประกอบ <script> ในบรรทัดลงในเนื้อหาบางส่วนที่มาจากเครือข่ายเพื่ออัปเดตสิ่งสำคัญ 2-3 อย่างต่อไปนี้

<!-- The JSON below contains information about the current page. -->
<script id="page-data" type="application/json">'{"title":"Sand Wasp &mdash; World of Wasps","description":"Read all about the sand wasp in this tidy little post."}'</script>
<script>
  const pageData = JSON.parse(document.getElementById('page-data').textContent);

  // Update the page title
  document.title = pageData.title;
</script>
<article>
  <!-- Page content omitted... -->
</article>

นี่เป็นเพียงตัวอย่างหนึ่งของสิ่งที่คุณอาจต้องทำหากตัดสินใจเลือกตั้งค่าโปรแกรมทำงานของบริการนี้ ตัวอย่างเช่น สำหรับแอปพลิเคชันที่ซับซ้อนมากขึ้นซึ่งมีข้อมูลผู้ใช้ เช่น คุณอาจต้องจัดเก็บข้อมูลที่เกี่ยวข้องในเว็บสโตร์ เช่น localStorage และอัปเดตหน้าเว็บจากที่นั่น

การรับมือกับเครือข่ายที่ช้า

ข้อเสียประการหนึ่งของการตอบสนองสตรีมมิงที่ใช้มาร์กอัปจาก Precache อาจเกิดขึ้นเมื่อการเชื่อมต่อเครือข่ายช้า ปัญหาคือมาร์กอัปส่วนหัวจาก Precache จะปรากฏขึ้นทันที แต่เนื้อหาที่บางส่วนจากเครือข่ายอาจใช้เวลาพอสมควรในการมาถึงหลังจากการแสดงผลครั้งแรกของมาร์กอัปส่วนหัว

ซึ่งอาจทำให้เกิดประสบการณ์ที่สับสนได้ และถ้าเครือข่ายทำงานช้ามาก ก็อาจรู้สึกว่าหน้าเว็บเสียหายและไม่แสดงผลต่ออีก ในกรณีเช่นนี้ คุณสามารถเลือกใส่ไอคอนหรือข้อความในมาร์กอัปบางส่วนของเนื้อหา ซึ่งคุณสามารถซ่อนได้เมื่อเนื้อหาโหลดแล้ว

โดยวิธีหนึ่งคือการใช้ CSS สมมติว่าบางส่วนของส่วนหัวลงท้ายด้วยองค์ประกอบ <article> ที่เปิดอยู่ ซึ่งว่างเปล่าจนกว่าเนื้อหาบางส่วนจะเข้ามาเติมข้อมูล คุณอาจเขียนกฎ CSS ให้มีลักษณะคล้ายกับด้านล่างนี้

article:empty::before {
  text-align: center;
  content: 'Loading...';
}

วิธีนี้ได้ผล แต่จะแสดงข้อความการโหลดบนไคลเอ็นต์โดยไม่คำนึงถึงความเร็วของเครือข่าย หากไม่ต้องการให้แสดงข้อความแบบแปลกๆ ให้ลองใช้วิธีนี้เพื่อฝังตัวเลือกไว้ในข้อมูลโค้ดด้านบนในคลาส slow

.slow article:empty::before {
  text-align: center;
  content: 'Loading...';
}

จากที่นี่ คุณสามารถใช้ JavaScript ในบางส่วนของส่วนหัวเพื่ออ่านประเภทการเชื่อมต่อที่มีประสิทธิภาพ (อย่างน้อยในเบราว์เซอร์ Chromium) เพื่อเพิ่มคลาส slow ไปยังองค์ประกอบ <html> ในการเชื่อมต่อบางประเภท

<script>
  const effectiveType = navigator?.connection?.effectiveType;

  if (effectiveType !== '4g') {
    document.documentElement.classList.add('slow');
  }
</script>

การดำเนินการนี้จะช่วยให้มั่นใจว่าประเภทการเชื่อมต่อที่มีประสิทธิภาพซึ่งช้ากว่าประเภท 4g จะได้รับข้อความการโหลด จากนั้นในส่วนของเนื้อหา คุณสามารถใส่องค์ประกอบ <script> ในบรรทัดเพื่อนำคลาส slow ออกจาก HTML เพื่อกำจัดข้อความการโหลด

<script>
  document.documentElement.classList.remove('slow');
</script>

การจัดทำคำตอบสำรอง

สมมติว่าคุณกำลังใช้กลยุทธ์ที่เน้นเครือข่ายเป็นหลักสำหรับบางส่วนของเนื้อหา หากผู้ใช้ออฟไลน์อยู่และไปยังหน้าที่เคยเข้าชมมาก่อน ก็จะถูกครอบคลุม อย่างไรก็ตาม หากผู้ใช้ไปที่หน้าเว็บยังไม่เคยไปมาก่อน ก็จะไม่ได้ดำเนินการใดๆ คุณจะต้องใช้การตอบสนองสำรองเพื่อหลีกเลี่ยงปัญหานี้

โค้ดที่จำเป็นต้องใช้เพื่อสร้างการตอบสนองสำรองจะแสดงในตัวอย่างโค้ดก่อนหน้านี้ กระบวนการนี้มี 2 ขั้นตอน ดังนี้

  1. แคชการตอบสนองแบบออฟไลน์สำรองไว้ล่วงหน้า
  2. ตั้งค่าโค้ดเรียกกลับ handlerDidError ในปลั๊กอินสำหรับกลยุทธ์ที่เน้นเครือข่ายเป็นหลักเพื่อตรวจสอบแคชของหน้าเว็บเวอร์ชันล่าสุดที่เข้าถึง หากไม่มีการเข้าถึงหน้าเว็บ คุณจะต้องใช้เมธอด matchPrecache จากโมดูล workbox-precaching เพื่อเรียกการตอบสนองสำรองจากแคชล่วงหน้า

การแคชและ CDN

หากคุณกำลังใช้รูปแบบสตรีมมิงนี้ใน Service Worker ให้ประเมินว่าสถานการณ์ของคุณเป็นไปตามเงื่อนไขต่อไปนี้หรือไม่

  • คุณใช้ CDN หรือแคชระดับกลาง/สาธารณะประเภทอื่นๆ
  • คุณได้ระบุส่วนหัว Cache-Control ที่มีคำสั่ง max-age และ/หรือ s-maxage ที่ไม่ใช่ 0 ร่วมกับคำสั่ง public

หากคุณสะดวกทั้ง 2 กรณีนี้ แคชกลางอาจเก็บการตอบกลับสำหรับคำขอการนำทางไว้ อย่างไรก็ตาม โปรดทราบว่าเมื่อคุณใช้รูปแบบนี้ คุณอาจแสดงการตอบสนองสองลักษณะที่แตกต่างกันสำหรับ URL ใดๆ ที่ระบุ:

  • การตอบกลับแบบเต็มที่มีมาร์กอัปส่วนหัว เนื้อหา และส่วนท้าย
  • การตอบกลับบางส่วนที่มีเฉพาะเนื้อหา

การดำเนินการนี้อาจทำให้เกิดลักษณะการทำงานที่ไม่พึงประสงค์บางอย่าง ซึ่งส่งผลให้มีมาร์กอัปส่วนหัวและส่วนท้ายเพิ่มขึ้น 2 เท่า เนื่องจากโปรแกรมทำงานของบริการอาจดึงการตอบสนองเต็มรูปแบบจากแคช CDN และรวมเข้ากับมาร์กอัปส่วนหัวและส่วนท้ายที่ทำแคชล่วงหน้า

ในการแก้ปัญหานี้ คุณจะต้องใช้ส่วนหัว Vary ซึ่งส่งผลต่อลักษณะการแคชด้วยการใส่คำตอบที่แคชได้ลงในส่วนหัวอย่างน้อย 1 รายการซึ่งอยู่ในคำขอ เนื่องจากเราเปลี่ยนแปลงการตอบกลับคำขอการนำทางที่แตกต่างกันตามส่วนหัวคำขอ Service-Worker-Navigation-Preload และ X-Content-Mode แบบกำหนดเอง เราจึงต้องระบุส่วนหัว Vary นี้ในการตอบสนองดังนี้

Vary: Service-Worker-Navigation-Preload,X-Content-Mode

ส่วนหัวนี้ช่วยให้เบราว์เซอร์แยกความแตกต่างระหว่างการตอบกลับที่สมบูรณ์และบางส่วนสำหรับคำขอการนำทาง และจะหลีกเลี่ยงปัญหาเกี่ยวกับมาร์กอัปส่วนหัวและส่วนท้ายแบบทวีคูณ เช่นเดียวกับแคชระดับกลาง

ผลลัพธ์

คำแนะนำเกี่ยวกับประสิทธิภาพเวลาที่ใช้ในการโหลดส่วนใหญ่จะมุ่งเน้นที่ "แสดงให้พวกเขาเห็นสิ่งที่คุณมี" อย่ารั้งรอ และอย่ารอจนคุณมีทุกอย่างเสร็จก่อนแล้วจึงจะแสดงให้ผู้ใช้เห็น

Jake Archibald ใน เคล็ดลับสนุกๆ สําหรับเนื้อหาที่ทํางานได้เร็วขึ้น

เบราว์เซอร์มีประสิทธิภาพในการรับมือกับการตอบสนองต่อคำขอการนำทาง แม้จะเป็นเนื้อหาการตอบสนอง HTML ขนาดใหญ่ก็ตาม โดยค่าเริ่มต้น เบราว์เซอร์จะสตรีมและประมวลผลมาร์กอัปเป็นส่วนๆ อย่างต่อเนื่องโดยจะได้ไม่ต้องทำงานเป็นเวลานาน ซึ่งส่งผลดีต่อประสิทธิภาพในการเริ่มต้นทำงาน

วิธีนี้ถือเป็นข้อได้เปรียบของเราเมื่อใช้รูปแบบโปรแกรมทำงานของบริการสตรีมมิง เมื่อใดก็ตามที่คุณตอบกลับคำขอจากโปรแกรมทำงานผ่านทางแคชตั้งแต่เริ่มต้น จุดเริ่มต้นของการตอบกลับก็จะตามมาเกือบจะทันที เมื่อคุณต่อมาร์กอัปส่วนหัวและส่วนท้ายที่บันทึกไว้ล่วงหน้าเข้าด้วยกันกับการตอบสนองจากเครือข่าย คุณจะได้รับประโยชน์ด้านประสิทธิภาพที่เห็นได้ชัดดังต่อไปนี้

  • เวลาเป็นไบต์แรก (TTFB) มักจะลดลงอย่างมาก เนื่องจากไบต์แรกของการตอบสนองต่อคำขอการนำทางจะเกิดขึ้นทันที
  • First Contentful Paint (FCP) จะทำงานเร็วมาก เนื่องจากมาร์กอัปส่วนหัวในแคชล่วงหน้าจะมีการอ้างอิงไปยังสไตล์ชีตที่แคชไว้ ซึ่งหมายความว่าหน้าเว็บจะแสดงผลอย่างรวดเร็วมาก
  • ในบางกรณี Largest Contentful Paint (LCP) ก็อาจเร็วขึ้นด้วย โดยเฉพาะในกรณีที่องค์ประกอบบนหน้าจอที่ใหญ่ที่สุดมาจากบางส่วนของส่วนหัวที่จัดเก็บถาวรล่วงหน้า อย่างไรก็ตาม การแสดงบางอย่างนอกแคชของโปรแกรมทำงานให้เร็วที่สุดควบคู่ไปกับเพย์โหลดมาร์กอัปที่เล็กลงก็อาจส่งผลให้ LCP ดีขึ้นได้

สถาปัตยกรรมสตรีมมิงแบบหลายหน้าอาจยุ่งยากเล็กน้อยในการตั้งค่าและทำซ้ำ แต่ในเชิงทฤษฎีนั้น ความซับซ้อนที่เกี่ยวข้องมักไม่ยากเท่า SPA ในทางทฤษฎี ข้อดีหลักคือคุณไม่ต้องแทนที่รูปแบบการนำทางเริ่มต้นของเบราว์เซอร์ แต่จะเป็นการปรับปรุงรูปแบบดังกล่าว

ยิ่งไปกว่านั้น Workbox ทำให้สถาปัตยกรรมนี้ไม่เพียงแค่เกิดขึ้นได้ แต่ง่ายกว่าการที่คุณนำไปติดตั้งใช้งานด้วยตนเอง ลองใช้เว็บไซต์ของคุณเอง แล้วดูว่าเว็บไซต์แบบหลายหน้าของคุณสามารถให้ผู้ใช้ในวงการทำงานได้เร็วเพียงใด

แหล่งข้อมูล