Xin chào, lại là mình đây!
Khoảng một tháng trước, chúng ta đã bàn về việc viết nên một ứng dụng mà khởi nguồn đến từ chiếc app đầy nổi (tai) tiếng của Mark Zuckerberg, Facemash. Chúng mình đã nói về những câu chuyện đằng sau, rồi nói về thuật toán và những gì nằm ẩn bên trong chiếc app đó. Mình rất tự hào khi được biết rằng phần 1 đã được đón nhận một cách nồng nhiệt.
Thế rồi, mình để các bạn lại với một cliffhanger – kết thúc dở dang: chiếc app sẽ không được viết bằng, mình tin là, bất cứ framework nào bạn đã từng biết. Không React, chẳng Angular, cũng từ chối cả Vue. Một tháng mà không update gì cả, mình tin là mọi người đang rất nóng lòng được đón đọc phần tiếp theo, và vài bạn có khi nghĩ rằng mình bỏ rơi các bạn rồi, như cách phim Italian Job kết thúc vậy.
Nhưng, đừng lo, mình đã quay trở lại đây để hoàn thành những gì mình đã khởi đầu. Ở bài blog của BraveBits tháng này, chúng mình sẽ trả lời ba câu hỏi hóc búa:
- Tại sao lại cần một framework mới?
- Framework mới là gì?
- Framework mới hoạt động với app như thế nào?
Vậy, thắt chặt dây an toàn và đi thôi!
Một tiêu đề “láo” và vấn đề với website hiện tại
Ngày 28. Tháng 9. Mình đang ngồi lướt Medium, đọc và học thêm vài thứ hay ho từ các “thiên tài” công nghệ trên đây. Không có gì quá đặc biệt cả, thế rồi, tự dưng mình thấy một cái tiêu đề như thế này:
Đúng vậy, lúc đó mình cảm giác y như bạn bây giờ ấy: “Cái đ-?”. React thì là thư viện được dùng nhiều nhất trong giới lập trình web, và Next.js thì đang ngày càng củng cố vị thế của mình. Thế ai điên đến mức nghĩ rằng ngày tàn của hai ông hoàng ngành web app đang đến gần vậy?
Tuy nhiên, chả nhẽ tò mò thế mà không đọc? Và đọc xong, tất nhiên, mình cũng không hoàn toàn đồng ý với mọi thứ bài viết nói, nhưng mà mình đã có một cái nhìn sâu hơn về những gì thực sự diễn ra dưới tấm màn của một web app, và càng đào sâu, mình càng nhìn ra được những vấn đề tồn đọng của những công cụ ta đang dùng để dựng nên app.
Sự tăng tiến mạnh trong nhu cầu tương tác của người dùng, và kéo theo đó, JavaScript
Web app đang phát triển ngày càng nhanh, nhu cầu cho một chiếc app có tính tương tác và ứng dụng đã tăng lên gấp n lần. Và, website bây giờ không chỉ phải phục vụ trải nghiệm của khách hàng, mà chính chúng ta, những người dựng app, cũng đặt thêm những nhu cầu về việc lưu trữ và diễn giải hành vi người dùng để có thêm những cái nhìn đóng góp cho việc phát triển app. Và, điều này dẫn đến một kiến trúc web ngày càng phức tạp, web mở rộng dẫn đến một vấn đề nan giải: cần dùng quá nhiều JavaScript. Tải và ghép JavaScript vào app là một quá trình chậm chạp, và đó là lý do chính tại sao một app “tương tác cao” của chúng ta lại chạy chậm hơn hẳn một chiếc app chỉ có HTML không.
Thật sự thì, CPU của chúng ta chưa chuẩn bị cho sự phát triển này
Bạn có còn nhớ cái lúc mà Gangnam Style làm tê liệt YouTube trong vài giờ sau khi đạt 1 tỷ lượt xem? Khi đó, cơ chế của YouTube chỉ dành ra một kiểu dữ liệu tối-đa-9-chữ-số để lưu chữ số lượt xem video (ai mà biết được lại có một video vượt qua cột mốc ấy chứ?). Ừ thì, vấn đề với CPU cũng tương tự đấy. CPU cho dù đã chạy nhanh hơn qua từng năm tháng, nhưng sự thật là chúng ta đang đạt đến mức độ tối đa mà nó có thể chạy, đấy là chưa kể CPU cần đến nhiều bộ vi xử lý chạy song song.
Vấn đề là, cấu trúc của JavaScript là đơn luồng, và càng nhiều JS cần có, càng nhiều việc mà một chiếc vi xử lý khốn khổ phải thực hiện. Nhìn ra được vấn đề của việc lượng JS đang tăng tiến nhanh hơn nhiều so với chất lượng của CPU, là một điều quan trọng để hiểu được tính nghiêm trọng của vấn đề.
Một vấn đề tuyến tính
Khi phát triển web app bây giờ, chúng ta đang đặt nhiều quan tâm vào thời gian chạy (runtime) mà không phải là thời gian để tương tác được (TTI – time-to-interactive). App càng phức tạp thì càng mất nhiều thời gian từ quá trình ta tương tác thị giác với app, cho đến xúc giác.
Đó là vì quá trình khởi động của app bắt nguồn từ hai đại lượng: độ phức tạp của app, và dữ liệu khởi tạo của framework. Vậy nên, gọi mối quan hệ này là “y=mx+b” cũng được.
Và vấn đề là, nhìn vào biểu đồ trên ta có thể thấy được, có tối ưu hoá đi bao nhiêu thì tổng dung lượng của web app vẫn sẽ tăng tiến theo độ phức tạp của app: đây là vấn đề tuyến tính. Câu hỏi đặt ra: làm thế nào để “m” giải về 0 và chỉ giữ cost của framework là đại lượng duy nhất tồn tại khi boot app?
Core Functionality, and How a Chess Algorithm Fits Into Play
“Framework hiện tại đang chạy O(n); chúng tôi cần O(1)” – Misko Hevery, creator of Qwik.
Chúng ta cần một cơ chế hoàn toàn mới. Chúng ta cần một framework mà load một đại lượng thời gian không đổi mặc cho app có phức tạp đến mức nào.
Thế thì, các framework hiện tại đang hoạt động như thế nào? Ở một trang web thông thường, chúng ta đầu tiên render HTML từ server và làm nó hiện ra ở client – ở thời điểm này ta chưa thể tương tác gì với app cả. Sau đó, app mới tải và ghép các file JavaScript vào đúng vị trí của nó, lắng nghe và thực hiện yêu cầu từ người dùng, Đây là một quy trình được gọi là ‘hydration’. Và, như ta đã nói, hydration rất “đắt”, vì ngay cả khi app đã được vẽ ra trước mắt chúng ta, ta vẫn chẳng thế tương tác gì vì một lượng thời gian chờ đợi script lâu và khó chịu.
Có nhiều cách đi đường vòng để xử lý hydration, như kiến trúc quần đảo (island architecture) hay tải lười (lazy loading), nhưng mà đường vòng thì không bao giờ là đường đúng cả.
Vậy nên, đó là khi một framework mới mẻ trẻ trung bước vào sân chơi. Xin giới thiệu… Qwik
Phát triển bới đội ngũ Builder.io, với người đứng đầu là CTO Misko Hevery (ông chính là người đã tạo nên AngularJS!), Qwik còn có tuổi đời rất trẻ khi mới được trình làng giữa năm 2022 và còn đang ở bản beta. Qwik tự đánh giá bản thân là “framework của tương lai”, và nhìn những gì Qwik đang phát triển, mình tin rằng đây không phải thùng rỗng kêu to. Điểm đặc biệt của Qwik nằm ở hai tính chất: Resumable và Progressive.
Resumable
Resumable là việc dừng một hành động xử lý ở server và tiếp nối hành động đó ở frontend mà không cần phải chạy lại toàn bộ quá trình booting.
Tưởng tượng ta đang xem một series trên Netflix đi, mỗi lần ta stop lại nghỉ ngơi chút đỉnh, ta chắc chắn sẽ không muốn phải xem lại từ tập 1 phải không nào? Ấy thế mà app của chúng ta đang chạy kha khá tương tự như vậy đó.
Qwik nhận ra rằng hydration là thừa thãi: mỗi lần nhận một hành động của người dùng là một quá trình booting tốn thời gian. Vậy nên, Qwik tạo nên khái niệm Resumability, tính tiếp diễn của một app. Về cơ bản, Qwik bỏ qua bước bootup và nhảy thẳng đến state hiện tại của bạn. Vì thế, người dùng – chúng ta – được quyền quyết định chúng ta sẽ cần những phần nào của app, và bao giờ cần nó, đúng như tên gọi: tiếp diễn.
Progressive
Progressive là khái niệm về việc chỉ tải xuống những tệp script mà người dùng cần khi họ tương tác với các component trong trang, mà không tải xuống toàn bộ ban đầu. Qwik hướng đến việc tách nhỏ code app ra thành nhiều mảnh vụn (chunk) và chỉ lazy load chúng khi cần.
Qwik làm được điều này với một công cụ gọi là Optimizer và một biểu tượng, $. Mỗi khi framework phát hiện một dấu $ được đặt trong code, Optimizer sẽ tách hàm/component chứa $ ra thành hai files, chỉ tải và ghép nó một cách bất đồng bộ mỗi khi người dùng cần tương tác.
Với hai cơ chế trên, Qwik là framework đầu tiên phá vỡ sự lệ thuộc vào hydration, và có thể, đặt lại nền móng cho toàn bộ ngành lập trình web app nói chung. Tất nhiên, mình đang không cố để hạ thấp một framework nào cả, nhưng khi có một công cụ tốt mà cần được dành những sự chú ý và khen ngợi, thì ta cũng cần sẵn sàng làm thế. Và Qwik là một trong số đó, bây giờ.
Qwik x Songmash
Nói dài nói dai lại thành nói dại, túm lại là: đến lúc ghép Songmash và Qwik rồi. Một dự án đến từ niềm vui thuần khiết của lập trình, thì chẳng ngại gì mà thử một thứ gì đó mới lạ nhỉ?
All right, cùng nhảy vào và xem Qwik đem lại những gì nào?
User Flow
App có thể được chia thành hai trang: trang chủ và trang nghệ sĩ. Trang chủ chỉ đơn giản trình bày tất các nghệ sĩ mà người dùng có thể tương tác, và ở trang nghệ sĩ là khi phần hay bắt đầu
Mục tiểu sử nghệ sĩ
Mục “mash” nhạc: phần chính của chiếc app, nơi người dùng có thể chọn một trong hai bài hát và phần backend sẽ tính toán điểm dựa trên thuật toán Elo
Và mục xếp hạng bài hát nghệ sĩ
Diễn giải Code
- Thư mục routings, đơn giản đan rổ vì mình chỉ có hai loại trang thôi mà
- Thư mục services, dùng để kết nối frontend với API đã tạo sẵn từ backend
- Thư mục helpers, là nơi mình viết những file TypeScript xử lý dữ liệu, mà một trong số đó là nơi mình viết nên cơ chế tính toán Elo
- Thư mục components, nơi mọi thứ liên quan đến components thuộc về, tất nhiên rồi
Một ví dụ về cú pháp của Qwik: hãy để ý ba chiếc hooks: useStore, useClientEffect và useWatch. Chiếc đầu tiên, tương đương với useState ở React, quản lý và update state, còn hai chiếc sau chính là useEffect nhưng mà chia nhỏ ra hơn. Điều này là vô cùng hữu ích với cơ chế của Qwik, tách biệt tính năng của client và server side, để dừng và chạy tiếp các hành động một cách nhuần nhuyễn và tuân thủ những gì mà resumability đề ra.
Hãy nhìn đi, điểm Lighthouse cho chiếc app Qwik của mình cao chót vót, mà đấy là mình còn chưa tối ưu hoá hết mức đó!
Endnote
Và đó là hành trình của chúng mình với Songmash. Đi từ một ý tưởng vụn vặt của Mark Zuckerberg, một thuật toán hay ho, một cách lấy dữ liệu không truyền thống, và một chiếc framework sẽ sớm tạo nên tên tuổi, chúng ta có một chiếc app thoả mãn hết cỡ cái niềm vui thuần khiết dành cho code, cũng như là một tinh thần luôn muốn học hỏi thêm của mình. Mình cũng hy vọng, chiếc app này cũng sẽ là động lực để các bạn bắt tay vào làm một thứ gì đó, và tiếp tục truyền đạt tình yêu code này tới những người khác nữa.
Vì, một ý tưởng nhỏ, một chút kiến thức, và nhiều chút tình yêu lập trình, đó là công thức cho sự thành công đó. Hoặc, chí ít là, niềm vui.
Link Github cho bà con như thường lệ. See you again soon!
Cao Xuân An