Lớp ứ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
- hiển thị nội dung một cách linh động
Vỏ ứng dụng là bí quyết để đạt được hiệu suất tốt một cách đáng tin cậy. Hãy coi shell của ứng dụng như gói mã mà bạn sẽ phát hành lên cửa hàng ứng dụng nếu đang xây dựng ứng dụng gốc. Đó là tải cần thiết để bắt đầu, nhưng có thể không phải là toàn bộ câu chuyện. Công cụ này lưu trữ giao diện người dùng trên thiết bị và lấy nội dung một cách linh động thông qua API.
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ể tiến bộ thay đổi thông qua việc sử dụng và sự đồng ý của người dùng để mang lại trải nghiệm giống ứng dụng gốc hơn, hoàn chỉnh với tính năng hỗ trợ khi không có mạng, 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 trình chạy dịch vụ cũng như khả năng lưu vào bộ nhớ đệm của các trình chạy đó. Điều này cho phép bạn tập trung vào tốc độ, mang lại cho ứng dụng web của bạn tính năng tải tức thì và các 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.
Để khai thác tối đa các khả năng này, chúng ta cần có một cách suy nghĩ mới về trang web: cấu trúc vỏ ứng dụng.
Hãy tìm hiểu cách tạo cấu trúc ứng dụng bằng cấu trúc vỏ ứng dụng tăng cường của trình chạy dịch vụ. Chúng ta sẽ xem xét cả kết xuất 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 vấn đề này, ví dụ bên dưới cho thấy lần tải đầu tiên của một ứng dụng sử dụng cấu trúc này. Lưu ý thông báo ngắn "Ứng dụng đã sẵn sàng để sử dụng khi không có mạng" ở 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.
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 ở chế độ nền, tách biệt với trang web của bạn. WebView phản hồi các sự kiện, bao gồm cả các yêu cầu mạng được thực hiện từ các trang mà WebView phân phát và thông báo đẩy từ máy chủ của bạn. Trình chạy dịch vụ có thời gian hoạt động ngắn theo ý định. Ứng dụng này sẽ thức dậy khi nhận được một sự kiện và chỉ chạy trong thời gian cần thiết để xử lý sự kiện đó.
Worker cũng có một bộ API hạn chế so với JavaScript trong ngữ cảnh duyệt web thông thường. Đây là tiêu chuẩn cho worker trên web. Worker không thể truy cập vào DOM nhưng có thể truy cập vào các thành phần như API bộ nhớ đệm và có thể gửi yêu cầu mạng bằng API tìm nạp. Bạn cũng có thể sử dụng IndexedDB API và postMessage() để lưu trữ dữ liệu và gửi thông báo giữa worker dịch vụ và các trang mà worker dịch vụ đó kiểm soát. Sự kiện đẩy được gửi từ máy chủ của bạn có thể gọi Notification API để 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 thực hiện từ một trang (kích hoạt một sự kiện tìm nạp trên trình chạy dịch vụ) và trả về một phản hồi được truy xuất từ mạng hoặc truy xuất từ bộ nhớ đệm cục bộ hoặc thậm chí được tạo theo phương thức lập trình. Về cơ bản, đây 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, trang web vẫn xem như không có trình chạy dịch vụ nào tham gia.
Để tìm hiểu sâu hơn về trình chạy dịch vụ, hãy đọc bài viết 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 hữu ích cho việc lưu vào bộ nhớ đệm khi không có 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ại vào trang web hoặc ứng dụng web của bạn. Bạn có thể lưu vỏ ứng dụng vào bộ nhớ đệm để ứng dụng hoạt động khi không có mạng và điền nội dung bằng JavaScript.
Trong các lượt truy cập lặp lại, tính năng 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 đến từ đó. Hãy coi đây là cách hiển thị thanh công cụ và thẻ ngay lập tức, sau đó tải nội dung còn lại một cách dần dần.
Để kiểm thử cấu trúc này trên thiết bị thực, chúng tôi đã chạy mẫu vỏ ứng dụng trên WebPageTest.org và hiển thị kết quả bên dưới.
Kiểm thử 1: Kiểm thử trên cáp bằng 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à không đạt được hiệu quả vẽ có ý nghĩa cho đến 1,2 giây. Nhờ lưu vào bộ nhớ đệm của trình chạy dịch vụ, lượt truy cập lại của chúng ta đạt được hiệu quả vẽ có ý nghĩa và hoàn tất quá trình tải trong 0,5 giây.
Kiểm thử 2: Kiểm thử trên mạng 3G bằng Nexus 5 bằng Chrome Dev
Chúng ta cũng có thể kiểm thử mẫu bằng kết nối 3G hơi chậm hơn một chút. Lần này, chúng ta mất 2,5 giây trong lượt truy cập đầu tiên để có lần vẽ đầu tiên có ý nghĩa. Mất 7,1 giây để tải trang hoàn toàn. Với tính năng lưu vào bộ nhớ đệm của worker dịch vụ, lượt truy cập lại của chúng ta đạt được hiệu quả vẽ có ý nghĩa và hoàn tất quá trình tải trong 0,8 giây.
Các chế độ xem khác cũng cho thấy điều tương tự. So sánh 3 giây cần thiết để đạt được lần vẽ có ý nghĩa đầu tiên trong vỏ ứng dụng:
xuống còn 0,9 giây khi tải cùng một trang từ bộ nhớ đệm của trình chạy dịch vụ. Người dùng cuối tiết kiệm được hơn 2 giây.
Bạn cũng có thể đạt được hiệu suất tương tự và đáng tin cậy cho các ứng dụng của riêng mình bằng cách sử dụng cấu trúc vỏ ứng dụng.
Liệu worker dịch vụ có yêu cầu chúng ta phải suy nghĩ lại cách cấu trúc ứng dụng không?
Worker dịch vụ ngụ ý một số thay đổi tinh tế trong cấu trúc ứng dụng. Thay vì nén tất cả ứng dụng của bạn vào một chuỗi HTML, bạn nên làm theo kiểu AJAX. Đây là nơi bạn có một shell (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 biệt.
Việc phân tách này có nhiều tác động. 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 worker dịch vụ trên ứng dụng. Trong các lần truy cập tiếp theo, bạn chỉ cần yêu cầu dữ liệu.
Còn tính năng cải tiến tăng dần thì sao?
Mặc dù không phải trình duyệt nào cũng hỗ trợ worker dịch vụ, nhưng cấu trúc vỏ nội dung ứng dụng sử dụng tính năng tăng cườ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.
Dưới đây, bạn có thể thấy phiên bản đầy đủ được kết xuất trong Chrome, Firefox Nightly và Safari. Ở ngoài cùng bên trái, bạn có thể thấy phiên bản Safari trong đó nội dung được kết xuất trên máy chủ mà không cần worker dịch vụ. Ở bên phải, chúng ta thấy các phiên bản Chrome và Firefox Nightly được hỗ trợ bởi worker dịch vụ.
Khi nào bạn nên sử dụng cấu trúc này?
Cấu trúc giao diện ứng dụng phù hợp nhất với các ứng dụng và trang web động. Nếu trang web của bạn nhỏ và tĩnh, có thể bạn không cần vỏ ứng dụng và 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 tách logic ứng dụng khỏi nội dung, giúp bạn dễ dàng áp dụng mẫu này hơn.
Có ứng dụng nào đang dùng mẫu này không?
Bạn có thể sử dụng cấu trúc vỏ ứng dụng 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à cấu trúc này đã hoạt động tốt trên các trang web 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.
Vỏ ứng dụng ngoại tuyến là một điểm cải thiện lớn về hiệu suất và cũng được minh hoạ rõ trong ứng dụng Wikipedia ngoại tuyến của Jake Archibald và ứng dụng web tiến bộ Flipkart Lite.
Giải thích về cấu trúc
Trong trải nghiệm tải lần đầu, mục tiêu của bạn là đưa nội dung có ý nghĩa đến màn hình của người dùng nhanh nhất có thể.
Tải lần đầu và tải các trang khác
Nhìn chung, cấu trúc giao diện ứng dụng sẽ:
Ưu tiên tải ban đầu, nhưng cho phép worker dịch vụ lưu vỏ ứ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 phải tìm nạp lại vỏ từ mạng.
Tải từng phần hoặc tải trong nền mọi nội dung khác. Bạn nên sử dụng tính năng lưu vào bộ nhớ đệm đọc xuyên cho nội dung động.
Sử dụng các 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 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. (Chúng ta sẽ nói thêm về sw-precache ở phần sau.)
Để thực hiện việc này:
Máy chủ sẽ gửi nội dung HTML mà ứng dụng có thể hiển thị và sử dụng các tiêu đề hết hạn bộ nhớ đệm HTTP trong tương lai xa để tính đến các trình duyệt không hỗ trợ worker dịch vụ. Tệp này sẽ phân phát tên tệp bằng hàm băm để cho phép cả việ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 các kiểu CSS nội tuyến trong thẻ
<style>
trong tài liệu<head>
để cung cấp lần sơn đầu tiên nhanh chóng của vỏ ứ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. Vì không thể tải CSS không đồng bộ, nên chúng ta có thể yêu cầu kiểu bằng JavaScript vì ngôn ngữ này LÀ không đồng bộ thay vì đồng bộ và do trình phân tích cú pháp điều khiển. Chúng ta cũng có thể tận dụngrequestAnimationFrame()
để tránh các trường hợp có thể xảy ra một lượt truy cập nhanh vào bộ nhớ đệm và kết thúc bằng các kiểu vô tình trở thành một phần của đường dẫn kết xuất quan trọng.requestAnimationFrame()
buộc phải vẽ khung đầu tiên trước khi tải các kiểu. Một cách 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.Trình chạy dịch vụ sẽ lưu trữ một mục trong bộ nhớ đệm của vỏ ứng dụng để trong các lần truy cập lặp lại, vỏ 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.
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 cấu trúc vỏ ứng dụng, JavaScript ES2015 cơ bản cho ứng dụng và Express.js cho máy chủ. Tất nhiên, bạn có thể sử dụng ngăn xếp của riêng mình cho phần ứng dụng 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 để cung cấp vòng đời của worker dịch vụ 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 ứng dụng trang đơn khác vào bộ nhớ đệm. |
Kích hoạt | Xoá bộ nhớ đệm cũ. |
Tìm nạp | Phân phát một ứng dụng web một trang cho URL và sử dụng bộ nhớ đệm cho các thành phần và 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 cấu trúc này, thành phần phía máy chủ (trong trường hợp của chúng ta, được viết bằng Express) phải có khả năng xử lý nội dung và bản trình bày riêng biệt. Bạn có thể thêm nội dung vào một bố cục HTML để hiển thị trang ở dạng tĩnh hoặc phân phát nội dung riêng biệt và tải động.
Rõ ràng là 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 chế độ thiết lập mà chúng tôi sử dụng cho ứng dụng minh hoạ. Hầu hết các chế độ thiết lập máy chủ đều có thể đạt được mẫu ứng dụng web này, mặc dù có yêu cầu một số cấu trúc lại. Chúng tôi nhận thấy mô hình sau đây hoạt động khá hiệu quả:
Các điểm cuối được xác định cho ba phần của ứng dụng: URL mà người dùng nhìn thấy (chỉ mục/ký tự đại diện), vỏ ứng dụng (trình chạy dịch vụ) và các phần HTML.
Mỗi điểm cuối có một bộ điều khiển sẽ kéo vào một bố cục tay điều khiển, từ đó có thể kéo vào các thành phần hiển thị và phần tay điều khiển. Nói một cách đơn giản, thành phần hiển thị một phần là các thành phần hiển thị 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 việc đồng bộ hoá dữ liệu nâng cao thường dễ dàng chuyển sang cấu trúc Vỏ ứng dụng hơn. Các ứng dụng này thường sử dụng tính năng liên kết và đồng bộ hoá dữ liệu thay vì các 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 vào bộ nhớ đệm vỏ ứng dụng và mọi thứ mà vỏ ứng dụng phụ thuộc vào (CSS, JS, v.v.).
Sau đó, vỏ ứng dụng sẽ hoạt động như một ứng dụng web một trang, 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 một điểm cuối /partials*. Điểm cuối này trả về một phần nhỏ HTML, CSS và JS cần thiết để hiển thị nội dung đó. Lưu ý: Có nhiều cách để tiếp cận vấn đề 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) để hiển thị ban đầu và do đó không "tĩnh" theo nghĩa HTML được làm phẳng.
Trình duyệt không hỗ trợ worker dịch vụ phải luôn được phân phát trải nghiệm dự phòng. Trong bản minh hoạ, chúng ta sẽ quay lại chế độ kết xuất tĩnh cơ bản phía máy chủ, nhưng đây chỉ là một trong nhiều tuỳ chọn. Phương diện worker dịch vụ mang đến 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 một trang bằng cách sử dụng vỏ ứng dụng được lưu vào bộ nhớ đệm.
Tạo phiên bản tệp
Một câu hỏi đặ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. Tuỳ chọn này dành riêng cho ứng dụng và có các lựa chọn sau:
Trước tiên, hãy sử dụng mạng và sử dụng phiên bản đã lưu vào bộ nhớ đệm nếu không.
Chỉ có mạng và không hoạt động nếu không có 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 vỏ ứng dụng, bạn nên sử dụng phương pháp ưu tiên bộ nhớ đệm để thiết lập worker dịch vụ. Nếu không lưu vỏ ứng dụng vào bộ nhớ đệm, thì bạn chưa áp dụng đúng cấu trúc.
Công cụ
Chúng tôi duy trì một số thư viện trợ giúp của worker dịch vụ giúp quy trình lưu trước giao diện của ứng dụng hoặc xử lý các mẫu lưu vào bộ nhớ đệm phổ biến dễ thiết lập hơn.
Sử dụng sw-precache cho vỏ ứng dụng
Việc sử dụng sw-precache để lưu vào bộ nhớ đệm cho giao diện ứng dụng sẽ xử lý các vấn đề liên quan đến bản sửa đổi tệp, câu hỏi cài đặt/kích hoạt và trường hợp tìm nạp cho giao diện ứng dụng. Thả sw-precache vào quy trình xây dựng ứng dụng và sử dụng ký tự đại diện có thể định cấu hình để chọn tài nguyên tĩnh. 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 một cách an toàn và hiệu quả bằng cách sử dụng trình xử lý tìm nạp bộ nhớ đệm trước.
Các lượt truy cập ban đầu vào ứng dụng của bạn sẽ kích hoạt tính năng tìm nạp trước toàn bộ tài nguyên cần thiết. Trải nghiệm này tương tự như khi 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, 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 thông báo cho người dùng khi có một shell mới bằng thông báo "App updates" (Cập nhật ứng dụng). Làm mới để tải phiên bản mới." Mẫu này là một cách ít gây phiền hà để cho người dùng biết rằng họ có thể làm mới để tải phiên bản mới nhất.
Sử dụng sw-toolbox để lưu vào bộ nhớ đệm trong thời gian chạy
Sử 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 cho hình ảnh, cùng với một bộ nhớ đệm có 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. Bạn có thể chọn Fastest (Nhanh nhất), 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
Cấu trúc vỏ ứng dụng có 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à bạn nên đánh giá nỗ lực và lợi ích về hiệu suất tổng thể của cấu trúc này.
Trong các thử nghiệm của mình, chúng tôi đã tận dụng tính năng 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 tính năng cải tiế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, hãy xem xét cấu trúc và đánh giá xem liệu cấu trúc đó có phù hợp với các dự án của riêng bạn hay không.
Xin cảm ơn các chuyên gia đánh giá: Jeff Posnick, Paul Lewis, Alex Russell, Seth Thompson, Rob Dodson, Taylor Savage và Joe Medley.