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

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

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

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

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

เกมเพลย์ PROXX

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

บทความนี้เป็นส่วนหนึ่งของชุดบทความ 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 เวอร์ชันที่ไม่ได้เพิ่มประสิทธิภาพเป็น [interactive][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 จะเริ่มโหลดก็ต่อเมื่อทรัพยากรที่ 1 โหลดเสร็จแล้ว และทรัพยากรที่ 3 จะเริ่มโหลดก็ต่อเมื่อทรัพยากรที่ 2 โหลดเสร็จแล้ว
การแสดงโฆษณาสื่อกลางตามลำดับขั้นจะให้ข้อมูลเชิงลึกเกี่ยวกับทรัพยากรที่โหลดอยู่ ณ เวลานั้นและระยะเวลาที่ใช้ในการโหลด

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

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

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

ผลลัพธ์

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

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

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

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

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

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

การฝัง

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

ส่วนสว่างของคําขอบ่งบอกว่าคําขอกําลังรอรับไบต์แรกของคําตอบ

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

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

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

การฝัง JavaScript ช่วยให้ TTI ลดลงจาก 8.5 วินาทีเป็น 7.2 วินาที

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

การแยกโค้ดอย่างหนัก

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

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

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

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

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

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

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 ซึ่งเราจะพูดถึงวิธีเพิ่มประสิทธิภาพรันไทม์ในอุปกรณ์ที่มีข้อจำกัดสูง