单页 Web 应用 (SPA) 的常见架构特性是支持应用的全局功能所需的一组最少的 HTML、CSS 和 JavaScript。但在实际操作中,这往往是所有页面始终显示的页眉、导航栏和其他常见界面元素。当 Service Worker 预缓存这个最小界面的 HTML 和依赖资源时,我们称之为应用 Shell。
App Shell 在 Web 应用的感知性能方面发挥着重要作用。这是最先加载的内容,因此,也是用户在等待内容填充到界面中时最先看到的内容。
App Shell 可以快速加载(前提是网络可用且至少还快),但预缓存 App Shell 及其关联资产的 Service Worker 可为 App Shell 模型提供以下附加优势:
- 以可靠、一致的效果吸引重复访问用户。首次访问没有安装 Service Worker 的应用时,必须先从网络加载应用的标记及其关联资源,然后 Service Worker 才能将其放入缓存中。但是,重复访问将从缓存中提取 App Shell,这意味着可以瞬时加载和呈现。
- 在离线场景中可靠地使用功能。有时互联网接入不稳定,或根本无法上网,用户可怕的“我们找不到该网站”它不会消失。App Shell 模型通过使用缓存中的 App Shell 标记响应任何导航请求,解决此问题。即使有人在您的 Web 应用中访问他们之前从未访问过的网址,App Shell 也会从缓存中提供,并用有用内容填充。
何时应使用 App Shell 模型
如果您的常用界面元素不会因路由而异,但内容会更改,则 App Shell 最为合理。大多数 SPA 可能已经使用一种非常有效的 App Shell 模型。
如果项目就是这样描述的,而您想要添加 Service Worker 来增强其可靠性和性能,则 App Shell 应当:
- 加载速度快。
- 使用
Cache
实例中的静态资源。 - 添加常见的界面元素,如标题和边栏,这些元素与网页内容分开。
- 检索并显示特定网页的内容。
- 如果适用,可以选择缓存动态内容以供离线观看。
App Shell 通过 API 或 JavaScript 中绑定的内容动态加载特定于网页的内容。它还应该自更新,也就是说,如果 App Shell 的标记发生更改,Service Worker 更新应获取新的 App Shell 并自动对其进行缓存。
构建应用 Shell
App Shell 应与内容独立存在,但又能为在其中填充内容提供基础。理想情况下,它应尽可能精简,但应在初始下载时包含足够多的实用内容,以便用户了解体验会快速加载。
合适的平衡点取决于您的应用。Jake Archibald 的 Trained To Thrill 应用的应用 Shell 包含一个带有刷新按钮的标头,用于从 Flickr 提取新内容。
App Shell 标记因项目而异,不过以下是一个提供应用样板文件的 index.html
文件示例:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>
Application Shell Example
</title>
<link rel="manifest" href="/manifest.json">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="stylesheet" type="text/css" href="styles/global.css">
</head>
<body>
<header class="header">
<!-- Application header -->
<h1 class="header__title">Application Shell Example</h1>
</header>
<nav class="nav">
<!-- Navigation items -->
</nav>
<main id="app">
<!-- Where the application content populates -->
</main>
<div class="loader">
<!-- Spinner/content placeholders -->
</div>
<!-- Critical application shell logic -->
<script src="app.js"></script>
<!-- Service worker registration script -->
<script>
if ('serviceWorker' in navigator) {
// Register a service worker after the load event
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js');
});
}
</script>
</body>
</html>
为项目构建 App Shell 时,它必须具有以下特征:
- HTML 应明确为各个界面元素设置独立的区域。在上面的示例中,这包括应用的标题、导航、主要内容区域和用于加载“旋转图标”的空间仅在内容加载时显示。
- 为应用程序 Shell 加载的初始 JavaScript 和 CSS 应尽量减少,并且只与应用程序 Shell 本身的功能(与内容无关)。这可确保应用能够尽快渲染其 shell,并最大限度地减少主线程工作,直到内容显示为止。
- 用于注册 Service Worker 的内嵌脚本。
构建 App Shell 后,您可以构建一个 Service Worker 来缓存它及其资源。
缓存 App Shell
App Shell 及其所需资产是 Service Worker 在安装时应立即预缓存的内容。假设有一个类似上例的 App Shell,让我们看看如何使用 workbox-build
在基本的 Workbox 示例中完成此操作:
// build-sw.js
import {generateSW} from 'workbox-build';
// Where the generated service worker will be written to:
const swDest = './dist/sw.js';
generateSW({
swDest,
globDirectory: './dist',
globPatterns: [
// The necessary CSS and JS for the app shell
'**/*.js',
'**/*.css',
// The app shell itself
'shell.html'
],
// All navigations for URLs not precached will use this HTML
navigateFallback: 'shell.html'
}).then(({count, size}) => {
console.log(`Generated ${swDest}, which precaches ${count} assets totaling ${size} bytes.`);
});
存储在 build-sw.js
中的这一配置会导入应用的 CSS 和 JavaScript,包括 shell.html
中包含的 App Shell 标记文件。该脚本通过 Node 执行,如下所示:
node build-sw.js
生成的 Service Worker 将写入 ./dist/sw.js
,并在完成后记录以下消息:
Generated ./dist/sw.js, which precaches 5 assets totaling 44375 bytes.
页面加载时,Service Worker 会预缓存 App Shell 标记及其依赖项:
<ph type="x-smartling-placeholder">![Chrome 开发者工具中网络面板的屏幕截图,显示了从网络下载的资源列表。由 Service Worker 预缓存的资源与行左侧一个齿轮的其他资源区分开来。在安装时,Service Worker 会预缓存一些 JavaScript 和 CSS 文件。](https://developer.chrome.google.cn/static/docs/workbox/app-shell-model/image/a-screenshot-the-network-96ad6896c678b.png?authuser=0000&hl=ca)
在几乎任何工作流(包括使用捆绑器的项目)中,都可以预缓存 App Shell 的 HTML、CSS 和 JavaScript。随着您阅读文档的学习,您将学习如何直接使用 Workbox 设置工具链,以构建最适合您项目的 Service Worker(无论它是否为 SPA)。
总结
将 App Shell 模型与 Service Worker 结合使用对离线缓存非常有用,尤其是在将其预缓存功能与用于标记或 API 响应的网络优先、回退到缓存策略结合使用时。这样,您便可以获得可靠、快速的体验,在重复访问时即使在离线条件下,也能立即呈现您的 App Shell。