โหลดเพจถัดไปเร็วขึ้นด้วย Guess.js และ React

Nuttavut Thongjor

หลังจากหลอกล่อให้ผู้คนเข้าใช้งานเว็บไซต์เราได้แล้ว หากเนื้อหามันใช่หรือโปรโมชันมันโดน ย่อมมีแนวโน้มที่ผู้ใช้งานจะท่องไปต่อที่หน้าเพจอื่น

ในยุคที่ Webpack และเครื่องมือ bundler เฟื่องฟู เราคงไม่แพ็ค JavaScript และ assets ทั้งหลายของเราเป็นก้อนเดียว โหลดหน้าโฮมเพจทีเดียว ตู้มมมม ได้ assets ที่จะใช้ทั้งเว็บไปทั้งก้อน แต่เรามักจะซอยย่อย ซอยยิก ๆ ส่วนของโค้ดเช่น JavaScript เป็นก้อน ๆ (chunk) หน้าเพจไหนต้องการใช้อะไรก็โหลดเฉพาะส่วนที่ต้องการ สิ่งนี้คือหลักการของ Code Spliting และ Lazy-Loaded Routes

routing-tree

สมมติว่าเว็บเรามีหน้าโฮมเพจ หน้า a b และ c เราทราบแล้วว่าเมื่อผู้ใช้งานอยู่หน้าโฮมเพจ (/) ก็มีแนวโน้มที่จะเปลี่ยนหน้าไปที่ a b หรือ c แต่การที่ผู้ใช้งานจะเปลี่ยนไปหน้าอื่นมันมีค่าใช้จ่ายอยู่คือการรอโหลดหน้าเพจถัดไป ทำอย่างไรหละจะให้หน้าเพจถัดไปโหลดได้เร็วขึ้น?

ความจริงที่ว่า a b และ c ผู้ใช้งานสามารถคลิกจากลิงก์ของหน้าโฮมเพจเพื่อเข้าถึงหน้าเหล่านี้ได้หมด หลังจากที่โฮมเพจโหลดเสร็จเราจึงต้องทำการ prefetch คือแอบโหลดหน้า a b และ c มาด้วย ด้วยวิธีการนี้พอผู้ใช้งานจิ้มลิงก์ของเพจไหนก็ตาม เขาเหล่านั้นจึงไม่ต้องเสียเวลารอโหลดหน้าเพจใหม่ เพราะเพจเหล่านี้ได้พร้อมเสริฟอยู่ในแคชเป็นที่เรียบร้อยแล้วนั่นเอง

ทว่าถ้าลิงก์ที่ไปต่อได้จากหน้าโฮมเพจมีซัก 100 ลิงก์หละจะเกิดอะไรขึ้น? คือถ้าที่บ้านเป็นโรงงานผลิต 4G แบบ True ก็คงไม่มีใครว่า เมื่อเป็นเช่นนี้การจะมา prefetch หน้าเพจถัด ๆ ไปทุกเพจจึงไม่ใช่ทางออกที่ดี

Google I/O 2018 มีสิ่งหนึ่งที่เป็นทางออกสำหรับปัญหานี้ นั่นคือ Guess.js

พยากรณ์เพจถัดไปด้วย Google Analytics

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

ย้อนกลับมาที่รูปก่อนหน้า หากรายงานจาก Google Analytics แสดงว่าเมื่อผู้ใช้งานอยู่ที่โฮมเพจ มักจะไปต่อที่เพจ a ด้วยสัดส่วน 0.65 (65%) เพจ b ที่ 0.2 (20%) และ c ที่ 0.15 (15%) เราอาจตัดสินใจได้ว่าควรทำ prefetch เฉพาะเพจ a เมื่อเพจปัจจุบันคือโฮมเพจ

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

JavaScript
1{
2 "": {
3 "/blog": 15,
4 "/login": 2,
5 "/courses": 32
6 },
7 "/blog": {
8 "/login": 2,
9 "/courses": 32,
10 "/create-react-app": 12,
11 "/css-in-js": 1,
12 "/graphql": 33
13 },
14 ...
15 ...
16}

สัดส่วนการเข้าถึงเพจตาม chunk

ตามที่เราคุยกันไปแต่ต้น สมัยนี้ไม่มีใครเข้าเพจเดียวโหลด JavaScript มาตู้มเดียวทั้งเว็บแล้วครับ เราทำ Code Spliting เพื่อแบ่งโค้ดออกเป็น chunk ย่อย ๆ โดยส่วนใหญ่เรานิยมใช้เทคนิค Route-based code-splitting หรือการแบ่ง chunk ตามเส้นทางการเข้าถึง เช่น เมื่อเข้าสู่เพจ /articles ก็จะทำการโหลด chunk ของ articles และเมื่อเข้าสู่เพจ /users จึงทำการหยิบชิ้นส่วนของ users อีกทีนึง

Guess.js ช่วยให้การหาสัดส่วนการเข้าถึงตาม chunk เป็นเรื่องง่ายขึ้น ด้วยการสร้างโมเดล Markov Chain อิงตามน้ำหนักว่าเมื่อผู้ใช้เข้าสู่เพจนี้แล้วมักจะโหลด chunk ไหนต่อเป็นสัดส่วนเท่าไหร่ อย่าลืมนะครับเรามักแบ่ง chunk ตามเส้นทางการเข้าถึงเพจ ถ้าเข้าเพจไหนเยอะสัดส่วนของ chunk เพจนั้นก็จะสูงตาม

JavaScript
1{
2 '/': [
3 { chunk: 'articles.js', probability: 0.7 },
4 { chunk: 'login.js', probability: 0.3 }
5 ],
6 '/articles': [
7 { chunk: 'login.js', probability: 0.1 },
8 { chunk: 'courses.js', probability: 0.9 }
9 ],
10 ...
11}

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

Prefetch ด้วยค่าสัดส่วนทำงานอย่างไร

เมื่อเราเข้าถึงหน้าเพจหนึ่ง ๆ ก็ถึงเวลาที่ต้องตัดสินใจว่าเพจถัดไปที่ควรโหลดมาล่วงหน้าคือ chunk อะไร หลังจากพิจารณาตามค่าสัดส่วนแล้วพบว่า chunk ไหนจะถูกโหลด เราเพียงทำการเพิ่ม <link rel='prefetch' href='<CHUNK>.js'> เข้าไปในส่วน head ของเพจปัจจุบัน เพียงเท่านี้เพจถัดไปก็จะถูกโหลดและทำการแคชพร้อมเสริฟเป็นที่เรียบร้อย

ใช้งาน Guess.js กับ React

ขั้นตอนต่าง ๆ ฟังเหมือนจะง่าย แต่ถ้าจะให้ทำเองคงขอตายแผล็บ หัวข้อนี้เราจึงจะมาใช้ Guess.js เพื่อช่วยพิจารณาการ prefetch chunk ถัดไปกันครับ

ก่อนอื่นเราต้องทำ Code Spliting ตาม route เสียก่อน เนื่องจาก Guess.js จะคาดเดาเพจถัดไปแล้วทำการ prefetch ตาม chunk

JSX
1<Route path="/articles" component={AsyncComponent(() => import('./articles'))} />
2<Route path="/courses" component={AsyncComponent(() => import('./courses'))} />
3<Route path="/login" component={AsyncComponent(() => import('./login'))} />

จากนั้นจึงทำการติดตั้ง GuessPlugin ใช้งานคู่กับ Webpack

JavaScript
1new GuessPlugin({
2 GA: '123456789',
3 period: {
4 startDate: new Date('2016-1-1'),
5 endDate: new Date('2018-2-24'),
6 },
7 routeFormatter(r) {
8 return r.replace(/^\/app/, '')
9 },
10})

สั้น ๆ เลยนะฮะ -- จบ

ปุกาศ ปุกาศ ตอนนี้ทางเพจ Babel Coder มีสอนพัฒนาโมบายแอพพลิเคชันด้วย React Native ด้วยหละ เรียนวันที่ 23 - 24 มิย 2561 ครับ

รายละเอียดเพิ่มเติม จิ้มลิงก์นี้จ้า

React Native Course

เอกสารอ้างอิง

Guess.js. Retrieved May, 11, 2018, from https://github.com/guess-js/guess

Guess.js React demo. Retrieved May, 11, 2018, from https://github.com/mgechev/guess-js-react-demo

Machine Learning-Driven Bundling. The Future of JavaScript Tooling.. Retrieved May, 11, 2018, from https://blog.mgechev.com/2018/03/18/machine-learning-data-driven-bundling-webpack-javascript-markov-chain-angular-react/

สารบัญ

สารบัญ

  • พยากรณ์เพจถัดไปด้วย Google Analytics
  • สัดส่วนการเข้าถึงเพจตาม chunk
  • Prefetch ด้วยค่าสัดส่วนทำงานอย่างไร
  • ใช้งาน Guess.js กับ React
  • เอกสารอ้างอิง