一篇文章让你了解 service-worker

WilburBSA 8年前
   <h2>Service Worker 是什么?</h2>    <p>service worker 是独立于当前页面的一段运行在浏览器后台进程里的脚本。</p>    <p>service worker不需要用户打开 web 页面,也不需要其他交互,异步地运行在一个完全独立的上下文环境,不会对主线程造成阻塞。基于service worker可以实现消息推送,静默更新以及地理围栏等服务。</p>    <p>service worker提供一种渐进增强的特性,使用特性检测来渐渐增强,不会在老旧的不支持 service workers 的浏览器中产生影响。可以通过service workers解决让应用程序能够离线工作,让存储数据在离线时使用的问题。</p>    <p>注意事项:</p>    <p>1.service worker运行在它们自己的完全独立异步的全局上下文中,也就是说它们有自己的容器。</p>    <p>2.service worker没有直接操作DOM的权限,但是可以通过postMessage方法来与Web页面通信,让页面操作DOM。</p>    <p>3.service worker是一个可编程的网络代理,允许开发者控制页面上处理的网络请求。</p>    <p>4.浏览器可能随时回收service worker,在不被使用的时候,它会自己终止,而当它再次被用到的时候,会被重新激活。</p>    <p>5.service worker的生命周期是由事件驱动的而不是通过Client。</p>    <h3>Service Worker生命周期</h3>    <p>service worker拥有一个完全独立于Web页面的生命周期</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/ddbf04453970b46ea2043791c4b40937.png"></p>    <ol>     <li> <p>注册service worker,在网页上生效</p> </li>     <li> <p>安装成功,激活 或者 安装失败(下次加载会尝试重新安装)</p> </li>     <li> <p>激活后,在sw的作用域下作用所有的页面,首次控制sw不会生效,下次加载页面才会生效。</p> </li>     <li> <p>sw作用页面后,处理fetch(网络请求)和message(页面消息)事件 或者 被终止(节省内存)。</p> </li>    </ol>    <h3>Service Worker支持使用</h3>    <p>浏览器支持</p>    <p><a href="/misc/goto?guid=4959739575636535314" rel="nofollow,noindex">service worker support</a></p>    <p><img src="https://simg.open-open.com/show/4cc8377d8bf12e00da56cbd6202e1a6f.png"></p>    <p>polyfill</p>    <p>使用 ServiceWorker cache polyfill 让旧版本浏览器支持 ServiceWorker cache API,</p>    <p>https</p>    <p>Server需要支持https</p>    <p>通过service worker可以劫持连接,伪造和过滤响应,为了避免这些问题,只能在HTTPS的网页上注册service workers,防止加载service worker的时候不被坏人篡改。</p>    <p>Github Pages是HTTPS的,可以通过Github做一些尝试</p>    <h3>调试工具</h3>    <p>在调试的时候可以用于unregister、stop、start等操作</p>    <p>chrome访问 chrome://inspect/#service-workers 或 chrome://serviceworker-internals 查看service-workers</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/f82dbd62176309ac96715d1d2283442c.png"></p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/65d6e6fc6bb2d92be17e6dbd6bed781d.png"></p>    <p>firefox通过 about:debugging#workers 查看</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/8445820c73264901dbe77b02f5cb16fd.png"></p>    <h2>离线存储数据</h2>    <p>对URL寻址资源,使用 Cache API 。对其他数据,使用IndexedDB。</p>    <h2>离线阅读</h2>    <h3>demo</h3>    <p>使用 https 访问本文,打开ChromeDevTools,选择Application选项卡->Service Workers</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/c420c1cc6263a2a2a7769de38693ed78.png"></p>    <p>可以看到Service Workers注册</p>    <p>点击下面离线保存按钮</p>    <p>然后选择Cache Storage,可以看到文字内容已经缓存到Cache Storage</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/131f5bb4b041496f0d225b088c33d8b1.png"></p>    <p>然后选择Service Workers 勾选 Offline,NetWork出现了:warning:️,然后试试离线访问本文:sunglasses:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/e39f48a9d1f1d00c7b07ca6f23ce1556.png"></p>    <h3>原理</h3>    <p>注册 service worker</p>    <p>创建一个 JavaScript 文件(比如:sw.js)作为 service worker</p>    <p>告诉浏览器注册这个JavaScript文件为service worker,检查service worker API是否可用,如果可用就注册service worker</p>    <pre>  <code class="language-javascript">if ('serviceWorker' in navigator) {    navigator.serviceWorker.register('/sw.js').then(function(registration) {      // Registration was successful      console.log('ServiceWorker registration successful with scope: ',    registration.scope);    }).catch(function(err) {      // registration failed :(      console.log('ServiceWorker registration failed: ', err);    });  }</code></pre>    <p>sw.js文件被放在这个域的根目录下,和网站同源。这个service work将会收到这个域下的所有fetch事件。</p>    <p>如果将service worker文件注册为/example/sw.js,那么,service worker只能收到/example/路径下的fetch事件(例如: /example/page1/, /example/page2/)。</p>    <pre>  <code class="language-javascript">// Service Workers  if ('serviceWorker' in navigator && navigator.onLine) {    navigator.serviceWorker.register('/sw.js').then(function(registration) {      console.log('ServiceWorker registration successful with scope: ',    registration.scope);    }).catch(function(err) {      console.log('ServiceWorker registration failed: ', err);    });      var currentPath = window.location.pathname;    var cacheButton = document.querySelector('.offline-btn');      var imageArray = document.querySelectorAll('img');    if(cacheButton) {      cacheButton.addEventListener('click', function(event) {        event.preventDefault();        // 缓存当前链接和使用的图片        var pageResources = [currentPath];        for (i = 0; i < imageArray.length; i++) {          pageResources.push(imageArray[i].src);        }        caches.open('offline-' + currentPath).then(function(cache) {          var updateCache = cache.addAll(pageResources);            updateCache.then(function() {            console.log('Article is now available offline.');            cacheButton.innerHTML = "☺";          });            updateCache.catch(function (error) {            console.log('Article could not be saved offline.');            cacheButton.innerHTML = "☹";          });        });      });    }  }</code></pre>    <p>缓存站点的资源</p>    <p>定义需要缓存的文件,然后在sw注册安装后回到cache Api将资源文件写入缓存。如果所有的文件都被缓存成功了,那么service worker就安装成功了。如果任何一个文件下载失败,那么安装步骤就会失败。</p>    <pre>  <code class="language-javascript">var cacheName = 'v1';  var assetsToCache = [    '/styles/main.css',    '/script/main.js'  ];    self.addEventListener('install', function(event) {    event.waitUntil(      caches.open(cacheName).then(function(cache) {        return cache.addAll(assetsToCache);      }).then(function() {        return self.skipWaiting();      })    );  });</code></pre>    <p>从缓存中加载</p>    <p>service worker成功注册,并且用户浏览了另一个页面或者刷新了当前的页面,service worker将开始接收到fetch事件。</p>    <p>拦截网络请求并使用缓存,缓存命中,返回缓存资源,否则返回一个实时从网络请求fetch的结果。</p>    <pre>  <code class="language-javascript">self.addEventListener('fetch', function(event) {    var requestUrl = new URL(event.request.url);    if (requestUrl.origin === location.origin) {        if (requestUrl.pathname === '/') {        event.respondWith(          caches.open(cacheName).then(function(cache) {            return fetch(event.request).then(function(networkResponse) {              cache.put(event.request, networkResponse.clone());              return networkResponse;            }).catch(function() {              return cache.match(event.request);            });          })        );      }    }      event.respondWith(      caches.match(event.request).then(function(response) {        return response || fetch(event.request);      })    );  });</code></pre>    <p>缓存版本管理</p>    <p>版本修改的时候会触发activate,将旧版本的缓存清理掉。</p>    <pre>  <code class="language-javascript">var OFFLINE_PREFIX = 'offline-';  var CACHE_NAME = 'main_v1.0.0';  self.addEventListener('activate', function(event) {    var mainCache = [CACHE_NAME];    event.waitUntil(      caches.keys().then(function(cacheNames) {        return Promise.all(          cacheNames.map(function(cacheName) {            if ( mainCache.indexOf(cacheName) === -1 && cacheName.indexOf(OFFLINE_PREFIX) === -1 ) {              // When it doesn't match any condition, delete it.              console.info('SW: deleting ' + cacheName);              return caches.delete(cacheName);            }          })        );      })    );    return self.clients.claim();  });</code></pre>    <h2>Service Worker 库</h2>    <ul>     <li> <p><a href="/misc/goto?guid=4959739575727608778" rel="nofollow,noindex">sw-toolbox</a></p> </li>     <li> <p><a href="/misc/goto?guid=4959739575812836864" rel="nofollow,noindex">sw-precache</a></p> </li>     <li> <p><a href="/misc/goto?guid=4959739575896108062" rel="nofollow,noindex">offline-plugin</a> ,webpack离线插件</p> </li>    </ul>    <h2>参考</h2>    <ul>     <li> <p><a href="/misc/goto?guid=4959733392202952323" rel="nofollow,noindex">your-first-pwapp</a></p> </li>     <li> <p><a href="/misc/goto?guid=4959733392273143433" rel="nofollow,noindex">service-workers</a></p> </li>     <li> <p><a href="/misc/goto?guid=4959739576062894721" rel="nofollow,noindex">渐进式 Web App 的离线存储</a></p> </li>    </ul>    <p> </p>    <p>来自:http://kailian.github.io/2017/03/01/service-worker</p>    <p> </p>