เทคนิคเพื่อทำให้เว็บแอปโหลดเร็วแม้ในฟีเจอร์โฟน

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

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

  • CPU ที่ไม่รัดกุม
  • 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 (คล้ายกับที่คุณเห็นในเครื่องมือสำหรับนักพัฒนาเว็บ) และแถบแสดงตัวอย่างรูปภาพที่ด้านบน แถบฟิล์มจะแสดงสิ่งที่ผู้ใช้เห็นขณะโหลดแอป ในระบบ 2G ประสบการณ์การโหลด PROXX เวอร์ชันที่ไม่ได้เพิ่มประสิทธิภาพนั้นค่อนข้างแย่:

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

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

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

หลังจากดาวน์โหลด JS ของ gzip ประมาณ 62 KB และสร้าง DOM แล้ว ผู้ใช้จะเห็นแอปของเรา แอปเป็นแบบอินเทอร์แอกทีฟทางเทคนิค แต่เมื่อมองจากภาพ กลับเห็นภาพของความเป็นจริงที่ต่างกัน แบบอักษรของเว็บยังคงโหลดอยู่ในพื้นหลังและจนกว่าจะพร้อม ผู้ใช้จะไม่เห็นข้อความใดๆ แม้ว่าสถานะนี้จัดว่าเป็น First Meaningful Paint (FMP) แต่ก็ไม่ถือว่าเป็นการโต้ตอบที่เหมาะสม เนื่องจากผู้ใช้ไม่สามารถบอกได้ว่าอินพุตใดๆ เกี่ยวกับอะไร ระบบจะใช้เวลาอีก 3 วินาทีเมื่อใช้ 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 ข้อต่อแบบอักษร รูปแบบแบบอักษรมีขนาดเล็กมาก เราจึงตัดสินใจแทรกในบรรทัดใน HTML ของเรา และนำการเชื่อมต่อที่ไม่จำเป็นออก 1 รายการ เพื่อหลีกเลี่ยงไม่ให้เกิดค่าใช้จ่ายในการตั้งค่าการเชื่อมต่อสำหรับไฟล์แบบอักษร เราสามารถคัดลอกไฟล์ดังกล่าวไปยังเซิร์ฟเวอร์ของเราเอง

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

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

ผลลัพธ์

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

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

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

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

ถึงแม้เราจะลด TTI ให้ แต่เราไม่ส่งผลกระทบต่อหน้าจอสีขาวที่ยาวตลอดไปซึ่งผู้ใช้ต้องอยู่เป็นเวลา 8.5 วินาที เราสามารถพูดได้ว่าการปรับปรุง FMP ครั้งใหญ่ที่สุดจะทําได้โดยการส่งมาร์กอัปที่มีการจัดรูปแบบใน index.html เทคนิคทั่วไปในการทำเช่นนี้คือ การแสดงผลล่วงหน้าและการแสดงผลฝั่งเซิร์ฟเวอร์ ซึ่งมีความเกี่ยวข้องอย่างใกล้ชิดและมีอธิบายไว้ในการแสดงผลบนเว็บ ทั้ง 2 เทคนิคเรียกใช้เว็บแอปในโหนดและเรียงอันดับ 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 มาใช้แล้ว เราอาจปรับปรุง FMP ให้ดีขึ้น เรายังคงต้องโหลดและเรียกใช้ JavaScript ในปริมาณเท่าเดิมเหมือนก่อนหน้านี้ ดังนั้นจึงไม่ควรคาดหวังว่า TTI จะเปลี่ยนแปลงไปมาก เพราะ index.html ของเราใหญ่ขึ้นและอาจทำให้เปลี่ยน TTI ไปเล็กน้อย วิธีการตรวจสอบมีเพียงวิธีเดียวคือการเรียกใช้ WebPageTest

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

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

ภายใน

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

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

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

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

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

การกำหนดค่า JavaScript ทำให้เราสามารถลด TTI ของเราจาก 8.5 วินาทีเป็น 7.2 วินาที

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

การแยกโค้ดเชิงรุก

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

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

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

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

สิ่งที่เราต้องทำคือการแยกโค้ด การแยกโค้ดจะแยกแพ็กเกจแบบโมโนลิธออกเป็นส่วนเล็กๆ ที่สามารถใช้งานแบบ Lazy Loading ได้ตามคำสั่ง แพ็กเกจยอดนิยม เช่น Webpack, Rollup และ Parcel รองรับการแยกรหัสโดยใช้ import() แบบไดนามิก Bundler จะวิเคราะห์โค้ดและโมดูลทั้งหมดที่นำเข้าแบบคงที่แบบในบรรทัด ทุกอย่างที่คุณนำเข้าแบบไดนามิกจะใส่ลงในไฟล์ของตัวมันเองและจะดึงข้อมูลจากเครือข่ายก็ต่อเมื่อเรียกใช้ import() แล้ว แน่นอนว่าการเข้าถึงเครือข่ายมีค่าใช้จ่ายและควรทำก็ต่อเมื่อคุณมีเวลาว่างเท่านั้น หลักการสำคัญในที่นี้คือการนำเข้าโมดูลที่จำเป็นในเวลาโหลดคงที่และโหลดส่วนอื่นๆ แบบไดนามิก แต่คุณไม่ควรรอจนถึงวินาทีสุดท้ายในการโหลดโมดูลแบบ Lazy Loading ซึ่งเราจะใช้อย่างแน่นอน ช่อง Idle Along Urgent ของ Phil Walton เป็นรูปแบบที่ยอดเยี่ยมสำหรับการเริ่มต้นอย่างลงตัวระหว่างการโหลดแบบ Lazy 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();
    }
  };
}

ด้วยเหตุนี้ เราจึงใช้ "คำมั่นสัญญา" ของคอมโพเนนต์ในฟังก์ชัน 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 วินาที การพัฒนาใหม่อย่างมากจาก 11 เพลงต้นฉบับของเรา

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

ประสิทธิภาพในมือที่มากขึ้น

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

บทสรุป

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

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

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

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