ลดขนาดไฟล์ให้เล็กจิ๋วด้วย Bundle Analyzer (Page Speed Optimize 2)

Uoo Worapon
fastworkco
Published in
4 min readMay 30, 2022

จากบทความที่แล้วที่เราแก้ปัญหา First Contentful Paint ช้า ด้วยการ build page เป็น static site genertationไปแล้ว ในบทความนี้จะพูดถึงการลดขนาด first load file size

ปัญหาอีกข้อที่ทำให้ First Contentful Paint แย่คือ Large First load file Size

อธิบายคือ file javascript ของเราที่ server Frontend ต้องส่งให้ browser ในครั้งแรกนั้นใหญ่เกินไป ทำให้การ render page ทำได้ช้าลง

first load file size

จากรูปภาพด้านบน เป็นสรุป size ของ page แต่ละหน้าที่ next.js แสดงให้ดูหลัง build bundle เสร็จ โดยที่ column ขวาสุดจะแสดงให้เห็น firstload file size ที่ browser จะได้รับไป render หน้าเว็บเป็นชุดข้อมูลแรก (สังเกตเลขสีแดง column ขวาสุดครับว่าก่อนที่เรา optimize ขนาดประมาณ 540 kB)

และในกรอบสีแดง (first load js shared by all) คือ javascript common file ที่จะถูก share ไปยังทุกหน้า Page ครับ

แต่หากเราดูข้อมูลแค่นี้ เรายังไม่สามารถทำการ optimize อะไรเพิ่มได้ เพราะข้อมูลน้อยเกินไป ผมจึงได้ใช้ bundle analyzer มาช่วยเพื่อให้วิเคราะห์ข้อมูลได้มากยิ่งขึ้น

npm install @next/bundle-analyzer
//package สำหรับ next.js หากใช้ตัวอื่นให้เสิช bundle-analyzer ครับ

โดยช่องสี่เหลี่ยมแต่ละกล่องจะสัมพันธ์กับความใหญ่ของ Package ยิ่ง package ไหนใหญ่ ให้เราสนใจกล่องนั้นเป็นพิเศษครับ

เมื่อเห็น analyzer แล้วหากเจอ package บางตัวที่ใหญ่เกินไปเราสามารถตัดสินใจได้ 2 แบบเพื่อลด size

  1. ใช้ package อื่นๆแทนที่เล็กกว่า หรือเลือกเขียนเองในบางส่วน วิธีนี้อาจต้องมีการแก้ไข code เก่าบางส่วนเพื่อให้ support package ใหม่ที่ใช้
  2. หากไม่มี package อื่นทดแทน หรือไม่ต้องการเปลี่ยน package ใหม่เพราะไม่ต้องการแก้ code เก่าเยอะ ก็ให้พยายามแยก package ที่ใหญ่มากๆ ออกจาก common javascript chunk เพื่อที่ไม่ให้ทุกหน้าต้องรับภาระโหลด package ไปด้วย แต่กลับไม่ได้ใช้ package นั้นครับ

ลองดูของจริงกันครับ โดยดูกล่องสีส้มที่ใหญ่สุดก่อนอันดับแรก

step 1: จัดการ moment.js

จะเห็นว่าไฟล์นี้ประกอบด้วยกล่องหลายใบซ้อนๆกันอีก โดยกล่องที่ใหญ่ที่สุดนั้นคือ node_modules ประกอบด้วย moment ที่มี locale หลายภาษามากๆ

ซึ่งเราหลายๆคนรู้ดีกันอยู่แล้วว่า momentjs เป็น package ที่ใหญ่มากๆ ทาง fastwork จึงตัดสินใจหา package อื่นทดแทนที่ชื่อว่า dayjs และลบ momentjs ออกทั้งหมดไปแล้ว

แต่ถึงอย่างนั้น ผมก็พึ่งเจอว่า ถึงเราไม่ได้เรียกใช้ moment โดยตรง แต่กลับมี package บางตัว ที่ไปเรียกใช้ moment มาอยู่ดี นั่นก็คือreact-dates ครับ

หลักฐานจาก yarn.lock

แต่โชคยังดีที่ผมเคยอ่าน doc ของ next.js เจอว่า เค้าทำการ optimize moment ให้เราแล้ว โดยจะลบภาษาที่เราไม่ได้ใช้ออกให้ ทำให้ไฟล์นั้นเล็กลงพอสมควร เพียงแค่เราต้อง upgrade next.js version ครับ

yarn upgrade next@lastest react@lastest react-dom@lastest// รันโลด

โดยหากใครที่เริ่มสร้าง repo ใหม่ก็จะได้ feature นี้ไปทันที หรือหากใครต้องการ upgrade ลองไปปรับ code ตาม upgrade guide ได้เลยครับ อาจต้องใช้เวลาหน่อย

แต่พอ upgrade เสร็จแล้วก็คุ้มครับ ได้ feature ใหม่ๆ และ performance ที่ดีขึ้นเป็นผลพลอยได้ด้วยครับ ของ fastwork เป็นการ upgrade จาก 10 => 12 เรื่องที่ต้องแก้หลักๆคือ next.js เปลี่ยน Compiler จาก webpack เป็น Rust ที่ใช้ SWC แทน

ผมจะแก้ให้ run ได้ ทีละปมก่อนคือ upgrade จาก webpack4 => webpack5 ให้รันผ่านก่อน แล้วค่อยปรับให้ webpack5 => SWC ครับ หลังจากที่ ใช้ SWC แล้ว project compile ไวขึ้นชัดเจนทั้ง build dev mode และ build production mode

เมื่อ upgrade next.js เสร็จแล้วใส่ config ให้ optimize moment.js

// next.config.js {
...
excludeDefaultMomentLocales: true,
}
* default เป็น true อยู่แล้วครับ แต่ใส่ไว้เพื่อความสบายใจ หรือหากคนอื่นมาอ่าน code จะได้ไม่งง ว่าทำไมภาษาของ moment จึงหายไป

ผลลัพธ์ของ step1 => locale ใน moment หายไป ขนาดของ package ก็เล็กลงไปด้วย

step 2: Code Spliting => แยก Package ที่ใช้กับแค่บางหน้าออกจาก common file chunk เพื่อลดภาระที่หน้าอื่นต้องโหลดไปด้วย แต่กลับไม่ได้ใช้งาน

package ที่ต้องการแยก: react-dates, branch-sdk

โดยปกติแล้ว next.js จะทำ code spliting ให้อยู่แล้วโดย base จาก route ในแต่ละหน้าครับ แต่เรายังสามารถทำ code spliting เพิ่มด้วยตัวเองได้โดยใช้ dynamic import

import dynamic from 'next/dynamic'

const HelloComponent = dynamic(() => import('../components/hello'))

function Home() {
return (
<div>
<Header />
<HelloComponent />
<p>HOME PAGE is here!</p>
</div>
)
}

export default Home

จากในตัวอย่างหาก HelloComponent เรียกใช้ package react-dates และbranch-sdk อยู่ component เดียว เราก็ควรแยก component นี้ให้เป็น dynamic import เพื่อไม่ให้ package react-dates และbranch-sdk ถูก build ไปไว้ที่ common chunk ครับ

ผลลัพธ์ของ step2 => package react-date, branch-sdk รวมไปถึง momentjs ที่มากับ react-date ถูกแยกออกไปจาก common chunk เป็นที่เรียบร้อย (แยกไปอยู่ที่กล่องสีม่วงๆ 3 อันครับ)

step 3: Upgrade Firebase/Database

step นี้ค่อนข้างง่ายครับคือ upgrade firebase version จาก 8=>9 เนื่องจาก version 9 นี้ จะเป็นการ import แบบ Modular โดยจะโหลดเฉพาะ module ที่ใช้เท่านั้น
*อาจต้องมีการ refactor code นิดหน่อยครับ

เรามาดูผลลัพธ์ First Load File size กันครับ

  • ก่อน Optimize: first load file size แต่ละหน้าขนาดประมาณ 540 kB
  • หลัง Optimize: first load file size แต่ละหน้าขนาดประมาณ 300 kB

แถมให้อีกข้อ

→ Largest Contentful Paint ←
ปัญหาข้อนี้คือ element ที่แสดงบนหน้าเว็บที่ใหญ่ที่สุดแสดงช้าครับ ข้อนี้ส่วนใหญ่จะเกิดจากรูปภาพที่ใหญ่ที่สุดใน page นั้น render ช้าครับ

optimize image

หากเราใช้ Next.js version 10 ขึ้นไป สามารถใช้ next/image มาช่วย optimize image ของเราให้มีประสิทธิภาพดีขึ้น และอย่าลืมใส่ props priority ลงไปเพื่อทำการ preload รูปที่ต้องการแสดงเป็นอันดับแรกด้วยครับ

หากใครใช้ next แล้ว แนะนำว่าต้องใช้ next/image ครับ แล้วปัญหา score Largest Contentful Paint หรือ Cumulative Layout Shift ของรูปภาพ จะหายไปอย่างแน่นอนครับ

<img> ที่ถูก render จาก next/image

จริงๆแล้วการทำ Optimize web นั้นมีอีกหลายข้อที่ควรทำ แต่เนื่องจากในบทความนี้ ผมอยากแชร์สิ่งที่พึ่ง research และทำ POC มา แชร์ให้กับคนที่สนใจ เผื่อทำไปประยุกต์ใช้กับ web ตัวเองครับ หวังว่าคงเป็นประโยชน์กับหลายๆคนครับ

--

--

Uoo Worapon
fastworkco

Programmer ขี้ลืม จนต้องจดบันทึกไว้ใน Medium