渐进式 web 应用程序

渐进式 web 应用程序(PWA)由一系列技术和模式组成,主要用于改善用户体验,帮助创建更可靠和可用的应用程序。尤其是移动用户,他们会发现应用程序能更好的集成到他们的设备中,就跟本地安装的应用程序一样。

渐进式 web 应用程序主要由两种技术组成:Service Worker 和 Manifest。Dojo 的构建命令通过 .dojorcpwa 对象支持这两种技术。

Manifest

Manifest 在一个 JSON 文件中描述一个应用程序,并提供了一些详细信息,因此可以直接从万维网安装到设备的主屏幕上。

.dojorc

  1. {
  2. "build-app": {
  3. "pwa": {
  4. "manifest": {
  5. "name": "Todo MVC",
  6. "description": "A simple to-do application created with Dojo",
  7. "icons": [
  8. { "src": "./favicon-16x16.png", "sizes": "16x16", "type": "image/png" },
  9. { "src": "./favicon-32x32.png", "sizes": "32x32", "type": "image/png" },
  10. { "src": "./favicon-48x48.png", "sizes": "48x48", "type": "image/png" },
  11. { "src": "./favicon-256x256.png", "sizes": "256x256", "type": "image/png" }
  12. ]
  13. }
  14. }
  15. }
  16. }

当提供了 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

  1. {
  2. "build-app": {
  3. "pwa": {
  4. "serviceWorker": {
  5. "cachePrefix": "my-app",
  6. "excludeBundles": ["admin"],
  7. "routes": [
  8. {
  9. "urlPattern": ".*\\.(png|jpg|gif|svg)",
  10. "strategy": "cacheFirst",
  11. "cacheName": "my-app-images",
  12. "expiration": { "maxEntries": 10, "maxAgeSeconds": 604800 }
  13. },
  14. {
  15. "urlPattern": "http://my-app-url.com/api/articles",
  16. "strategy": "cacheFirst",
  17. "expiration": { "maxEntries": 25, "maxAgeSeconds": 86400 }
  18. }
  19. ]
  20. }
  21. }
  22. }
  23. }

ServiceWorker 配置

在底层,@dojo/webpack-contrib 中的 ServicerWorkerPlugin 用于生成 service worker,它的所有选项都是有效的 pwa.serviceWorker 属性。

属性类型可选描述
bundlesstring[]需要进行预缓存的一组包。默认是所有包。
cachePrefixstring在运行时进行预缓存使用的前缀。
clientsClaimbooleanService worker 是否要在开始激活时控制客户端。默认为 false
excludeBundlesstring[]要从预缓存中排除的一组包。默认为 []
importScriptsstring[]需要在 service worker 中加载的一组脚本的路径。
precacheobject描述预缓存配置选项的对象(见下文)。
routesobject[]一组描述要在运行时缓存的配置对象(见下文)。
skipWaitingbooleanService worker 是否要跳过“等待”生命周期。

预缓存

precache 选项使用以下选项控制预缓存行为:

属性类型可选描述
baseDirstring匹配 include 时使用的根目录。
ignorestring[]一组通配符模式的字符串,当生成预缓存项时用于匹配需要忽略的文件。默认为 [ ‘node_modules/*/‘ ]
includestring or string[]一个或者一组通配符模式的字符串,用于匹配 precache 应该包含的文件。默认是构建管道中的所有文件。
indexstring如果请求以 / 结尾的 URL 失败,则应该查找的 index 文件名。默认为 ‘index.html’
maxCacheSizenumber往预缓存中添加的每一个文件不应超过的最大字节数。默认为 2097152 (2 MB)。
strictboolean如果为 true,则 include 模式匹配到一个不存在的文件夹时,构建就会失败。默认为 true
symlinksboolean当生成预缓存时是否允许软连接(symlinks)。默认为 true

运行时缓存

除了预缓存之外,还可以为特定路由提供缓存策略,以确定它们是否可以缓存以及如何缓存。routes 选项是一组包含以下属性的对象:

属性类型可选描述
urlPatternstring用于匹配特定路由的模式字符串(会被转换为正则表达式)。
strategystring缓存策略(见下文)。
optionsobject一个描述附加选项的对象。每个选项的详情如下。
cacheNamestring路由使用的缓存名称。注意 cachePrefix 不会 添加到缓存名前。默认为主运行时缓存(${cachePrefix}-runtime-${domain})。
cacheableResponseobject使用 HTTP 状态码或者报头(Header)信息来决定是否可以缓存响应的内容。此对象有两个可选属性:statusesheadersstatuses 是一组对缓存生效的状态码。headers 是一组 HTTP 的 header 和 value 键值对;至少要与一个报头匹配,响应才会被视为有效。当 strategy 的值是 ‘cacheFirst’ 时,默认为 { statuses: [ 200 ] };当 strategy 的值为 networkFirst 或者 staleWhileRevalidate 时,默认为 { statuses: [0, 200] }
expirationobject控制如何让缓存失效。此对象有两个可选属性。maxEntries 是任何时间可以缓存的响应个数。一旦超过此最大值,就会删除最旧的条目。maxAgeSeconds 是一个响应能缓存的最长时间(以秒为单位),超过此时长就会被删除。
networkTimeoutSecondsnumbernetworkFirst 策略一起使用,指定当网络请求的响应多久没有返回时就从缓存中获取资源,单位为秒。

目前支持四种路由策略:

  • networkFirst 尝试通过网络加载资源,如果请求失败或超时才从缓存中获取资源。对于频繁更改或者可能频繁更改(即没有版本控制)的资源,这是一个很有用的策略。
  • cacheFirst 优先从缓存中加载资源,如果缓存中不存在,则通过网络获取。这对于很少更改或者能缓存很长一段时间的资源(受版本控制的资源)来说是最好的策略。
  • networkOnly 强制始终通过网络获取资源,对于无需离线处理的资源是很有用的策略。
  • staleWhileRevalidate 同时从缓存和网络中请求资源。网络成功响应后都会更新缓存。此策略最适用于不需要持续更新的资源,比如用户信息。但是,当获取第三方资源时没有发送 CORS 报头,就无法读取响应的内容或验证状态码。因此,可能会缓存错误的响应。在这种情况下,networkFirst 策略可能更适合。