เทคนิคในการทําให้เว็บแอปโหลดได้เร็ว แม้ในโทรศัพท์รุ่นเก่า

วิธีที่เราใช้การแยกโค้ด การแทรกโค้ดในบรรทัด และการแสดงผลฝั่งเซิร์ฟเวอร์ใน PROXX

ในงาน Google I/O 2019 Mariko, Jake และฉันได้เปิดตัว PROXX ซึ่งเป็นเกม Minesweeper เวอร์ชันโคลนที่ทันสมัยสำหรับเว็บ สิ่งที่ทำให้ PROXX แตกต่างคือการมุ่งเน้นที่การช่วยเหลือพิเศษ (คุณเล่นเกมนี้ด้วยโปรแกรมอ่านหน้าจอได้) และความสามารถในการทำงานบนฟีเจอร์โฟนได้ดีพอๆ กับบนอุปกรณ์เดสก์ท็อประดับไฮเอนด์ ฟีเจอร์โฟนมีข้อจำกัดหลายประการ ดังนี้

  • CPU ประสิทธิภาพต่ำ
  • GPU มีประสิทธิภาพต่ำหรือไม่มี GPU
  • หน้าจอขนาดเล็กที่ไม่มีการป้อนข้อมูลด้วยการสัมผัส
  • หน่วยความจำมีจำนวนจำกัดมาก

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

เกมเพลย์ PROXX

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

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

การบันทึกสถานะที่เป็นอยู่

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

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

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

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

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

[First Meaningful Paint][FMP] ใน PROXX เวอร์ชันที่ไม่ได้เพิ่มประสิทธิภาพนั้น_ในทางเทคนิค_เป็นแบบ[อินเทอร์แอกทีฟ][TTI] แต่ไม่มีประโยชน์ต่อผู้ใช้

หลังจากดาวน์โหลด JS ที่บีบอัดด้วย gzip ประมาณ 62 KB และสร้าง DOM แล้ว ผู้ใช้จะเห็นแอปของเรา แอปนี้จะโต้ตอบได้ในทางเทคนิค อย่างไรก็ตาม เมื่อดูภาพจะเห็นความเป็นจริงที่แตกต่างออกไป แบบอักษรบนเว็บยังคงโหลดอยู่เบื้องหลัง และผู้ใช้จะไม่เห็นข้อความจนกว่าแบบอักษรจะพร้อมใช้งาน แม้ว่าสถานะนี้จะถือเป็น First Meaningful Paint (FMP) แต่ก็ไม่ถือว่าเป็นอินเทอร์แอกทีฟอย่างเหมาะสม เนื่องจากผู้ใช้ไม่ทราบว่าอินพุตแต่ละรายการเกี่ยวกับอะไร และใช้เวลาอีก 1 วินาทีในเครือข่าย 3G และ 3 วินาทีในเครือข่าย 2G จนกว่าแอปจะพร้อมใช้งาน โดยรวมแล้ว แอปใช้เวลา 6 วินาทีในเครือข่าย 3G และ 11 วินาทีในเครือข่าย 2G เพื่อให้พร้อมโต้ตอบ

การวิเคราะห์ Waterfall

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

  1. มีเส้นบางๆ หลากสีหลายเส้น
  2. ไฟล์ JavaScript จะสร้างเชน เช่น ทรัพยากรที่ 2 จะเริ่มโหลดเมื่อทรัพยากรแรกโหลดเสร็จแล้วเท่านั้น และทรัพยากรที่ 3 จะเริ่มโหลดเมื่อทรัพยากรที่ 2 โหลดเสร็จแล้วเท่านั้น
Waterfall จะให้ข้อมูลเชิงลึกเกี่ยวกับทรัพยากรที่กำลังโหลด เวลาที่โหลด และระยะเวลาที่ใช้

การลดจำนวนการเชื่อมต่อ

เส้นบางแต่ละเส้น (dns, connect, ssl) แสดงถึงการสร้างการเชื่อมต่อ HTTP ใหม่ การตั้งค่าการเชื่อมต่อใหม่มีค่าใช้จ่ายสูงเนื่องจากใช้เวลาประมาณ 1 วินาทีใน 3G และประมาณ 2.5 วินาทีใน 2G ใน Waterfall เราเห็นการเชื่อมต่อใหม่สำหรับรายการต่อไปนี้

  • คำขอที่ 1: index.html ของเรา
  • คำขอที่ 5: รูปแบบอักษรจาก fonts.googleapis.com
  • คำขอที่ 8: Google Analytics
  • คำขอที่ 9: ไฟล์แบบอักษรจาก fonts.gstatic.com
  • คำขอที่ 14: ไฟล์ Manifest ของเว็บแอป

การเชื่อมต่อใหม่สำหรับ index.html เป็นสิ่งที่หลีกเลี่ยงไม่ได้ เบราว์เซอร์ต้องสร้างการเชื่อมต่อกับเซิร์ฟเวอร์ของเราเพื่อรับเนื้อหา คุณหลีกเลี่ยงการเชื่อมต่อใหม่สำหรับ Google Analytics ได้โดยการแทรกบางอย่าง เช่น Minimal Analytics ไว้ในบรรทัด แต่ Google Analytics ไม่ได้บล็อกแอปของเราไม่ให้แสดงผลหรือโต้ตอบได้ ดังนั้นเราจึงไม่สนใจว่าแอปจะโหลดเร็วเพียงใด ตามหลักการแล้ว ควรโหลด Google Analytics ในช่วงที่ไม่มีการใช้งาน เมื่อโหลดสิ่งอื่นๆ ทั้งหมดแล้ว วิธีนี้จะช่วยไม่ให้ใช้แบนด์วิดท์หรือพลังการประมวลผลในระหว่างการโหลดครั้งแรก การเชื่อมต่อใหม่สำหรับไฟล์ Manifest ของเว็บแอปจะกำหนดโดยข้อกำหนดการดึงข้อมูล เนื่องจากต้องโหลดไฟล์ Manifest ผ่านการเชื่อมต่อที่ไม่มีข้อมูลเข้าสู่ระบบ ขอย้ำอีกครั้งว่าไฟล์ Manifest ของเว็บแอปไม่ได้บล็อกแอปของเราไม่ให้แสดงผลหรือโต้ตอบได้ ดังนั้นเราจึงไม่ต้องกังวลมากนัก

อย่างไรก็ตาม แบบอักษรทั้ง 2 แบบและสไตล์ของแบบอักษรเป็นปัญหาเนื่องจากบล็อกการแสดงผลและการโต้ตอบ หากเราดู CSS ที่ส่งโดย fonts.googleapis.com จะมีกฎ @font-face เพียง 2 ข้อ โดยกฎ 1 ข้อสำหรับแบบอักษรแต่ละแบบ สไตล์แบบอักษรมีขนาดเล็กมากจนเราตัดสินใจรวมไว้ใน HTML เพื่อลดการเชื่อมต่อที่ไม่จำเป็นออกไป 1 รายการ เราสามารถคัดลอกไฟล์แบบอักษรไปยังเซิร์ฟเวอร์ของเราเองเพื่อหลีกเลี่ยงค่าใช้จ่ายในการตั้งค่าการเชื่อมต่อ

การโหลดพร้อมกัน

เมื่อดูที่ Waterfall เราจะเห็นว่าเมื่อไฟล์ JavaScript แรกโหลดเสร็จแล้ว ไฟล์ใหม่จะเริ่มโหลดทันที ซึ่งเป็นเรื่องปกติสำหรับการขึ้นต่อกันของโมดูล โมดูลหลักของเราอาจมีการนำเข้าแบบคงที่ ดังนั้น JavaScript จึงทำงานไม่ได้จนกว่าจะโหลดการนำเข้าเหล่านั้น สิ่งสำคัญที่ควรทราบคือระบบจะทราบทรัพยากร Dependency ประเภทนี้ในเวลาบิลด์ เราสามารถใช้แท็ก <link rel="preload"> เพื่อให้แน่ใจว่าทรัพยากร Dependency ทั้งหมดจะเริ่มโหลดทันทีที่เราได้รับ HTML

ผลลัพธ์

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

เราใช้แถบแสดงตัวอย่างรูปภาพของ WebPageTest เพื่อดูว่าการเปลี่ยนแปลงของเราบรรลุผลอะไรบ้าง

การเปลี่ยนแปลงเหล่านี้ลด TTI จาก 11 เหลือ 8.5 ซึ่งเป็นเวลาในการตั้งค่าการเชื่อมต่อประมาณ 2.5 วินาทีที่เราตั้งเป้าที่จะนำออก เราทำได้ดีมาก

การแสดงผลล่วงหน้า

แม้ว่าเราจะเพิ่งลด TTI แต่ก็ไม่ได้ส่งผลต่อหน้าจอสีขาวที่ผู้ใช้ต้องทนดูเป็นเวลานานถึง 8.5 วินาที การปรับปรุงที่สำคัญที่สุดสำหรับ FMP สามารถทำได้โดยการส่งมาร์กอัปที่มีสไตล์ใน index.html เทคนิคที่ใช้กันโดยทั่วไปเพื่อให้บรรลุเป้าหมายนี้คือการแสดงผลล่วงหน้าและการแสดงผลฝั่งเซิร์ฟเวอร์ ซึ่งมีความเกี่ยวข้องกันอย่างใกล้ชิดและอธิบายไว้ในการแสดงผลบนเว็บ ทั้ง 2 เทคนิคจะเรียกใช้เว็บแอปใน Node และแปลง DOM ที่ได้เป็น HTML การแสดงผลฝั่งเซิร์ฟเวอร์จะดำเนินการนี้ตามคำขอในฝั่งเซิร์ฟเวอร์ ส่วนการแสดงผลล่วงหน้าจะดำเนินการนี้ในเวลาบิลด์และจัดเก็บเอาต์พุตเป็น index.html ใหม่ เนื่องจาก PROXX เป็นแอป JAMStack และไม่มีฝั่งเซิร์ฟเวอร์ เราจึงตัดสินใจใช้การแสดงผลล่วงหน้า

การติดตั้งใช้งานโปรแกรมแสดงผลล่วงหน้าทำได้หลายวิธี ใน PROXX เราเลือกใช้ Puppeteer ซึ่งจะเริ่ม Chrome โดยไม่มี UI และช่วยให้คุณควบคุมอินสแตนซ์นั้นจากระยะไกลด้วย Node API ได้ เราใช้สิ่งนี้เพื่อแทรกมาร์กอัปและ JavaScript ของเรา จากนั้นจึงอ่าน DOM กลับมาเป็นสตริงของ HTML เนื่องจากเราใช้โมดูล CSS เราจึงแทรก CSS ในหน้าของสไตล์ที่ต้องการได้โดยไม่มีค่าใช้จ่าย

  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.setContent(rawIndexHTML);
  await page.evaluate(codeToRun);
  const renderedHTML = await page.content();
  browser.close();
  await writeFile("index.html", renderedHTML);

เมื่อใช้เมตริกนี้ เราคาดว่าจะเห็นการปรับปรุง FMP เรายังคงต้องโหลดและเรียกใช้ JavaScript ในปริมาณเท่าเดิม ดังนั้นจึงไม่ควรคาดหวังว่า TTI จะเปลี่ยนแปลงมากนัก หากมีสิ่งใด index.html ของเราก็มีขนาดใหญ่ขึ้นและอาจทำให้ TTI ของเราช้าลงเล็กน้อย มีวิธีเดียวที่จะทราบได้ นั่นคือการเรียกใช้ WebPageTest

แถบแสดงตัวอย่างรูปภาพแสดงให้เห็นถึงการปรับปรุงที่ชัดเจนสำหรับเมตริก FMP TTI ส่วนใหญ่จะไม่ได้รับผลกระทบ

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

การแทรกในบรรทัด

เมตริกอีกรายการที่ทั้ง DevTools และ WebPageTest มีให้คือเวลาที่ได้รับข้อมูลไบต์แรก (TTFB) ซึ่งเป็นเวลาที่ใช้ตั้งแต่ไบต์แรกของคำขอที่ส่งไปจนถึงไบต์แรกของการตอบกลับที่ได้รับ เวลานี้มักเรียกว่า Round Trip Time (RTT) ด้วย แม้ว่าในทางเทคนิคแล้วตัวเลข 2 ตัวนี้จะมีความแตกต่างกัน โดย RTT จะไม่รวมเวลาประมวลผลคำขอในฝั่งเซิร์ฟเวอร์ DevToolsและ WebPageTest จะแสดงภาพ TTFB ด้วยสีอ่อนภายในบล็อกคำขอ/การตอบกลับ

ส่วนสีอ่อนของคำขอหมายความว่าคำขอกำลังรอรับไบต์แรกของการตอบกลับ

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

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

CSS ที่สำคัญของเราได้รับการแทรกในหน้าแล้วด้วยโมดูล CSS และเครื่องมือแสดงผลล่วงหน้าที่ใช้ Puppeteer สำหรับ JavaScript เราจำเป็นต้องแทรกโมดูลที่สำคัญและทรัพยากร Dependencyในหน้าเว็บ งานนี้มีความยากแตกต่างกันไปตาม Bundler ที่คุณใช้

การแทรก JavaScript ในบรรทัดช่วยลด TTI จาก 8.5 วินาทีเหลือ 7.2 วินาที

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

การแยกโค้ดแบบ Aggressive

ใช่ index.html ของเรามีทุกอย่างที่จำเป็นต่อการโต้ตอบกับผู้ใช้ แต่เมื่อตรวจสอบอย่างละเอียดแล้ว กลับพบว่ามีข้อมูลอื่นๆ ทั้งหมดด้วย index.html ของเรามีขนาดประมาณ 43 KB ลองพิจารณาความสัมพันธ์กับสิ่งที่ผู้ใช้โต้ตอบได้ตั้งแต่เริ่มต้น เรามีแบบฟอร์มสำหรับกำหนดค่าเกมซึ่งมีคอมโพเนนต์ 2-3 รายการ ปุ่มเริ่ม และอาจมีโค้ดบางส่วนเพื่อคงอยู่และโหลดการตั้งค่าของผู้ใช้ ก็มีเท่านี้แหละ 43 KB ดูเหมือนจะเยอะมาก

หน้า Landing Page ของ PROXX โดยจะใช้เฉพาะคอมโพเนนต์ที่สำคัญเท่านั้น

หากต้องการทำความเข้าใจว่าขนาดแพ็กเกจมาจากไหน เราสามารถใช้ Source Map Explorer หรือเครื่องมือที่คล้ายกันเพื่อแจกแจงสิ่งที่อยู่ในแพ็กเกจ ตามที่คาดไว้ Bundle ของเรามีตรรกะของเกม, เครื่องมือแสดงผล, หน้าจอชนะ, หน้าจอแพ้ และยูทิลิตีจำนวนมาก หน้า Landing Page ต้องใช้โมดูลเหล่านี้เพียงส่วนย่อยเล็กๆ เท่านั้น การย้ายทุกอย่างที่ไม่จำเป็นอย่างยิ่งสำหรับการโต้ตอบไปยังโมดูลที่โหลดแบบ Lazy Loading จะลด TTI ลงอย่างมาก

การวิเคราะห์เนื้อหาของ `index.html` ของ PROXX แสดงทรัพยากรที่ไม่จำเป็นจำนวนมาก ระบบจะไฮไลต์ทรัพยากรที่สำคัญ

สิ่งที่เราต้องทำคือแยกโค้ด การแยกโค้ดจะแบ่งแพ็กเกจขนาดใหญ่ออกเป็นส่วนย่อยๆ ที่สามารถโหลดแบบ Lazy Loading ได้ตามต้องการ เครื่องมือ Bundler ยอดนิยมอย่าง Webpack, Rollup และ Parcel รองรับการแยกโค้ดโดยใช้ import() แบบไดนามิก Bundler จะวิเคราะห์โค้ดและแทรกโมดูลทั้งหมดที่นำเข้าแบบคงที่ ทุกอย่างที่คุณนำเข้าแบบไดนามิกจะอยู่ในไฟล์ของตัวเอง และจะดึงข้อมูลจากเครือข่ายเมื่อมีการเรียกใช้ import() เท่านั้น แน่นอนว่าการเข้าถึงเครือข่ายมีค่าใช้จ่ายและควรทำก็ต่อเมื่อคุณมีเวลาเหลือเฟือเท่านั้น หลักการสำคัญคือการนำเข้าโมดูลที่จำเป็นอย่างยิ่งในเวลาโหลดแบบคงที่ และโหลดสิ่งอื่นๆ ทั้งหมดแบบไดนามิก แต่คุณไม่ควรรอจนถึงวินาทีสุดท้ายเพื่อโหลดโมดูลแบบ Lazy Loading ที่จะมีการใช้งานอย่างแน่นอน Idle Until Urgent ของ Phil Walton เป็นรูปแบบที่ยอดเยี่ยมสำหรับจุดกึ่งกลางที่เหมาะสมระหว่างการโหลดแบบ Lazy Loading และการโหลดแบบ Eager Loading

ใน PROXX เราได้สร้างไฟล์ lazy.js ที่นำเข้าทุกอย่างที่เราไม่ต้องการแบบคงที่ ในไฟล์หลัก เราจะนำเข้า lazy.js แบบไดนามิกได้ อย่างไรก็ตาม คอมโพเนนต์ Preact บางส่วนของเราไปอยู่ใน lazy.js ซึ่งกลายเป็นเรื่องที่ซับซ้อนเล็กน้อยเนื่องจาก Preact ไม่สามารถจัดการคอมโพเนนต์ที่โหลดแบบ Lazy Loading ได้ทันที ด้วยเหตุนี้ เราจึงเขียน Wrapper คอมโพเนนต์ deferred เล็กๆ ที่ช่วยให้เราแสดงผลตัวยึดตำแหน่งได้จนกว่าคอมโพเนนต์จริงจะโหลด

export default function deferred(componentPromise) {
  return class Deferred extends Component {
    constructor(props) {
      super(props);
      this.state = {
        LoadedComponent: undefined
      };
      componentPromise.then(component => {
        this.setState({ LoadedComponent: component });
      });
    }

    render({ loaded, loading }, { LoadedComponent }) {
      if (LoadedComponent) {
        return loaded(LoadedComponent);
      }
      return loading();
    }
  };
}

เมื่อใช้ฟีเจอร์นี้ เราจะใช้ Promise ของคอมโพเนนต์ในฟังก์ชัน render() ได้ เช่น คอมโพเนนต์ <Nebula> ซึ่งแสดงภาพพื้นหลังแบบเคลื่อนไหวจะถูกแทนที่ด้วย <div> ที่ว่างเปล่าขณะที่คอมโพเนนต์กำลังโหลด เมื่อโหลดคอมโพเนนต์และพร้อมใช้งานแล้ว ระบบจะแทนที่ <div> ด้วยคอมโพเนนต์จริง

const NebulaDeferred = deferred(
  import("/components/nebula").then(m => m.default)
);

return (
  // ...
  <NebulaDeferred
    loading={() => <div />}
    loaded={Nebula => <Nebula />}
  />
);

เมื่อทำทั้งหมดนี้แล้ว เราลดขนาด index.html ลงเหลือเพียง 20 KB ซึ่งน้อยกว่าครึ่งหนึ่งของขนาดเดิม การดำเนินการนี้ส่งผลต่อ FMP และ TTI อย่างไร WebPageTest จะบอกคุณเอง

แถบแสดงตัวอย่างรูปภาพยืนยันว่าตอนนี้ TTI อยู่ที่ 5.4 วินาที ซึ่งเป็นการปรับปรุงที่ก้าวกระโดดจากโมเดล 11s เดิมของเรา

FMP และ TTI ของเราห่างกันเพียง 100 มิลลิวินาที เนื่องจากเป็นเพียงเรื่องของการแยกวิเคราะห์และการเรียกใช้ JavaScript ในบรรทัด หลังจากใช้เวลาเพียง 5.4 วินาทีบนเครือข่าย 2G แอปก็พร้อมให้โต้ตอบได้อย่างสมบูรณ์ ส่วนโมดูลอื่นๆ ทั้งหมดที่มีความสำคัญน้อยกว่าจะโหลดในเบื้องหลัง

กลไพ่เพิ่มเติม

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

บทสรุป

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

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

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

โปรดติดตามตอนที่ 2 ซึ่งเราจะพูดถึงวิธีเพิ่มประสิทธิภาพรันไทม์ในอุปกรณ์ที่มีข้อจำกัดสูง