Lazy as application

Tâm sự một chút, sau kì nghỉ Tết vừa rồi mình có review lại hướng đi trong thời gian tới, nghĩ thì chưa ra mà vô tình sa vào trạng thái Chênh vênh nên có stress tẹo, định cho bản thân thời gian F5 refresh mà sinh ra lười biếng hồi nào chẳng hay. Cũng may có buổi nói chuyện với một người anh nên vấn đề cũng đã được giải quyết, cái lười cũng sợ quá chạy mất tiêu

Cũng với cái chủ đề lười này, với con người chúng ta ảnh hưởng là vậy, cơ mà với application thì tốt lắm nhé ! Để mình kể cho mà nghe, chuyện ông Lazy loading và bà Code-splitting : )))

Bắt đầu thoyyyy !

Base

Với mỗi application, trong quá trình phát triển, chúng ta luôn cố gắng chia kiến trúc code nhỏ và chi tiết nhất để có thể dễ dàng xử lý logicmaintain sau này. Song, khi build để deploy lên server, các files này sẽ được Webpack, Rollup hay Browserify đóng gói lại.

Cùng nhau ngó qua cơ chế Webpack thực hiện việc này một chút nhé !

How Webpack bundling works?

Trong quá trình Bundling, Webpack sẽ tạo ra một thứ gọi là dependency graph có dạng:


Root của dependency graph chính là entry point được config trong Webpack (giả sử nó là main.js).

Các modules được import trong main.js sẽ trở thành các node tương ứng của root. Và dĩ nhiên, mỗi module được import trong các node này sẽ trở thành các node con của chúng.

Dependency graph cũng tương tự như Original DOM tree trong HTML.


Nếu chúng ta sử dụng các Web APIs để detect các DOM nodes thì thông qua Dependency graph, Webpack cũng detect các modules được đưa vào output bundle.

Output bundle là một file (hoặc nhiều nếu dùng lazy loading) chứa các code vanila Javascript được compiles từ các modules trong dependency graph.


Muốn xem nội dung file Output bundle trên brouser, bạn chỉ cần mở cửa sổ Console lên, trong Tab Network:

sau đó click vào file bundle.js, sẽ thấy nó ...đáng yêu vô cùng luônnnn =)))

BONUS

Trong tab Network, phía bên cột Waterfall, nếu có đoạn progress màu đỏ, nghĩa là có tài-nguyên-chưa-thực-sự-cần và nên thực hiện lazy load đó ! Có thể mình sẽ chia sẻ thêm về sử dụng các chức năng của Chrome dev tools trong thời gian tới ^^



Để tóm tắt lại, quá trình bundling của Webpack có thể được hình dung theo sơ đồ sau:


Yayyyyy, bây giờ đã hiểu được Webpack làm gì rồi, ta lại nhận ra một vấn đề phát sinh:

Khi application càng nhiều modules, kích cỡ file bundle ban đầu càng to

Khi bundle càng to, thời gian client load càng lâu

Khi thời gian càng lâu, khả năng người dùng rời trang web càng cao.


Đó là chưa kể người dùng còn sử dụng máy tính cấu hình thấp, mobile internet data hay các trường hợp có slow internet connections nữa thì toang đúng không nào :v


Tổ lái sang UX một xíuuu, theo phân tích của Google, 53% người dùng mobile quyết định rời khỏi trang web nếu thời gian phản hồi của nó lớn hơn 3s.

Như vậy, vấn đề bigger bundle = fewer users có thể trực tiếp làm mất potential revenue (doanh thu từ những người khách hàng tiềm năng).

Một ví dụ cụ thể, việc delay 2s sau khi hiển thị kết quả đã khiến Bing mất đi 4.3% potential revenue.


Như chúng ta đã phân tích phía trên, Application từ 1 bundle ban đầu sẽ rất lớn, khi load về sẽ chậm hơn khi bundle càng ngày càng to, và có thể có nhiều phần code mà màn hình đó người dùng ko cần. Điều này cũng làm giảm performance đi sương sương. Lúc này đây, Lazy loadingcode splitting được sinh ra cho đời bớt khổ (J4F) 🥰🥰

Main

Code-Splitting concept

Code splitting is just a process of splitting the app into this lazily loaded chunks.


Ý tưởng là ta sẽ tách file bundle.js ban đầu ra thành 1 file bundle nhỏ hơn và các file chunks nhỏ hơn, chỉ tải những thứ client-thực-sự-cần.

Điều này có thể làm giảm kích thước bundle và các tài nguyên cần thiết tải lên cho lần khởi tạo đầu tiên; Việc tải các component hoặc modules khác chỉ diễn ra khi phía client-thực-sự-yêu-cầu.

Và đó chính là Lazy loading:

Lazy loading

Lazy loading is technique of rendering only-needed or critical user interface items first, then quietly unrolling the non-critical items later.


Notes:

Code splitting is process of splitting app into chunks.

Lazy loading is process of loading chunks.


Pet demo

Observation

Quan sát kĩ các files được load trong tab Network nhé:

Architect

Root App component chứa một button toggle, click vào toggle thì bắt đầu load component Hello. Hết : ))

Explaination

Khi sử dụng lazy loading, bundle ban đầu là một parent chunk, trường hợp mình split route hay render thêm một lazy component (Hello component), nó sẽ load children chunk tương ứng. Lúc này, trên thẻ head sẽ được append thêm file javascript này:

<script src="/static/js/child-chunk.js"></script>

để chỉ dẫn cho thằng browser sẽ đi load children chunk sau khi load xong parent.

Bạn có thể tìm hiểu sâu hơn về code splitting concept của Webpack trên trang chủ của nó nhé.

Notes

Mình gọi đây là một pet demo vì nó chỉ có 2 component, và ban đầu cột Waterfall cũng không thể hiện việc có tài-nguyên-nào-đó bị load thừa cả. Mục đích của mình là để chúng ta có thể hiểu được cách hoạt động và dễ dàng quan sát cơ chế của nó ^^



Khi chưa thử code demo mình đã nghĩ rằng các phương pháp làm tăng performance nói chung cũng như Lazy loadingCode splitting nói riêng, có nhiều ý nghĩa như vậy, chắc code cũng phải cũng đao to búa lớn lắm. Thế nhưng NHẦM TO các bạn à. Không tin thì đọc tiếp phần dưới xem nào : )))

How's ReactJS lazy?

Phần này viết này mình sẽ giới thiệu một vài cách thực hiện lazy loading trong ReactJS nhé, các hotface khác cùng với ReactJS như Angular, VueJS cũng có các features, library tương ứng hỗ trợ , nếu có thắc mắc gì thêm các bạn có thể ping cho mình ^^

Ý tưởng của 3 cách mình giới thiệu xoay quanh một điểm chốt: sử dụng dynamic import().

React.lazy() feature


Đây là feature mới của ReactJS version 16.6:

const LazyComponent = React.lazy(() => import('./LazyComponent'));

const App = () => (
  <div>
      <Suspense fallback={<div>Loading...</div>}>
        <section>
          <LazyComponent />
        </section>
      </Suspense>
  </div>
);
Notes:

Dù đã là một feature khá hay ho của ReactJS nhưng React.lazySuspense chưa hỗ trợ cho việc server-side rendering.

Đó là lý có thêm 2 libraries dưới đây:

react-loadable library (RL)

import Loadable from 'react-loadable';

const LazyComponent = Loadable({
  loader: () => import('./LazyComponent'),
  loading: <div>Loading...</div>,
});

const App = () => (<LazyComponent/>)

Theo thông tin mình hóng được thì react-loadable đã từng được recommended trong một thời gian dài. Song, tính tại thời điểm mình viết bài này (02/2020), điểm trừ duy nhất của RL là vẫn chưa tương thích được với Webpack v4+Babel v7+ 😭😭

Loadable component (LC)

import loadable from '@loadable/component';

const LazyComponent = loadable(() => import('./LazyComponent'))

const App = () => (
  <div>
    <LazyComponent />
    </div>
);

Trong Loadable Component, ta cũng có lazy() method, Suspense component, fallback,... có cú pháp giống y React.lazy() 😄

Trường hợp muốn sử dụng fallback thì chúng ta không nhất thiết dùng phải Suspense:

// Type 1: Dùng Suspense (như Syntax của React.lazy())

// Type 2: Trong loadable() options
const LazyComponent = loadable(
  () => import('./LazyComponent'),
  { fallback: <div>Loading...</div> }
);

// Type 3: Via fallback props
const App = () => (<LazyComponent fallback={<div>Loading...</div>} />);

Ngoài ra, Loadable component còn cho phép ta truyền một dynamic value vào dynamic import():

import loadable from '@loadable/component'

const AsyncPage = loadable(props => import(`./${props.page}`))

const App = () => (
  <div>
    <AsyncPage page="Home" />
    <AsyncPage page="Contact" />
  </div>
)

Bonus:

Ngoài syntax cơ bản như trên, RLLC còn cho phép xử lý khá nhiều case: tùy chỉnh Loading component trong trường hợp slow network conection (hay thậm chí là rớt mạng) 🤣, customize lại modules được render hay thậm chí là xoắn quẩy thêm Preloading nữa... 😄 Ta có thể tìm hiểu chi tiết trên Github của chúng.

Lazy as application

Cá nhân mình thấy rằng, việc application load các modules cũng tương tự như cách chúng ta đối diện với các mục tiêu trong cuộc sống ấy:

Có bao giờ ở một thời điểm nào đó, bạn mong chờ ở bản thân code giỏi, có mindset full-stack...overflow, muốn học một khóa UI - UX, muốn tập chơi organ, muốn mở kênh youtube, viết blog chia sẻ với cộng đồng Developers,... không? Và thế là không biết bắt đầu cái nào trước, lại return undefined; // Chênh vênh . OKR tràn từ quý này sang quý khác mà mục tiêu vẫn còn đó.


Và nếu như application thực hiện lazy loading: Load những modules thực sự cần thiết trước, sau đó load tiếp nếu có request, chúng ta cũng sẽ làm điều tương tự: đánh specify cho các mục tiêu này (có thể phương pháp SMART), sau đó pick ngay module đầu tiên và thực hiện nó. Từ đó bạn sẽ cảm thấy dễ dàng hơn khi đưa ra quyết định ^^

Conclusion

Vậy là chúng ta đã cùng nhau tìm hiểu về Lazy Loading - Code splitting: cách Webpack build files, ý nghĩ files bundle, chunk, pet demo và một vài cách áp dụng cụ thể trong ReactJS rồi 😄😄

Lazy Loading tuyệt vời là vậy, nên nếu như ngày nọ ông dev ngồi cạnh có bảo:

- Mày lười như phần mềm ấy >.<

thì hãy vui vẻ đón nhận, bởi vì đó là một lời khen mà 😉))

Song, mình chỉ muốn lưu ý thêm một điều:

Lazy loading giúp tăng performance nhưng không phải project nào xài nó thì performance cũng cao.


Pet demo nho nhỏ trên giúp chúng ta trực quan hiểu rõ hơn về cơ chế của nó thôi, thực tế mà áp dụng thì quả là trường hợp dùng dao mổ trâu để giết gà đó 🤣🤣 Dùng đúng mới thấy hiệu quả chứ k phải cái gì cũng code splitting nha ^^

Hy vọng rằng bài viết này mang lại giá trị cho các bạn. Cảm ơn các bạn đã đọc bài chia sẻ của mình. Tặng mình 1 upvote để có thêm động lực cho những bài viết sắp tới nhé 😛😛

Đã đọc tới đây rồi, tiện ghé qua Blog của mình chơi một chút rồi về !

Chúc các bạn cuối tuần vui vẻ !

Happy coding !



Reference: CSS Tricks, Medium, VueSchool, Haodev.