Tải ứng dụng web tức thì với cấu trúc shell ứng dụng

Addy Osmani
Addy Osmani
Matt Gaunt

Giao diện ứng dụng là HTML, CSS và JavaScript tối thiểu hỗ trợ giao diện người dùng. Giao diện ứng dụng phải:

  • tải nhanh
  • được lưu vào bộ nhớ đệm
  • tự động hiển thị nội dung

Giao diện ứng dụng là bí quyết để có hiệu suất tốt một cách đáng tin cậy. Hãy xem giao diện của ứng dụng giống như gói mã bạn phát hành lên cửa hàng ứng dụng nếu bạn đang xây dựng ứng dụng gốc. Đây là tải cần thiết để khởi đầu, nhưng có thể không phải là toàn bộ câu chuyện. Lớp này giúp giao diện người dùng được cục bộ và tự động lấy nội dung thông qua API.

Tách HTML, JS, CSS và nội dung HTML

Thông tin khái quát

Bài viết Ứng dụng web tiến bộ của Alex Russell mô tả cách một ứng dụng web có thể thay đổi từng bước trong quá trình sử dụng và sự đồng ý của người dùng để cung cấp trải nghiệm giống ứng dụng gốc hoàn chỉnh hơn với khả năng hỗ trợ ngoại tuyến, thông báo đẩy và khả năng thêm vào màn hình chính. Điều này phụ thuộc rất nhiều vào chức năng và lợi ích về hiệu suất của service worker cũng như khả năng lưu vào bộ nhớ đệm. Điều này cho phép bạn tập trung vào tốc độ, cung cấp cho các ứng dụng web tính năng tải tức thì giống như bản cập nhật thường xuyên mà bạn thường thấy trong các ứng dụng gốc.

Để tận dụng tối đa các tính năng này, chúng ta cần có cách tư duy mới về trang web: kiến trúc giao diện ứng dụng.

Hãy cùng tìm hiểu cách định cấu trúc ứng dụng bằng cấu trúc shell ứng dụng tăng cường cho trình chạy dịch vụ. Chúng ta sẽ xem xét cả quá trình hiển thị phía máy khách và phía máy chủ, đồng thời chia sẻ một mẫu toàn diện mà bạn có thể thử ngay hôm nay.

Để nhấn mạnh điểm này, ví dụ dưới đây cho thấy lượt tải đầu tiên của một ứng dụng sử dụng cấu trúc này. Lưu ý rằng thông báo ngắn "Ứng dụng đã sẵn sàng để sử dụng ngoại tuyến" ở cuối màn hình. Nếu sau này có bản cập nhật cho shell, chúng ta có thể thông báo cho người dùng để làm mới phiên bản mới.

Hình ảnh trình chạy dịch vụ chạy trong Công cụ cho nhà phát triển cho shell ứng dụng

Một lần nữa, trình chạy dịch vụ là gì?

Trình chạy dịch vụ là một tập lệnh chạy trong nền, tách biệt với trang web của bạn. Chrome phản hồi các sự kiện, bao gồm cả yêu cầu mạng được thực hiện từ các trang mà dịch vụ phân phát và thông báo đẩy từ máy chủ của bạn. Một trình chạy dịch vụ có thời gian hoạt động ngắn có chủ đích. Trình phân tích cú pháp đánh thức khi nhận được sự kiện và chỉ chạy khi cần xử lý sự kiện đó.

Trình chạy dịch vụ cũng có một bộ API hạn chế khi so sánh với JavaScript trong ngữ cảnh duyệt web thông thường. Đây là tiêu chuẩn đối với trình chạy trên web. Service worker không thể truy cập DOM nhưng có thể truy cập vào những thứ như Cache API và họ có thể thực hiện các yêu cầu mạng bằng cách sử dụng Fetch API. Bạn cũng có thể sử dụng IndexedDB APIpostMessage() để duy trì và thông báo dữ liệu giữa trình chạy dịch vụ và các trang mà trình chạy này kiểm soát. Sự kiện đẩy được gửi từ máy chủ của bạn có thể gọi API thông báo để tăng mức độ tương tác của người dùng.

Trình chạy dịch vụ có thể chặn các yêu cầu mạng được tạo từ một trang (kích hoạt sự kiện tìm nạp trên trình chạy dịch vụ) và trả về phản hồi được truy xuất từ mạng hoặc được truy xuất từ bộ nhớ đệm cục bộ, hoặc thậm chí được tạo theo lập trình. Hiệu quả, đó là một proxy có thể lập trình trong trình duyệt. Điều thú vị là, bất kể phản hồi đến từ đâu, Google vẫn nhìn vào trang web như thể không có sự tham gia của trình chạy dịch vụ.

Để tìm hiểu chi tiết hơn về trình chạy dịch vụ, hãy đọc phần Giới thiệu về Trình chạy dịch vụ.

Lợi ích về hiệu suất

Trình chạy dịch vụ rất hiệu quả khi lưu vào bộ nhớ đệm khi không có kết nối mạng, nhưng cũng mang lại hiệu suất đáng kể dưới dạng tải tức thì cho các lượt truy cập lặp lại vào trang web hoặc ứng dụng web. Bạn có thể lưu shell của ứng dụng vào bộ nhớ đệm để shell hoạt động khi không có mạng và điền nội dung của ứng dụng bằng JavaScript.

Đối với những lượt truy cập nhiều lần, điều này cho phép bạn nhận được các pixel có ý nghĩa trên màn hình mà không cần mạng, ngay cả khi nội dung của bạn cuối cùng bắt nguồn từ đó. Hãy coi tính năng này là hiển thị thanh công cụ và thẻ ngay lập tức, sau đó tải từng bước nội dung còn lại.

Để kiểm thử kiến trúc này trên thiết bị thực, chúng tôi đã chạy mẫu giao diện ứng dụng trên WebPageTest.org và hiển thị kết quả như bên dưới.

Thử nghiệm 1: Thử nghiệm trên cáp với Nexus 5 bằng Chrome Dev

Chế độ xem đầu tiên của ứng dụng phải tìm nạp tất cả tài nguyên từ mạng và chỉ xuất hiện sau 1,2 giây. Nhờ chức năng lưu vào bộ nhớ đệm của trình chạy dịch vụ, lượt truy cập lặp lại của chúng tôi đạt được thời gian hiển thị hữu ích và hoàn tất việc tải xong trong 0,5 giây.

Sơ đồ thời gian hiển thị thử nghiệm trang web về kết nối cáp

Thử nghiệm 2: Thử nghiệm trên 3G với Nexus 5 bằng Chrome Dev

Chúng tôi cũng có thể kiểm tra kết nối 3G chậm hơn một chút. Lần này, trong lần truy cập đầu tiên, bạn sẽ mất 2,5 giây để hiển thị nội dung đầu tiên. Phải mất 7,1 giây để tải đầy đủ trang. Với chức năng lưu vào bộ nhớ đệm của trình chạy dịch vụ, lượt truy cập lặp lại của chúng tôi sẽ hiển thị nội dung quan trọng và tải xong hoàn toàn trong 0,8 giây.

Sơ đồ thời gian hiển thị thử nghiệm trang web về kết nối 3G

Các góc nhìn khác cũng kể một câu chuyện tương tự. So sánh 3 giây cần để đạt được màu hiển thị có ý nghĩa đầu tiên trong giao diện ứng dụng:

Hiển thị tiến trình cho chế độ xem đầu tiên từ Kiểm tra trang web

thành 0,9 giây khi cùng một trang được tải từ bộ nhớ đệm trình chạy dịch vụ của chúng tôi. Người dùng cuối của chúng tôi tiết kiệm được hơn 2 giây thời gian.

Hiển thị tiến trình cho chế độ xem lặp lại từ Kiểm tra trang web

Các ứng dụng của bạn có thể đạt được hiệu suất tương tự và đáng tin cậy khi sử dụng kiến trúc shell ứng dụng.

Trình chạy dịch vụ có đòi hỏi chúng ta phải suy nghĩ lại về cách chúng ta sắp xếp cấu trúc ứng dụng không?

Service worker ngụ ý một số thay đổi tinh vi trong kiến trúc ứng dụng. Thay vì nén tất cả ứng dụng thành chuỗi HTML, bạn có thể làm theo kiểu AJAX. Đây là nơi bạn có giao diện (luôn được lưu vào bộ nhớ đệm và luôn có thể khởi động mà không cần mạng) và nội dung được làm mới thường xuyên và được quản lý riêng.

Việc phân chia này có tác động rất lớn. Trong lần truy cập đầu tiên, bạn có thể hiển thị nội dung trên máy chủ và cài đặt trình chạy dịch vụ trên máy khách. Trong các lượt truy cập tiếp theo, bạn chỉ cần yêu cầu dữ liệu.

Còn tính năng nâng cao tăng dần thì sao?

Mặc dù trình chạy dịch vụ hiện không được hỗ trợ bởi tất cả các trình duyệt, nhưng kiến trúc shell nội dung ứng dụng sử dụng tính năng nâng cao tăng dần để đảm bảo mọi người đều có thể truy cập vào nội dung. Ví dụ: hãy lấy dự án mẫu của chúng tôi.

Bạn có thể thấy phiên bản đầy đủ hiển thị trong Chrome, Firefox Nightly và Safari ở bên dưới. Ở bên trái, bạn có thể thấy phiên bản Safari nơi nội dung được hiển thị trên máy chủ không có trình chạy dịch vụ. Ở bên phải, chúng ta thấy các phiên bản Chrome và Firefox Nightly sử dụng trình chạy dịch vụ.

Hình ảnh Giao diện ứng dụng được tải trong Safari, Chrome và Firefox

Khi nào thì nên sử dụng kiến trúc này?

Kiến trúc giao diện ứng dụng phù hợp nhất đối với các ứng dụng và trang web động. Nếu trang web của bạn có kích thước nhỏ và tĩnh, có thể bạn không cần đến giao diện ứng dụng và có thể chỉ cần lưu toàn bộ trang web vào bộ nhớ đệm trong bước oninstall của trình chạy dịch vụ. Hãy sử dụng phương pháp phù hợp nhất với dự án của bạn. Một số khung JavaScript đã khuyến khích việc phân tách logic ứng dụng khỏi nội dung, giúp mẫu này dễ áp dụng hơn.

Có ứng dụng chính thức nào sử dụng mẫu này chưa?

Kiến trúc giao diện ứng dụng có thể thực hiện chỉ với một vài thay đổi đối với giao diện người dùng tổng thể của ứng dụng và hoạt động tốt cho các trang web có quy mô lớn như Ứng dụng web tiến bộ I/O 2015 của Google và Hộp thư đến của Google.

Hình ảnh Hộp thư đến của Google đang tải. Minh hoạ Inbox bằng trình chạy dịch vụ.

Giao diện ứng dụng ngoại tuyến là một thành công lớn về hiệu suất và cũng được thể hiện rõ trong ứng dụng Wikipedia ngoại tuyếnFlipkart Lite

Ảnh chụp màn hình bản minh hoạ Wikipedia của Jake Archibald.

Giải thích cấu trúc

Trong trải nghiệm tải đầu tiên, mục tiêu của bạn là đưa nội dung có ý nghĩa lên màn hình của người dùng nhanh nhất có thể.

Tải và tải các trang khác lần đầu

Sơ đồ về lượt tải đầu tiên với giao diện ứng dụng

Nhìn chung, kiến trúc giao diện ứng dụng sẽ:

  • Ưu tiên tải ban đầu, nhưng cho phép trình chạy dịch vụ lưu shell ứng dụng vào bộ nhớ đệm để các lượt truy cập lặp lại không yêu cầu tìm nạp lại shell từ mạng.

  • Tải từng phần hoặc tải trong nền mọi tính năng khác. Một lựa chọn phù hợp là sử dụng tính năng đọc qua bộ nhớ đệm cho nội dung động.

  • Ví dụ: sử dụng công cụ trình chạy dịch vụ, chẳng hạn như sw-precache để lưu vào bộ nhớ đệm và cập nhật một trình chạy dịch vụ quản lý nội dung tĩnh của bạn một cách đáng tin cậy. (Tìm hiểu thêm về sw-precache sau.)

Để làm được điều này:

  • Máy chủ sẽ gửi nội dung HTML mà máy khách có thể kết xuất và dùng các tiêu đề hết hạn của bộ nhớ đệm HTTP trong tương lai xa hơn để xét đến những trình duyệt không hỗ trợ trình chạy dịch vụ. Công cụ này sẽ phân phát tên tệp bằng cách sử dụng hàm băm để cho phép cả "tạo phiên bản" và dễ dàng cập nhật sau này trong vòng đời của ứng dụng.

  • (Các) trang sẽ bao gồm kiểu CSS cùng dòng trong thẻ <style> trong tài liệu <head> để cung cấp nhanh nét vẽ đầu tiên của giao diện ứng dụng. Mỗi trang sẽ tải không đồng bộ JavaScript cần thiết cho chế độ xem hiện tại. Do CSS không thể được tải không đồng bộ, chúng ta có thể yêu cầu các kiểu bằng cách sử dụng JavaScript vì nó IS không đồng bộ thay vì dựa trên trình phân tích cú pháp và đồng bộ. Chúng ta cũng có thể tận dụng requestAnimationFrame() để tránh các trường hợp chúng ta có thể gặp phải sự cố liên quan đến bộ nhớ đệm nhanh và các kiểu vô tình trở thành một phần trong đường dẫn hiển thị quan trọng. requestAnimationFrame() buộc khung đầu tiên phải được vẽ trước khi tải các kiểu. Một lựa chọn khác là sử dụng các dự án như loadCSS của Filament Group để yêu cầu CSS không đồng bộ bằng JavaScript.

  • Service worker sẽ lưu trữ một mục nhập đã lưu vào bộ nhớ đệm của giao diện ứng dụng để đối với các lượt truy cập lặp lại, shell có thể được tải hoàn toàn từ bộ nhớ đệm của trình chạy dịch vụ, trừ phi có bản cập nhật trên mạng.

Giao diện ứng dụng cho nội dung

Cách triển khai thực tế

Chúng tôi đã viết một mẫu hoạt động đầy đủ bằng cách sử dụng kiến trúc shell ứng dụng, JavaScript ES2015 cho ứng dụng khách và Express.js cho máy chủ. Tất nhiên không có gì cản trở bạn sử dụng ngăn xếp của riêng mình cho phần máy khách hoặc phần máy chủ (ví dụ: PHP, Ruby, Python).

Vòng đời của trình chạy dịch vụ

Đối với dự án shell ứng dụng, chúng ta sử dụng sw-precache, phần tử này cung cấp vòng đời của trình chạy dịch vụ như sau:

Sự kiện Hành động
Cài đặt Lưu giao diện ứng dụng và các tài nguyên trang đơn khác của ứng dụng.
Ứng dụng Xoá bộ nhớ đệm cũ.
Tìm nạp Phân phát một ứng dụng web trang đơn cho các URL, đồng thời sử dụng bộ nhớ đệm cho các thành phần và các phần được xác định trước. Sử dụng mạng cho các yêu cầu khác.

Bit máy chủ

Trong kiến trúc này, thành phần phía máy chủ (trong trường hợp của chúng ta là được viết bằng Express) có thể xử lý riêng nội dung và bản trình bày. Nội dung có thể được thêm vào một bố cục HTML dẫn đến việc hiển thị tĩnh của trang hoặc nội dung có thể được phân phối riêng biệt và được tải động.

Điều này có thể hiểu là cách thiết lập phía máy chủ của bạn có thể khác biệt đáng kể so với cách chúng tôi sử dụng cho ứng dụng minh hoạ. Hầu hết cách thiết lập máy chủ đều có thể thực hiện được mẫu ứng dụng web này, mặc dù kiểu thiết lập này cần phải được tái cấu trúc. Chúng tôi nhận thấy mô hình sau đây hoạt động khá hiệu quả:

Sơ đồ cấu trúc giao diện ứng dụng
  • Điểm cuối được xác định cho ba phần của ứng dụng: URL (chỉ mục/ký tự đại diện) mà người dùng dành cho ứng dụng, giao diện ứng dụng (trình chạy dịch vụ) và các phần HTML của bạn.

  • Mỗi điểm cuối có một bộ điều khiển đưa vào bố cục thanh điều khiển, từ đó có thể kéo các thành phần và chế độ xem của thanh tay lái. Nói một cách đơn giản, thành phần từng phần là các chế độ xem là các đoạn HTML được sao chép vào trang cuối cùng. Lưu ý: Các khung JavaScript thực hiện đồng bộ hoá dữ liệu nâng cao hơn thường dễ chuyển đổi hơn sang kiến trúc Giao diện ứng dụng. Chúng có xu hướng sử dụng tính năng liên kết và đồng bộ hoá dữ liệu thay vì từng phần.

  • Ban đầu, người dùng được phân phát một trang tĩnh có nội dung. Trang này đăng ký một trình chạy dịch vụ (nếu được hỗ trợ) để lưu shell ứng dụng và mọi thứ phụ thuộc vào (CSS, JS, v.v.).

  • Sau đó, giao diện ứng dụng sẽ hoạt động như một ứng dụng web trang đơn, sử dụng javascript để XHR trong nội dung cho một URL cụ thể. Các lệnh gọi XHR được thực hiện đến điểm cuối /partials*, điểm cuối này trả về phần nhỏ HTML, CSS và JS cần thiết để hiển thị nội dung đó. Lưu ý: Có nhiều cách để đạt được mục tiêu này và XHR chỉ là một trong số đó. Một số ứng dụng sẽ nội tuyến dữ liệu của chúng (có thể sử dụng JSON) để kết xuất ban đầu và do đó không phải là "tĩnh" theo nghĩa HTML đã làm phẳng.

  • Những trình duyệt không có hỗ trợ trình chạy dịch vụ phải luôn được cung cấp trải nghiệm dự phòng. Trong bản minh hoạ, chúng ta sẽ quay trở lại phương thức kết xuất tĩnh cơ bản phía máy chủ, nhưng đây chỉ là một trong số nhiều lựa chọn. Khía cạnh trình chạy dịch vụ mang lại cho bạn những cơ hội mới để nâng cao hiệu suất của ứng dụng kiểu Ứng dụng trang đơn bằng cách sử dụng giao diện ứng dụng đã lưu vào bộ nhớ đệm.

Tạo phiên bản tệp

Một câu hỏi được đặt ra là làm thế nào để xử lý việc tạo phiên bản và cập nhật tệp. Đây là ứng dụng dành riêng cho bạn và có các tuỳ chọn sau:

  • Mạng trước tiên và sử dụng phiên bản đã lưu trong bộ nhớ đệm.

  • Chỉ mạng và bị lỗi nếu không có kết nối mạng.

  • Lưu phiên bản cũ vào bộ nhớ đệm và cập nhật sau.

Đối với chính giao diện ứng dụng, bạn nên sử dụng phương pháp ưu tiên bộ nhớ đệm để thiết lập trình chạy dịch vụ. Nếu bạn không lưu shell ứng dụng vào bộ nhớ đệm, tức là bạn chưa áp dụng cấu trúc đúng cách.

Công cụ

Chúng tôi duy trì một số thư viện trình trợ giúp trình chạy dịch vụ khác nhau giúp quá trình lưu trước shell của ứng dụng hoặc xử lý các mẫu lưu vào bộ nhớ đệm phổ biến dễ dàng hơn.

Ảnh chụp màn hình Trang web thư viện Service Worker trên Web cơ bản

Sử dụng sw-precache cho giao diện ứng dụng của bạn

Việc sử dụng sw-precache để lưu shell ứng dụng vào bộ nhớ đệm sẽ giúp xử lý các vấn đề về sửa đổi tệp, câu hỏi về việc cài đặt/kích hoạt và tình huống tìm nạp cho shell ứng dụng. Thả sw-precache vào quy trình xây dựng của ứng dụng và sử dụng các ký tự đại diện có thể định cấu hình để chọn các tài nguyên tĩnh của bạn. Thay vì tạo tập lệnh trình chạy dịch vụ theo cách thủ công, hãy để sw-precache tạo một tập lệnh quản lý bộ nhớ đệm của bạn một cách an toàn và hiệu quả thông qua trình xử lý tìm nạp ưu tiên bộ nhớ đệm.

Các lượt truy cập ban đầu vào ứng dụng của bạn sẽ kích hoạt khả năng lưu trước toàn bộ tài nguyên cần thiết. Điều này tương tự như trải nghiệm cài đặt ứng dụng gốc từ cửa hàng ứng dụng. Khi người dùng quay lại ứng dụng của bạn, chỉ những tài nguyên đã cập nhật mới được tải xuống. Trong bản minh hoạ, chúng tôi sẽ thông báo cho người dùng khi có shell mới với thông báo "Bản cập nhật ứng dụng. Làm mới để dùng phiên bản mới." Mẫu này là một cách dễ dàng để cho người dùng biết rằng họ có thể làm mới để sử dụng phiên bản mới nhất.

Dùng sw-toolbox để lưu vào bộ nhớ đệm trong thời gian chạy

Dùng sw-toolbox để lưu vào bộ nhớ đệm trong thời gian chạy bằng nhiều chiến lược tuỳ thuộc vào tài nguyên:

  • cacheFirst dành cho hình ảnh, cùng với bộ nhớ đệm được đặt tên chuyên dụng có chính sách hết hạn tuỳ chỉnh là N maxEntries.

  • networkFirst hoặc nhanh nhất đối với các yêu cầu API, tuỳ thuộc vào độ mới của nội dung mong muốn. Nhanh nhất có thể cũng ổn, nhưng nếu có một nguồn cấp dữ liệu API cụ thể được cập nhật thường xuyên, hãy sử dụng networkFirst.

Kết luận

Kiến trúc shell ứng dụng mang lại một số lợi ích nhưng chỉ phù hợp với một số lớp ứng dụng. Mô hình này vẫn còn mới và sẽ rất đáng để đánh giá nỗ lực cũng như lợi ích hiệu suất tổng thể của kiến trúc này.

Trong các thử nghiệm, chúng tôi đã tận dụng việc chia sẻ mẫu giữa ứng dụng và máy chủ để giảm thiểu công việc xây dựng hai lớp ứng dụng. Điều này đảm bảo rằng tính năng nâng cao dần dần vẫn là một tính năng cốt lõi.

Nếu bạn đang cân nhắc sử dụng trình chạy dịch vụ trong ứng dụng của mình, hãy xem cấu trúc và đánh giá xem cấu trúc đó có phù hợp với dự án của riêng bạn không.

Xin cảm ơn những người đánh giá: Jeff Posnick, Paul Lewis, Alex Russell, Seth Thompson, Rob Dodson, Taylor Savage và Joe Medley.