Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WorkBox #13

Open
yaofly2012 opened this issue Oct 15, 2018 · 6 comments
Open

WorkBox #13

yaofly2012 opened this issue Oct 15, 2018 · 6 comments
Labels

Comments

@yaofly2012
Copy link
Owner

yaofly2012 commented Oct 15, 2018

1 route模块

1.1 缓存策略

statleWihieRevalidate PK netWorkFirst
serving-suggestions

window和sw scope共享CacheStorage ? !

1.2 跨域问题

opaque responses

1.3 Route.handleCallback

处理函数的content.params的值来自match函数的返回值,但是如果返回值是bool值则content.params为undefined(要看源码才知道为啥这样)。

1.4 Router, DefaultRouter

用来管理注册的route。处理流程见图
workbox.routing是DefaultRoute对象

  • registerNavigationRoute(cachedAssetUrl, options)
    注册个NavigationRoute,匹配的请求都返回由cachedAssetUrl指定的预缓存Response。如果预缓存里没有,则从网络获取(此时Response不会被缓存)。

  • registerRoute(capture, handle)
    capture可以是

  1. String -> 创建Route
    字符串是精确匹配的方式:
const matchCallback = ({ url }) => {
      return url.href === captureUrl.href;
};
  1. RegExp -> 创建RegExpRoute

  2. Function -> 创建Route

  3. Route -> 直接使用参数Route,忽略handle参数了

1.5 三种route:Route, RegExpRoute, NavigationRoute

const matchCb = ({url, event}) => {
  return (url.pathname === '/special/url');
};
  • RegExpRoute

For requests from the same origin, this regular expression will match as long as the request’s URL matches the regular expression.
However, for cross-origin requests, regular expressions must match the beginning of the URL

主要目的是为了开发自己明白自己是处理跨域的资源还是要处理同域的资源(同域认为是自己的)。
RegExpRoute的源码:

const match = ({ url }) => {
        const result = regExp.exec(url.href);
        // Return null immediately if there's no match.
        if (!result) {
          return null;
        }
        if (url.origin !== location.origin && result.index !== 0) {
          return null;
        }
        return result.slice(1);
      };

通过判断 result.index是否为0来确定的(故意的)。这样我们可以通过正则的写法来绕过跨域的限制。
所以workbox的“免责”来了:

If you wanted to match both local and third parties you can use a wildcard at the start of your regular expression, but this should be done with caution to ensure it doesn’t cause unexpected behaviors in you web app

  • NavigationRoute

It will only match incoming Requests whose mode is set to navigate

  1. 扫盲什么是navigation request

  2. NavigationRoute只匹配navigation Request,默认情况下其他的都通过,可以使用blacklist正则表达式数组配置黑名单增加更严格的限制。

  3. 如果要修改默认行为可以使用whitelist配置,whitelist的默认值是[/./],即匹配任意URL。

_math源码如下:

_match({ event, url }) {
      if (event.request.mode !== 'navigate') {
        return false;
      }
      const pathnameAndSearch = url.pathname + url.search;
      if (this._blacklist.some(regExp => regExp.test(pathnameAndSearch))) {
        return false;
      }
      if (this._whitelist.some(regExp => regExp.test(pathnameAndSearch))) {
  
        return true;
      } 
      return false;
    }

注意:

  1. request.mode, blacklist和whitelist三个条件的判断逻辑
  2. 只对路径和参数进行判断,不对域名进行check,即正则里不要包含域名部分

2 precaching模块

workbox.precaching.precacheAndRoute([
  '/styles/example.ac29.css',
  {
    url: '/index.html',
    revision: 'as46',
  }
]);
  1. url
    表示资源的路径

  2. revision
    是可选的,如果省略则取值为url的值,即认为资源的url也可代表资源的版本。
    版本可以是任意字符串,一般以资源内容的hash值作为版本号。

cacheable Response(可缓存的响应)

  1. 只针对运行时缓存
    workbox只对“有效的”响应进行缓存,那判断响应的有效性没有统一的标准,开发者可以使用cacheableResponse模块进行自定义“什么是有效的响应”。

  2. 默认行为

    • workbox各种内置策略
    • sw add/addAll行为

调试

Chrome

查看浏览器的serviceWorker

chrome://inspect/#service-workers

参考

  1. 官网
  2. Google ServiceWork
  3. concept-request-destination

源码分析

写的不错,必须看看

@yaofly2012
Copy link
Owner Author

yaofly2012 commented Nov 2, 2018

SW

The biggest impact of adding a service worker to your web application comes from responding to navigation requests without waiting on the network.

Fetch_API

1. fetch 和 XMLHttpRequest对比

相同点:

  • 都实现了异步请求
    不同点:
  • 出身背景不同:XMLHttpRequest主要是解决异步请求,fetch主要是为了ServiceWorker中请求
  • fetch只能异步(Promise方式的),XHR即可异步也可同步(基于事件方式的)。
    好吧已经讨论了
    Fetch 永生
    Why I still use XHR instead of the Fetch API
    MDN Using Fetch也在讨论了

2. fetch返回值Promise被reject

如果遇到网络故障,fetch() promise 将会 reject,带上一个 TypeError 对象

什么情况下算是网络故障?

  • CORS预检失败
  • 断网环境
    ... ?

issues

  1. 最好使用符合内容安全策略 (CSP)的链接而不是使用直接指向资源地址的方式来进行Fetch的请求

Headers

Headers熟悉的读写性?

参考:

  1. MDN Fetch_API

性能优化

  1. Speed up Service Worker with Navigation Preloads
  2. High-performance service worker loading

@yaofly2012
Copy link
Owner Author

yaofly2012 commented Nov 6, 2018

CacheStorage

Cache

只支持GET请求的缓存操作。见put操作的流程。Workbox怎么处理的?使用indexDB代替?

put

put方法对request和response的限制:

  1. request:
    • method必须是GET
    • URL scheme必须是http或者https
  2. response:
    • status 不能是206 ?
    • 如果包含Vary头,则Vary值中不能包含通配符* ?
    • 不能被locked或者disturbed ?
function put(request, response) {
  if(typeof request === 'string') {
    request = new Request(request);
  }
 ...
}

参考

  1. w3c ServiceWorker

@yaofly2012
Copy link
Owner Author

yaofly2012 commented Nov 9, 2018

precaching

先缓存再路由匹配。

功能

  1. 预缓存 & 新增,删除预缓存
    在install事件里执行预缓存操作,在activate事件里执行更新操作。
    注意预缓存的更新是在activate事件里,所以如果预缓存的资源发生变更记得要同步sw.js进行更新。

  2. 路由匹配
    绑定fetch事件,对request进行匹配操作。

precacheAndRoute

  1. revision 默认就是URL的值
    主要用于版本信息存在URL的资源
  2. 以资源的内容hash作为revision值,
    要在打包时完成了

Issues

  1. 资源版本存储在indexDB ?
    caches只能存储request/response。revision要存储在indexDB(异步的存储)
  2. 什么时候清理预缓存的资源?

Precache will perform these steps each time your service worker is install and activated,

  1. precache和runtime cache谁先处理请求?
    他们的路由匹配都依赖fetch事件,所以呢谁先注册fetch事件谁先被调用执行匹配操作。

@yaofly2012
Copy link
Owner Author

yaofly2012 commented Nov 9, 2018

ServiceWorkerGlobalScope

sw js执行的上下文。

Issues

  1. Developers should keep in mind that the ServiceWorker state is not persisted across the termination/restart cycle, so each event handler should assume it's being invoked with a bare, default global state.

console.log('SW Context') 
var count = 0;
self.addEventListener('fetch', function(event) {
   console.log(`fetch ${++count}--> from: ${event.request.url}`)
})
- 怎么手动重启?浏览器DevTools上面start/stop操作,重启操作只是修改SW的运行状态(Running, Stopped),不会影响SW本身的状态。
- 如何阻止浏览器自动关闭SW?

Service Worker termination by a timeout timer was canceled because DevTools is attached,即Chrome DevTool打开时,浏览器不会自动关闭SW。在开发的时候会发现这个提示。
- 浏览器每次启动都要占用时间,这方便的优化navigation-preload

  1. 每次注册sw js都会被执行么?【有变更时或者重启SW会执行SW JS,所以会存在问题1】
  2. 修改已注册的sw, 先触发fetch还是install事件?【 先触发fetch】
  3. 只能有异步的IO,不能有同步的IO?所以网络IO只能fetch, 缓存IO只能indexDB?
  4. 同一个页面注册多个sw ?
    如何标记一个SW?注册SW时有两个参数scriptURL和scope两个唯一才视为同一个个SW?

更新SW

  1. 避免新旧SW同时操作同一块缓存,一般通过版本号区分,这样在activate事件中可以方便清除旧缓存。workbox预缓存temp干嘛用的?

  2. 新SW“上位”

    • 被动上位
      等旧SW没有控制的页面,24h后?
    • 主动上位 self.skipWaiting()
      立马接管旧SW控制的页面
  3. 避免更改服务工作线程脚本的网址 ? 更新版本号可以吗?【不可以,qs也会作为sw的URL一部分】

  4. 新SW在激活过程中会暂存浏览器发送的请求,等激活成功后再统一发送请求。

事件

  1. 生命周期事件: install, activate
  2. 功能事件:fetch, message, push
  3. 异常/错误:error,unhandledrejection

ExtendableEvent

ExtendableEvent继承Event, 它添加了一个神奇的方法waitUntil():用来延长事件对象的生命时间。ServiceWorkerGlobalScope的事件:install(InstallEvent), activate(ActivateEvent), fetch(FetchEvent), message(ExtendableMessageEvent)都继承这个事件。
waitUntil()在各个事件里的延长事件生命时间的行为略有差别。

  1. install/activate/fetch事件
    见(ServiceWorker生命周期)[https://segmentfault.com/a/1190000017015820]
  2. message事件
    类似fetch事件里调用waitUntil。不会影响其他message回调函数调用。
  3. 其他事件:push, sync呢?待学习

clients属性 - Clients

注意:clients属性的类型是Clients不是元素是Client的数组。

WindowClient -> Client

SW JS事件回调函数外的代码什么时候执行

详解见参考
汇总下:

  1. In general, code that's outside any event handler, in the "top-level" of the service worker's global scope, will run each and every time the service worker thread(/process) is started up

  2. SW随时被浏览器关闭(为了性能和电池优化),关闭的时候全局变量都被回收的;

  3. 在有事件请求的时候再被启动,启动的时候会再执行SW JS。

异常处理

通过error和unhandledrejection事件处理:
ServiceWorkerGlobalScope可绑定error事件来监听所有的错误。ServiceWorkerGlobalScope中大部分操作都是基于Promise的,如果Promise被rejected并且没有绑定处理函数,不会触发error事件,而是触发unhandledrejection事件。

self.addEventListener('error', function(e) {
    e.preventDefault();
})
self.addEventListener('unhandledrejection', function(e) {
    e.preventDefault();
})
  1. MDN unhandledrejection

参考

  1. 好书

@yaofly2012
Copy link
Owner Author

yaofly2012 commented Nov 12, 2018

什么时间注册SW

为啥onLoad之后才注册sw ?

  1. 启动serviceWorker,执行install事件(事件回调还也许还会有资源请求)会影响页面加载。等页面加载完后再去启动serviceWorker比较合适。
  2. 规则1并不是永远是对的,也有尽早加载的原因:

参考

  1. registration

@yaofly2012
Copy link
Owner Author

yaofly2012 commented Nov 13, 2018

Navigation Preload

问题

  1. 当进入一个页面时,浏览器要花费时间寻找对应的SW,如果当对应的SW还未启动时(SW随时被关闭,在使用的时候会启动),那浏览器还得花费时间去启动SW,这些都会导致请求被延时。尤其对navigation 请求的延时会带来性能问题->[high-performance-loading][https://developers.google.com/web/fundamentals/primers/service-workers/high-performance-loading]
  2. 这也是使用SW的弊端(没有最完美的解决方案,只有最合适的解决方案)
  3. navigation request不使用缓存(如果使用缓存就没必要了关注SW这个延时了)

解决方式

针对navigation 请求进行了优化,开启navigation request preload:

  1. GET 方式的navigation 请求不用等待SW启动后再发送;
  2. 在fetch(若有)中可以使用preload响应。
  3. 为了告诉服务的这个是preload请求,在请求头里添加了头部Service-Worker-Navigation-Preload

Tip

  1. 可以在DevTool面板或者页面chrome://serviceworker-internals/手动关闭,启动SW;
  2. 页面chrome://serviceworker-internals/可以查看SW详细信息:
Registration ID: 146
Navigation preload enabled: true // 是否开启navigation preload
Navigation preload header length: 5 //  Service-Worker-Navigation-Preload头部值长度,字符数量
Active worker: // 已激活SW详细信息
    Installation Status: ACTIVATED // SW状态
    Running Status: RUNNING    // SW的运行状态(STOPPED, RUNNING)
    Fetch handler existence: EXISTS // 表示是否绑定fetch事件回调函数
    Script: http://localhost:8899/sw-navigation-preload.js?v=1 // 脚本URL
    Version ID: 1482
    Renderer process ID: 3544
    Renderer thread ID: 140128
    DevTools agent route ID: 17
    Log: // SW中console.log输出信息

Waiting worker: // 等待中的SW详细信息,结构同Active worker

Issues

  1. 若SW启动后,preload request还没响应如何处理?
    FetchEvent.preloadResponse是个Promise对象,表示的是preload的请求会话。所以作为respondWith的参数即可
 event.respondWith(async function() {
        const response = await event.preloadResponse;
        if(response) {
            return response;
        }
        return fetch(event.request)
    }())
  1. navigation preload requests 和正常请求区别?
  2. Uncaught (in promise) TypeError: Failed to execute 'fetch' on 'ServiceWorkerGlobalScope': 'only-if-cached' can be set only with 'same-origin' mode

paulirish/caltrainschedule.io@82d03d9

限制

  1. 必须是navigation request
  2. 必须是GET请求
  3. 开启navigation preload功能

注意

  1. 开启了就必须使用preload Response或者缓存的Reponse,否则很容易造成冗余请求

参考

  1. Speed up Service Worker with Navigation Preloads

@yaofly2012 yaofly2012 added the sw label Aug 3, 2020
@yaofly2012 yaofly2012 added the PWA label Aug 21, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant