Trường hợp hydrat hóa một phần (với Next và Preact)

Tại Spring, chúng tôi chịu trách nhiệm duy trì các trang web truyền thông tin tức khác nhau cho công ty mẹ của chúng tôi, Axel Springer. Một trong những bản phát hành mới nhất của chúng tôi, Welt.de, là trang web truyền thông tin tức nhanh nhất ở Đức; một trong những mục tiêu được tìm kiếm nhiều nhất của chúng tôi là liên tục đạt được hiệu suất tốt nhất có thể và lý do cho việc này rất đơn giản: hiệu suất tốt hơn thường có nghĩa là trải nghiệm người dùng tốt hơn và do đó duy trì người dùng cao hơn.

tl; dr

Cuộn xuống tới Recap Nero để có một bản tóm tắt ngắn với một infographic. Những điểm chính cho điều này một cách ngắn gọn:
  • Hiệu suất là rất quan trọng cho web
  • để đạt được hiệu suất cao, chúng tôi muốn gửi càng ít càng tốt cho khách hàng
  • chúng tôi có thể làm điều này bằng cách chọn các thành phần chúng tôi muốn gửi và hydrat cho khách hàng
  • chúng tôi để phần còn lại của trang tĩnh và có nhiều gốc kết xuất
  • Tất cả điều này hoạt động với một cơ sở mã duy nhất
  • Dưới đây là một bài viết dài về cách chúng tôi thực hiện các bước được ghi chú ở trên. Bạn cũng sẽ tìm thấy một liên kết đến một repo WIP của việc triển khai này tại đây:
  • https://github.com/spring-media/next-super-performance

Hiệu suất trên web

Nếu bạn theo dõi Addy Osmani, bạn đã biết máy khoan, anh ấy viết rất nhiều về nguyên nhân và ảnh hưởng của hiệu suất trên web. Để giúp bạn bắt đầu, tôi có thể giới thiệu bài viết Addy trên trang web về chi phí của JavaScript trong năm 2018. Hai điều rất quan trọng bạn có thể học được từ bài viết này là:

  • Chi phí JavaScript không chỉ là thời gian để tải gói của bạn
  • Thời gian phân tích và thực thi JavaScript của bạn cũng quan trọng như vậy

Tất nhiên có nhiều thứ để thực hiện hơn thế này, bao gồm các chiến lược tải, đường dẫn kết xuất quan trọng, ngân sách hiệu suất, v.v. Tất cả những điều này xoay quanh cách tối ưu hóa bất cứ điều gì bạn kết thúc gửi cho khách hàng của mình. Những gì chúng tôi muốn tập trung vào với hydrat hóa một phần không phải là làm thế nào để tối ưu hóa những gì bạn gửi, mà là bạn gửi bao nhiêu.

Một khía cạnh quan trọng của điều này sẽ là kết xuất phía máy chủ (SSR) bởi vì có rất nhiều thứ chúng ta có thể làm trên máy chủ mà không cần phải thực hiện trên máy khách. Trong thực tế, đây là mấu chốt của bài viết này; Bất cứ điều gì có thể được thực hiện trên máy chủ nên được thực hiện trên máy chủ nhưng máy khách chỉ nên được gửi bất cứ điều gì cần được thực hiện ở phía máy khách. Ngoài ra, bạn vẫn có thể áp dụng mọi thứ bạn biết về hiệu suất web nhưng bạn sẽ có ít yếu tố hơn để quản lý, điều này sẽ được giải thích sâu hơn về bài viết này.

SSR và hydrat hóa

Để thực hiện mục tiêu xây dựng trang web biểu diễn, chúng tôi sẽ sử dụng phiên bản sửa đổi của Next. Next đi kèm với rất nhiều tính năng nâng cao hiệu suất được tích hợp, quan trọng nhất là Next sẽ kết xuất phía máy chủ (SSR). Điều này có nghĩa là Next sẽ lấy ứng dụng của bạn, được viết bằng React và theo thứ tự này:

  1. Kết xuất nó dưới dạng chuỗi HTML trên máy chủ
  2. Gửi chuỗi HTML được hiển thị cho người dùng của bạn dưới dạng mã nguồn
  3. Gửi mã React của bạn dưới dạng JavaScript cho người dùng của bạn
  4. Và cuối cùng, sau đó, hãy hydrat hóa HTML của bạn bằng mã React của bạn

Trong trường hợp này, bạn có thể sử dụng mã React trên HTML của mình và sau đó nói với React một cái gì đó như thế này:

Xin chào React, đây là một số HTML khớp chính xác với những gì bạn sẽ kết xuất nếu tôi bảo bạn kết xuất trong một nút DOM trống, vui lòng không kết xuất lại mọi thứ, thay vào đó, vui lòng chỉ sử dụng HTML như thể bạn đã kết xuất nó và tiếp tục ngày của bạn

Phản ứng sẽ trả lời

Ok, tôi chỉ nhìn vào HTML của bạn và nó dường như khớp chính xác với những gì tôi sẽ đưa ra. Nó tuyệt thật. Tôi chỉ cần đính kèm một số trình xử lý sự kiện vào DOM của bạn và trang của bạn hiện hoạt động như một ứng dụng trang duy nhất giống như tôi đã tự mình làm tất cả.

Lợi ích của việc tải trang web theo cách này rất đơn giản: Người dùng của bạn sẽ thấy một trang được hiển thị đầy đủ khi họ tải trang web của bạn (thay vì một trang trống) và sau đó trang web sẽ tương tác. Trang web của bạn cũng sẽ được hưởng lợi từ một cải tiến hiệu suất lớn vì trình duyệt không cần thực hiện bất kỳ thao tác lặp lại nào (hiển thị lại trang của bạn bằng React).

Quá nhiều chi phí

Cách tiếp cận này thật tuyệt vời khi bạn muốn tạo các ứng dụng web hay nói cách khác là các trang web cần được kiểm soát hoàn toàn bởi JavaScript và cũng có thể tương tác bất cứ nơi nào bạn nhấp vào. Ví dụ về phương pháp này trong sản xuất bao gồm các trang web như Facebook, Twitter và ứng dụng email khách dựa trên web.

Nhưng hầu hết các trang web không giống như thế này, hầu hết các trang web đều thuộc dạng tĩnh và cũng chứa một số yếu tố tương tác.

Bây giờ bạn cuối cùng đã gửi toàn bộ mã ứng dụng của mình cho người dùng của bạn, bao gồm các thành phần React cho mỗi dòng tiêu đề hoặc đoạn văn bản ở bất cứ đâu trên trang của bạn. Kết quả là một gói lớn không cần thiết cần phải được tải, phân tích cú pháp và thực thi. Điều này dẫn đến hiệu suất tối ưu, trang của bạn sẽ chậm (er) đặc biệt đối với người dùng di động và không có lý do chính đáng!

Và đó là hút.

Vậy chúng ta có thể làm gì? Vâng, có rất nhiều chiến lược và sản phẩm ra khỏi đó. Một trong những ứng dụng nổi bật nhất là Gatsby, một trình tạo trang tĩnh (I chắc chắn bạn đã nghe về nó bây giờ) tập trung rất nhiều vào tối ưu hóa hiệu suất. Vấn đề với Gatsby là nó phải tạo ra tất cả các trang và trang con của bạn vào thời gian biên dịch không thực sự hoạt động khi bạn có các trang web được liên kết với một CMS cập nhật hàng ngày và lưu trữ hàng triệu bài viết chính xác là những gì chúng tôi cần cho chúng tôi các trang web truyền thông tin tức. Đây là lý do tại sao chúng tôi đang sử dụng Next cũng như sửa đổi nó cho phù hợp với nhu cầu của chúng tôi.

Nhập hydrat hóa một phần

Để giải quyết các vấn đề nêu trên, chúng tôi đã đưa ra một cái gì đó mà chúng tôi muốn gọi là hydrat hóa một phần.

Nếu bạn đã xem xét chủ đề này, có lẽ bạn sẽ bắt gặp các thuật ngữ như hydrat hóa tiến bộ hoặc hydrat hóa lười biếng. Tất cả đều có nghĩa là cùng một thứ cụ thể nhưng tất cả chúng đều có liên quan với nhau và thuộc cùng một khối cầu.

Ý tưởng cơ bản đằng sau phiên bản hydrat hóa một phần của chúng tôi là: Thay vì thực hiện SSR và sau đó gửi toàn bộ ứng dụng của bạn đến máy khách của bạn, chỉ một phần JavaScript của ứng dụng của bạn sẽ được gửi đến máy khách để hydrat hóa các phần của trang web yêu cầu JavaScript hoạt động. . Nếu bạn tạo một trang web bằng phương pháp như vậy, bạn sẽ có nhiều React ‘ứng dụng nhỏ với nhiều gốc kết xuất trên trang web tĩnh khác của bạn.

Làm mọi thứ theo cách này sẽ giúp trang web của bạn tăng hiệu suất lớn vì những gì bạn kết thúc vận chuyển là HTML đơn giản, CSS và lượng JavaScript ít nhất cần thiết cho trang của bạn. Một điều cần lưu ý, khi đo hiệu suất, bạn không chỉ phải tính đến thời gian tải mà còn phân tích cú pháp và thời gian thực hiện.

Bạn cũng có thể chọn thực hiện các chiến lược hiệu suất phù hợp trên tất cả những điều này, như phân tách mã, chăm sóc cho khởi động chậm TCP và đường dẫn kết xuất quan trọng của bạn, v.v.

Thực hiện của chúng tôi

Triển khai của chúng tôi bao gồm 2 gói:

  • Thư viện Preact để hydrat hóa một phần được gọi là pool-attkeeper-preact
  • Plugin Next.js được gọi là siêu hiệu suất tiếp theo

Thư viện thứ hai chỉ là một plugin cho Next.js sử dụng pool-attunt-preact, vì vậy hãy để tập trung vào pool-attkeeper-preact. Tôi có thể viết một bài tiếp theo về hiệu suất siêu tiếp theo ở một giai đoạn nào đó trong tương lai.

tiếp viên-preact

Bố cục với một tiêu đề, cơ thể, thanh bên và 2 yếu tố phản ứng

Hình dung bố cục này và để giả vờ các hộp màu xám là các yếu tố có thể hoàn toàn tĩnh và bạn muốn các màu tím có thể tương tác. Chẳng hạn, cái lớn hơn có thể là một nguồn cấp dữ liệu Twitter và một cái nhỏ hơn là một công cụ bỏ phiếu nhỏ. Vì vậy, chúng tôi cần áp dụng JavaScript cho các yếu tố này để làm cho chúng tương tác và chúng tôi muốn để phần còn lại là các yếu tố tĩnh.

Một ví dụ triển khai cho việc này bằng cách sử dụng pool-attunt-preact có thể trông như thế này:

Các dòng 3, 8 là tất cả các thành phần chúng tôi muốn hiển thị, các thành phần này sẽ được hiển thị trên máy chủ và sau đó được gửi dưới dạng HTML và CSS đến máy khách mà không có bất kỳ JavaScript nào.

Dòng 10 & 11 là nơi chúng tôi đánh dấu các thành phần TwitterFeed và Poll để hydrat hóa và nhận lại một thành phần mới. Dòng 18 & 19 là nơi chúng tôi sử dụng chúng.

Dòng 22 là vô cùng quan trọng. Đây là thành phần đưa dữ liệu hydrat hóa (đạo cụ và tên thành phần) vào trang.

Nhưng hãy để tôi giải thích. Khi chúng tôi thực hiện hydrat hóa bình thường với phản ứng, mã của bạn trông giống như thế này:

Có 2 vấn đề chúng ta cần giải quyết ở đây khi thực hiện hydrat hóa một phần

  1. ReactDOM.hydrate hoạt động trên một nút gốc trong DOM, nút mà nó sử dụng làm điểm bắt đầu cho quá trình hydrat hóa. Nút gốc đó phải chứa cây DOM được máy chủ kết xuất phù hợp với các thành phần và trạng thái ứng dụng của bạn. Lưu ý: Bạn cần đặt tên rõ ràng cho một nút DOM để hoạt động như một nút gốc. Trong ví dụ này, điều này rất đơn giản, bạn có thể cung cấp cho nút đó một id, sử dụng document.getEuitybyId và sau đó ném nút đó vào ReactDOM.hydrate và bạn đã hoàn thành!
    Mặt khác, hydrat hóa một phần có nghĩa là bạn sẽ có nhiều thành phần DOM trên trang tĩnh mà bạn cần hydrat hóa. Bạn sẽ muốn đặt tên rõ ràng cho tất cả những gì sẽ là công việc tẻ nhạt cho nhà phát triển.
  2. Điều gì sẽ xảy ra nếu HydrateTwitterFeed hoặc HydratedPoll cần các đạo cụ cần truyền lại cho họ? Nói, một cái gì đó như . Nếu chúng tôi muốn chạy ReactDOM.hydrate (, rootEuityOnThePage) chúng tôi sẽ lấy luke_schmuke từ đâu? Làm thế nào một trang tĩnh biết về điều này? Chúng tôi cần bằng cách nào đó lưu trữ và gửi chúng cho khách hàng.

Giải pháp

Cách chúng tôi giải quyết vấn đề này có thể được hiểu từ việc thực hiện withHydration:

Chúng ta hãy xem xét kỹ hơn về điều này: withHydration hoạt động bằng cách sử dụng kỹ thuật thành phần bậc cao hơn, thành phần bậc cao hơn trả về thành phần ban đầu cùng với các đạo cụ chưa được thay đổi ban đầu nhưng nó cũng đặt trước nó bằng thẻ