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

วิธีที่เราใช้การแยกโค้ด การแทรกโค้ด และการแสดงผลฝั่งเซิร์ฟเวอร์ใน 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 โดยเฉพาะการทดสอบ "simple" การตั้งค่า 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) แต่ก็ไม่เข้าเกณฑ์เป็นการโต้ตอบอย่างถูกต้อง เนื่องจากผู้ใช้ไม่สามารถบอกได้ว่าข้อมูลที่ป้อนนั้นเกี่ยวกับอะไร 3G จะใช้เวลาสักครู่และ 3 วินาทีเมื่อใช้ 2G จนกว่าแอปจะพร้อมใช้งาน โดยรวมแล้ว แอปนี้ใช้เวลา 6 วินาทีหากใช้งาน 3G และ 11 วินาทีใน 2G เพื่อโต้ตอบกับระบบ

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

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

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

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

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

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

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

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

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

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

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

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

แบบอินไลน์

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

วันที่
ส่วน Light ของคำขอจะบ่งบอกว่าคำขอกำลังรอรับไบต์แรกของการตอบกลับ

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

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

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

ด้วยรายละเอียดของ JavaScript เราจึงลด TTI จาก 8.5 วินาทีเหลือ 7.2 วินาที

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

การแยกโค้ดแบบเข้มงวด

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

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

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

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

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

ควบคุมได้ถนัดกว่าเดิม

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

บทสรุป

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

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

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

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