渐进式 web 应用程序
渐进式 web 应用程序(PWA)由一系列技术和模式组成,主要用于改善用户体验,帮助创建更可靠和可用的应用程序。尤其是移动用户,他们会发现应用程序能更好的集成到他们的设备中,就跟本地安装的应用程序一样。
渐进式 web 应用程序主要由两种技术组成:Service Worker 和 Manifest。Dojo 的构建命令通过 .dojorc 的 pwa 对象支持这两种技术。
Manifest
Manifest 在一个 JSON 文件中描述一个应用程序,并提供了一些详细信息,因此可以直接从万维网安装到设备的主屏幕上。
.dojorc
{
	"build-app": {
		"pwa": {
			"manifest": {
				"name": "Todo MVC",
				"description": "A simple to-do application created with Dojo",
				"icons": [
					{ "src": "./favicon-16x16.png", "sizes": "16x16", "type": "image/png" },
					{ "src": "./favicon-32x32.png", "sizes": "32x32", "type": "image/png" },
					{ "src": "./favicon-48x48.png", "sizes": "48x48", "type": "image/png" },
					{ "src": "./favicon-256x256.png", "sizes": "256x256", "type": "image/png" }
				]
			}
		}
	}
}
当提供了 manifest 信息时,dojo build 将在应用程序的 index.html 中注入必需的 <meta> 标签。
- mobile-web-app-capable="yes": 告知 Android 上的 Chrome 可以将此应用程序添加到用户的主界面上。
- apple-mobile-web-app-capable="yes": 告知 iOS 设备可以将此应用程序添加到用户的主界面上。
- apple-mobile-web-app-status-bar-style="default": 告知 iOS 设备,状态栏使用默认外观。
- apple-touch-icon="{{icon}}": 相当于 Manifest 中的 icons,因为 iOS 当前没有从 Manifest 中读取 icons,所以需要为 icons 数组中每张图片单独注入一个 meta 标签。
Service worker
Service worder 是一种 web worker,能够拦截网络请求、缓存和提供资源。Dojo 的 build 命令能够自动构建功能全面的 service worker,它会在启动时激活,然后使用配置文件完成预缓存和自定义路由处理。
例如,我们编写一个配置文件来创建一个简单的 service worker,它会缓存除了 admin 包之外的所有应用程序包,也会缓存应用程序最近访问的图像和文章。
.dojorc
{
	"build-app": {
		"pwa": {
			"serviceWorker": {
				"cachePrefix": "my-app",
				"excludeBundles": ["admin"],
				"routes": [
					{
						"urlPattern": ".*\\.(png|jpg|gif|svg)",
						"strategy": "cacheFirst",
						"cacheName": "my-app-images",
						"expiration": { "maxEntries": 10, "maxAgeSeconds": 604800 }
					},
					{
						"urlPattern": "http://my-app-url.com/api/articles",
						"strategy": "cacheFirst",
						"expiration": { "maxEntries": 25, "maxAgeSeconds": 86400 }
					}
				]
			}
		}
	}
}
ServiceWorker 配置
在底层,@dojo/webpack-contrib 中的 ServicerWorkerPlugin 用于生成 service worker,它的所有选项都是有效的 pwa.serviceWorker 属性。
| 属性 | 类型 | 可选 | 描述 | 
|---|---|---|---|
| bundles | string[] | 是 | 需要进行预缓存的一组包。默认是所有包。 | 
| cachePrefix | string | 是 | 在运行时进行预缓存使用的前缀。 | 
| clientsClaim | boolean | 是 | Service worker 是否要在开始激活时控制客户端。默认为 false。 | 
| excludeBundles | string[] | 是 | 要从预缓存中排除的一组包。默认为 []。 | 
| importScripts | string[] | 是 | 需要在 service worker 中加载的一组脚本的路径。 | 
| precache | object | 是 | 描述预缓存配置选项的对象(见下文)。 | 
| routes | object[] | 是 | 一组描述要在运行时缓存的配置对象(见下文)。 | 
| skipWaiting | boolean | 是 | Service worker 是否要跳过“等待”生命周期。 | 
预缓存
precache 选项使用以下选项控制预缓存行为:
| 属性 | 类型 | 可选 | 描述 | 
|---|---|---|---|
| baseDir | string | 是 | 匹配 include时使用的根目录。 | 
| ignore | string[] | 是 | 一组通配符模式的字符串,当生成预缓存项时用于匹配需要忽略的文件。默认为 [ 'node_modules/**/*' ]。 | 
| include | stringorstring[] | 是 | 一个或者一组通配符模式的字符串,用于匹配 precache 应该包含的文件。默认是构建管道中的所有文件。 | 
| index | string | 是 | 如果请求以 /结尾的 URL 失败,则应该查找的 index 文件名。默认为'index.html'。 | 
| maxCacheSize | number | 是 | 往预缓存中添加的每一个文件不应超过的最大字节数。默认为 2097152(2 MB)。 | 
| strict | boolean | 是 | 如果为 true,则include模式匹配到一个不存在的文件夹时,构建就会失败。默认为true。 | 
| symlinks | boolean | 是 | 当生成预缓存时是否允许软连接(symlinks)。默认为 true。 | 
运行时缓存
除了预缓存之外,还可以为特定路由提供缓存策略,以确定它们是否可以缓存以及如何缓存。routes 选项是一组包含以下属性的对象:
| 属性 | 类型 | 可选 | 描述 | 
|---|---|---|---|
| urlPattern | string | 否 | 用于匹配特定路由的模式字符串(会被转换为正则表达式)。 | 
| strategy | string | 否 | 缓存策略(见下文)。 | 
| options | object | 是 | 一个描述附加选项的对象。每个选项的详情如下。 | 
| cacheName | string | 是 | 路由使用的缓存名称。注意 cachePrefix不会 添加到缓存名前。默认为主运行时缓存(${cachePrefix}-runtime-${domain})。 | 
| cacheableResponse | object | 是 | 使用 HTTP 状态码或者报头(Header)信息来决定是否可以缓存响应的内容。此对象有两个可选属性: statuses和headers。statuses是一组对缓存生效的状态码。headers是一组 HTTP 的 header 和 value 键值对;至少要与一个报头匹配,响应才会被视为有效。当strategy的值是'cacheFirst'时,默认为{ statuses: [ 200 ] };当strategy的值为networkFirst或者staleWhileRevalidate时,默认为{ statuses: [0, 200] } | 
| expiration | object | 是 | 控制如何让缓存失效。此对象有两个可选属性。 maxEntries是任何时间可以缓存的响应个数。一旦超过此最大值,就会删除最旧的条目。maxAgeSeconds是一个响应能缓存的最长时间(以秒为单位),超过此时长就会被删除。 | 
| networkTimeoutSeconds | number | 是 | 与 networkFirst策略一起使用,指定当网络请求的响应多久没有返回时就从缓存中获取资源,单位为秒。 | 
目前支持四种路由策略:
- networkFirst尝试通过网络加载资源,如果请求失败或超时才从缓存中获取资源。对于频繁更改或者可能频繁更改(即没有版本控制)的资源,这是一个很有用的策略。
- cacheFirst优先从缓存中加载资源,如果缓存中不存在,则通过网络获取。这对于很少更改或者能缓存很长一段时间的资源(受版本控制的资源)来说是最好的策略。
- networkOnly强制始终通过网络获取资源,对于无需离线处理的资源是很有用的策略。
- staleWhileRevalidate同时从缓存和网络中请求资源。网络成功响应后都会更新缓存。此策略最适用于不需要持续更新的资源,比如用户信息。但是,当获取第三方资源时没有发送 CORS 报头,就无法读取响应的内容或验证状态码。因此,可能会缓存错误的响应。在这种情况下,- networkFirst策略可能更适合。