Babel Coder

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

beginner

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

ในยุคที่ 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) ของเพจ เพื่อบอกว่าตามสถิติแล้วผู้ใช้ไปเพจถัดไปจำนวนเท่าไร

{
  "": {
    "/blog": 15,
    "/login": 2,
    "/courses": 32
  },
  "/blog": {
    "/login": 2,
    "/courses": 32,
    "/create-react-app": 12,
    "/css-in-js": 1,
    "/graphql": 33
  },
  ...
  ...
}

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

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

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

{
  '/': [
    { chunk: 'articles.js', probability: 0.7 },
    { chunk: 'login.js', probability: 0.3 }
  ],
  '/articles': [
    { chunk: 'login.js', probability: 0.1 },
    { chunk: 'courses.js', probability: 0.9 }
  ],
  ...
}

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

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

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

new GuessPlugin({
  GA: '123456789',
  period: {
    startDate: new Date('2016-1-1'),
    endDate: new Date('2018-2-24')
  },
  routeFormatter(r) {
    return r.replace(/^\/app/, '');
  }
})

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

ปุกาศ ปุกาศ ตอนนี้ทางเพจ 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/


แสดงความคิดเห็นของคุณ


No any discussions