ทำ Code Splitting ใน React อย่างง่ายๆ ด้วย react-loadable

Tananan Tangthanachaikul
TakeMeTour Engineering
4 min readJul 9, 2018

ในบล็อกที่แล้วผมได้พูดเรื่องของการ optimize เพื่อเพิ่มความเร็วในการโหลดหน้าเว็บ ซึ่งจะเล่าถึงเทคนิคต่างๆ ที่ TakeMeTour ได้นำมาใช้เพื่อทำให้เว็บไซต์โหลดเร็วขึ้น

หาอ่านได้จากด้านล่างนี้

มี section นึงที่ผมได้พูดถึงเรื่องของการทำ Code Splitting เอาไว้ แต่อธิบายไว้แค่ว่าใน Next.js เองมีการทำ Route-based code splitting ไว้ให้แล้ว และถ้าอยากจะทำ Component-based ก็ต้องทำเองโดยใช้ dynamic import

ในบล็อกวันนี้จะมาเล่าเรื่อง Code Splitting แบบละเอียดมากขึ้น พร้อมทั้งแนะนำ tool ที่จะช่วยให้เราทำ Code Splitting ได้ง่ายดายขึ้นแบบมหาศาลกันครับ

Motivation

ปกติเวลาเราขึ้น project SPA ต่างๆ ไม่ว่าจะเป็น React, Vue โดยส่วนมากเวลาที่โค้ด build สำเร็จจะได้ไฟล์ผลลัพธ์ออกมาหนึ่งไฟล์ ซึ่งทั้งแอพจะอยู่ในไฟล์นั้นหมดเลย ทำให้พอแอพเริ่มงอกขึ้น เช่นมีหน้าใหม่ๆ เพิ่มขึ้น มี component ใหม่ๆ ขึ้นมา ไฟล์ build ก็จะบวมขึ้นเรื่อยๆ

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

ดังนั้นก็คงจะดีกว่า ถ้าเราสามารถทำให้การโหลดไฟล์ JavaScript code นั้น “โหลดของเท่าที่จำเป็น” มาใช้ก็พอ แล้วไปโหลดไฟล์เพิ่มเติมที่เหลือเมื่อครั้งที่จะถูกใช้เท่านั้น ก็น่าจะดีกว่า

จึงเป็นที่มาของการทำ Code Splitting

ซ้าย: ก่อนทำ / ขวา: หลังทำ

ถ้าดูจากรูปด้านบน ให้นึกภาพว่ากรอบสีเข้มนั้นคือไฟล์ build ที่ออกมา ซึ่งด้านซ้ายก็เหมือนกับว่าพอเรา build เสร็จจะได้ออกมาไฟล์นึงขนาดใหญ่ แต่ด้านขวาก็จะได้ไฟล์มาทั้งหมด 6 ไฟล์ ซึ่งขนาดเล็กใหญ่จะต่างกัน แต่ถ้าลองคิดดีๆ หากใน 6 ก้อนนั้นมีแค่ 2 ก้อนที่จะถูกใช้ตลอดในครั้งแรก แต่อีก 4 ก้อนจะไปใช้ทีหลัง ก็เหมือนเราลดขนาดของโค้ดลงไปได้เยอะเลยเมื่อเทียบกับก้อนซ้ายก้อนใหญ่ก้อนเดียว

ฟังดูอาจจะแบบ โอ้โห โคตรดี…แต่ทำยังไงดี

ไม่ต้องห่วงครับ ในทุกวันนี้ webpack 4 เองก็สามารถทำสิ่งนี้ให้ได้แบบไม่ต้อง config อะไรเลย และสำหรับชาว React เองก็มี library ชื่อ react-loadable ที่ช่วยทำสิ่งนี้ให้ง่ายขึ้นไปอีกเหมือนกัน ซึ่งจะอธิบายต่อในหัวข้อถัดไปครับ

Route-based VS Component-based

ซ้าย: Route-based / ขวา: Component-based

สองสิ่งนี้ถ้าให้พูดถึงความเหมือน มันก็คือการทำ Code splitting ทั้งคู่ เพียงแต่ทำในมุมมองต่างกัน

Route-based จะมองในมุมการแบ่งตาม route โดยจะแยกโค้ดตามแต่ละหน้า เช่นหน้านี้มีการใช้ component A, B, C ก็จะ bundle component A, B, C ไว้ด้วยกัน ซึ่งเป็นท่าที่นิยมทำกันมาก เพราะเข้าใจง่าย และหลายๆ tool เช่น Next.js ก็ทำสิ่งนี้ให้เองแบบอัตโนมัติแล้วด้วย

ส่วน Component-based นั้น จะแบ่งตาม component ครับ เช่น ในเคสที่แอพมีหลายแท็บ แท็บที่ยังไม่ได้เห็น ณ ตอนนี้ ก็ควรจะยังไม่โหลด ก็ควรจะไปโหลดหลังจากที่เข้ามาเห็นและใช้งานแท็บนั้นแล้ว

Introducing react-loadable

ปกติการทำ code splitting นั้น สามารถใช้ syntax dynamic import เพื่อทำ code splitting ได้เลย

เช่น ถ้าจะเรียกใช้ฟังก์ชัน range ของ lodash เดิมเราก็จะทำแบบนี้

const _ = require('lodash');// orimport _ from 'lodash';_.range(0, 10);

แต่เมื่อใช้ dynamic import เราจะทำแบบนี้แทน

import('lodash').then(_ => _.range(0, 10));

ถ้าจะทำ code splitting กับ React component ก็จะต้องทำประมาณนี้ เนื่องจากตัว import() จะ return promise ออกมา

ก็คือให้มัน render ตัว loading สักอย่างไปก่อน แล้วใช้ dynamic import ในการโหลด component มา และ setState ให้มัน โดยใช้ state ของ React ช่วย ในที่สุดมันก็จะเอา component ที่โหลดเสร็จมาใช้งาน

ซึ่งถ้าทำทุกที่…ก็ไม่ต้องพูดกัน ตายห่านเหนื่อยโคตร

จึงมีคนทำ library ชื่อ react-loadable ขึ้นมา ช่วยให้เราทำสิ่งนี้ได้ง่ายขึ้นมากกกกกกกกกกกกก

แทนที่จะทำแบบด้านบน เราทำแบบนี้แทน

ซึ่งง่ายดายมาก! เหมือนท้ายสุดจังหวะหยิบไปใช้ ก็เหมือนกันการหยิบ React component ไปใช้เลย

Route-based Code Splitting is easier with react-loadable + react-router 4

ซึ่งหากคุณใช้ react-router 4 อยู่ ตัว react-router สามารถใช้งานคู่กับ react-loadable เพื่อทำ Route-based code splitting ได้เลยแบบง่ายดายมากๆ

ยกตัวอย่าง สมมติเดิมๆ เราประกาศ route ไว้แบบนี้

หากอยากทำ code splitting ตาม route เราก็เพียงแค่ให้ตัว component ที่ render route นั้นๆ มาให้ react-loadable จัดการการโหลดแทน

เพียงแค่นี้ก็จะได้ Route-based code splitting แล้ว! และจริงๆ ตัว react-loadable ก็มี option มากมายให้ปรับแต่งได้ เช่นให้ delay การโหลด / ถ้าเกิด error โหลด component ไม่ได้จะให้ทำอะไร รวมถึงการโหลด component หลายๆ ตัวพร้อมกันก็ทำได้เหมือนกัน

ลดได้ขนาดไหน

หลังจากลองทำ Route-based code splitting จากเดิมที่ผม import ตัว component มาตรงๆ ก็เปลี่ยนเป็นใช้ react-loadable wrap มันขึ้นมา ผลออกมาน่าพอใจทีเดียว

Before
After
Before: ไฟล์เดียว แต่ใหญ่มาก
After: ได้หลายๆ ไฟล์ แต่ขนาดแต่ละชิ้นจะเล็ก

ถ้าดูแล้ว แม้ว่าหลังทำแล้วจะต้องมีการโหลด JavaScript หลายๆ ไฟล์ก็จริง แต่ขนาดโดยรวมก็จะเล็กลงในการโหลดครั้งแรก จาก 566KB (Gzip) เหลือ 427.6KB (Gzip) โดยประมาณ ซึ่งแม้มันจะเล็กน้อย แต่ถ้าลองดูตัวเลขไม่ unzip จะพบว่าลดลงไปเยอะอยู่ จาก 2MB เป็นประมาณ 1.5MB แต่ก็มีโอกาสที่จะลดขนาดได้อีก เช่น การทำ CommonChunk ที่จะดึงตัว module ที่มีการใช้ในหลายๆ ที่แยกออกมาเป็นอีกไฟล์นึง

Extra Tip

โดยปกติแล้ว react-loadable มันจะ split code ออกมาได้ชื่อไฟล์เป็น 1.[randomHash].js / 2.[randomHash].js แต่เราสามารถบอกใบ้ได้ว่าสำหรับ chunk code ส่วนนี้จะให้ชื่ออะไร เพื่อให้เรารู้ว่าการทำ code splitting แยกออกมาได้จริงๆ ไหม และมีขนาดเท่าไหร่ โดยให้ใส่ comment นำหน้า path ที่จะ import ใน import() ได้เลย แบบนี้

import(/* webpackChunkName: "user.page"*/'./Pages/User')

เท่านี้ก็จะเหมือนกับการให้ชื่อกับตัว chunk ไปเลย

ช่วงโฆษณา

TakeMeTour สตาร์ทอัพการท่องเที่ยวสัญชาติไทยแท้ๆ กำลังเปิดรับสมัคร Developer เพิ่มเติมอยู่ ในตำแหน่งต่อไปนี้

  • Senior/Junior JavaScript Engineer (Front-end/Back-end/Full-stack)
  • UI/UX Designer

สำหรับ JavaScript Engineer นั้น Stack ที่เราใช้คร่าวๆ เป็นดังนี้

  • Front-End: React/Redux/Next.js/Expo
  • Back-End: Node.js/Express (with micro-service architecture)/Mongoose/Elastic Search/Redis
  • และอื่นๆ อีกมากมาย รอคอยให้มาได้เล่นกันเต็มไปหมด เช่น Kubernetes, Data Studio

สำหรับการทำงานและสวัสดิการของเรานั้นประกอบด้วย

  • เวลาเข้าออก Flexible เข้าออกกี่โมงก็ได้ เราเน้นที่ผลงาน ไม่เน้นที่เวลาในการทำงาน
  • การแต่งตัวสบายๆ ใส่ชุดอะไรมาทำงานก็ได้ ขอแค่ให้เหมาะสม
  • ออฟฟิศเดินทางสะดวก ติด BTS และ Airport Rail Link สถานีพญาไท
  • วันหยุด 30 วันต่อปี
  • มี Google Home Mini เหงาหงอยรอคนมาคุยด้วย (ได้เหรอ)
  • รวมถึงยังมีบอร์ดเกมฝุ่นเกาะรอคนมาเล่นด้วยอยู่
  • มีเบียร์ฟรี!!!!!!!!

สำหรับผู้ที่สนใจสมัครตำแหน่ง JavaScript Engineer ให้ทำการ fork repo ด้านล่างไปทำ แล้วส่งอีเมล repo ที่ทำ และ resume เข้ามาที่ WantToWork@takemetour.com ได้เลยครับ

ส่วนตำแหน่งอื่นๆ ส่งอีเมลแนบ CV, Resume เข้ามาได้ที่ WantToWork@takemetour.com ได้เลยครับ

--

--

Tananan Tangthanachaikul
TakeMeTour Engineering

Senior Full-Stack Developer@TakeMeTour - A man who passionately craft software