Web的发展在Google的带领下真的可谓是日新月异,W3C也把Service Worker加入HTML5标准,Firefox也支持Service Worker,让Web应用也拥有Native App的特性,比如推送,后台同步,在Android下甚至可以更改状态栏的颜色,相信未来我们的应用只需要打开浏览器输入URL即可打开应用程序,在上面工作,软件都不需要安装,现在也诞生很多类似的应用,比如google docs,早在2009 Google就开发chromeOS 真可谓是一家眼光长远的公司

What is a Service Worker?

A service worker is a script that is run by your browser in the background, separate from a web page, opening the door to features which don’t need a web page or user interaction. Today, they already include features like push notifications and in the future it will include other things like, background sync, or geofencing. The core feature discussed in this tutorial is the ability to intercept and handle network requests, including programmatically managing a cache of responses.

service worker 是一个脚本,运行于浏览器后台,和Web页面分离,这些特性都不需要Web页面和交互,现在已经包含像推送,背景同步或者地理位置,并且将来它将会包含更多特性,在本教程中所讨论的核心功能是拦截和处理网络请求,包括管理响应和缓存

The reason this is such an exciting API is that it allows you to support offline experiences, giving developers complete control over what exactly that experience is.

这是一个激动人心的API,它可以让你的Web App支持离线使用,给开发人员可以完全控制用户体验。

Before service worker there was one other API that would give users an offline experience on the web called App Cache. The major issue with App Cache is the number of gotcha’s that exist as well as the design working particularly well for single page web apps, but not for multi-page sites. Service workers have been designed to avoid these common pain points.

之前,service work 的设计仅仅是解决单页Web App应用缓存问题,让用户也可以离线使用Web应用,Service Worker不适合多页面的站点

Service Worker的特性

It’s a JavaScript Worker, so it can’t access the DOM directly. Instead, a service worker can communicate with the pages it controls by responding to messages sent via the postMessage interface, and those pages can manipulate the DOM if needed.

它是一个Javascript worker,它不能直接访问DOM,service worker能够在多个页面之间通信,使用postMessage接口可以发送消息

Service worker is a programmable network proxy, allowing you to control how network requests from your page are handled.

Service worker 是可编程的网络代理,允许你在程序中控制网络请求

It will be terminated when not in use, and restarted when it’s next needed, so you cannot rely on global state within a service worker’s onfetch andonmessage handlers. If there is information that you need to persist and reuse across restarts, service workers do have access to the IndexedDB API.

当你不使用的时候,它会终止,并且在下一次启动时,所以你不能依赖于全局状态service worker’s的onfetch andonmessage处理程序, service workers 能够访问IndexedDB 的API

Service workers make extensive use of promises, so if you’re new to promises, then you should stop reading this and check out Jake Archibald’s article.

Service workers 广泛使用promises,如果你不了解promises,你应该停止阅读下文,查看Jake Archibald’s article.

Service Worker Lifecycle 生命周期

A service worker has a lifecycle which is completely separate from your web page.

service worker 生命周期就是从Web开始载入到页面完全关闭

To install a service worker for your site, you need to register it, which you do in your page’s JavaScript. Registering a service worker will cause the browser to start the service worker install step in the background.

在你的站点安装service worker,你需要在页面的Javascript程序注册的service worker将会在浏览器后台启动安装步骤

Typically during the install step, you’ll want to cache some static assets. If all the files are cached successfully, then the service worker becomes installed. If any of the files fail to download and cache, then the install step will fail and the service worker won’t activate (i.e. won’t be installed). If that happens, don’t worry, it’ll try again next time. But that means if it does install, you know you’ve got those static assets in the cache.

通常在安装期间,你会想缓存某些静态资源,如果全部文件缓存成功,表示service worker 已经安装完成,如果某些文件下载并且缓存失败,安装将会失败,service worker 也不会被激活,它会重试下载并且缓存,安装完成后,你可以在缓存中获取这些静态资源

When we’re installed, the activation step will follow and this is a great opportunity for handling any management of old caches, which we’ll cover during the service worker update section.

当安装完成时,激活步骤将会追踪和管理旧的缓存,当在更新时会被覆盖

After the activation step, the service worker will control all pages that fall under its scope, though the page that registered the service worker for the first time won’t be controlled until it’s loaded again. Once a service worker is in control, it will be in one of two states: either the service worker will be terminated to save memory, or it will handle fetch and message events which occur when a network request or message is made from your page.

激活之后service worker在它的作用范围内控制页面,第一次载入时service worker不会控制页面,当再次载入时,才会生效,service worker有两个状态,在终止状态,会节省内存使用,或者
当页面发出一个请求时或者生成一个消息时处理fetch and message事件

Service Worker 生命周期图

尝试Service Worker核心API

Grab the caches polyfill from this repository cache-polyfill,this polyfill will add support for Cache.addAll which Chrome M43’s implementation of the Cache API doesn’t currently support.

polyfill实现了Cache.addAll在Chrome M43’s不支持的缓存API

Grab dist/serviceworker-cache-polyfill.js to put somewhere in your site and use it in a service worker with the importScripts method. Any script which is imported will automatically be cached by the service worker.

把dist/serviceworker-cache-polyfill.js放到你的站点某一处, 并且在service worker使用importScripts 方法,全部脚本将会通过service worker被自动导入并缓存

importScripts('serviceworker-cache-polyfill.js');

需要HTTPS支持

During development you’ll be able to use service worker through localhost, but to deploy it on a site you’ll need to have HTTPS setup on your server.

开发期间,你可能通过localhost使用service worker,需要在服务器中配置HTTPS

Using service worker you can hijack connections, fabricate, and filter responses. Powerful stuff. While you would use these powers for good, a man-in-the-middle might not. To avoid this, you can only register for service workers on pages served over HTTPS, so we know the service worker the browser receives hasn’t been tampered with during its journey through the network.

使用Service Worker可以劫持连接,创建和过滤响应,非常强大,当你使用一个强大的东西,需要注意防止中间人的攻击,为了防止这种情况发生,我们必须在使用Service Worker的页面使用HTTPS服务

Github Pages are served over HTTPS, so they’re a great place to host demos.

Github Pages是使用HTTPS服务的,可以使用它来做测试

If you want to add HTTPS to your server then you’ll need to get a TLS certificate and set it up for your server. This varies depending on your setup, so check your server’s documentation and be sure to check out Mozilla’s SSL config generatorfor best practices.

如果你想添加HTTPS服务到你的服务器,你需要获得TLS证书,并且配置你的服务器,你可以查看Mozilla’s SSL config 文档配置HTTPS服务

怎么注册和安装Service Worker

To install a service worker you need to kick start the process by registering a service worker in your page. This tells the browser where your service worker JavaScript file lives.

安装service worker ,需要开启在页面开启一个service worker的服务进程,告诉浏览你的service worker 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);
  });
}

This code checks to see if the service worker API is available, and if it is, the service worker at /sw.js is registered.

代码首先检查service worker API 是否有效,如果是有效的 service worker 将会注册/sw.js

You can call register every time a page loads without concern; the browser will figure out if the service worker is already registered or not and handle it accordingly.

你不需要每次页面载入时都注册,浏览器会计算service worker 是否已经注册

This code checks to see if the service worker API is available, and if it is, the service worker at /sw.js is registered.

代码首先检查service worker API 是否有效,如果是有效的 service worker 将会注册/sw.js

You can call register every time a page loads without concern; the browser will figure out if the service worker is already registered or not and handle it accordingly.

你不需要每次页面载入时都注册,浏览器会判断service worker是否已经注册

One subtlety with the register method is the location of the service worker file. You’ll notice in this case that the service worker file is at the root of the domain. This means that the service worker’s scope will be the entire origin. In other words, this service worker will receive fetch events for everything on this domain. If we register the service worker file at /example/sw.js, then the service worker would only see fetch events for pages whose URL starts with /example/ (i.e./example/page1/, /example/page2/).

register 注册方法会查找service worker 文件,你会注意这个案例的Service Worker文件是在根域下,意思是说service worker’s的作用域将会被限制来源,换句话说,service worker 将会在这个域接收fetch事件,如果我们在 /example/sw.js
注册service worker文件,service worker 仅会监听以 /example/ 开始的URL(i.e./example/page1/, /example/page2/).

Now you can check that a service worker is enabled by going tochrome://inspect/#service-workers and looking for your site.

现在你已可以通过chrome://inspect/#service-workers检查站点service worker是否已开启

You may find it useful to test your service worker in an Incognito window so that you can close and reopen knowing that the previous service worker won’t affect the new window. Any registrations and caches created from within an Incognito window will be cleared out once that window is closed.

你也可以在新建的隐私窗口找到它并测试service worker,当隐私窗口关闭时任何注册和缓存都会被清除

Service Worker 安装步骤

After a controlled page kicks off the registration process, let’s shift to the point of view of the service worker script, which is given the opportunity to handle theinstall event.

完成注册之后,让我们看看service worker的安装

For the most basic example, you need to define a callback for the install event and decide which files you want to cache.

跟着这个基本示例,你需要为install事件定义回调函数并决定要缓存那些文件

// The files we want to cache
var urlsToCache = [
  '/',
  '/styles/main.css',
  '/script/main.js'
];

// Set the callback for the install step
self.addEventListener('install', function(event) {
    // Perform install steps
});

安装步骤

1.Open a cache 打开缓存
2.Cache our files 缓存设置的文件
3.Confirm whether all the required assets are cached or not 确认文件是否已缓存

var CACHE_NAME = 'my-site-cache-v1';
var urlsToCache = [
  '/',
  '/styles/main.css',
  '/script/main.js'
];

self.addEventListener('install', function(event) {
  // Perform install steps
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(function(cache) {
        console.log('Opened cache');
        return cache.addAll(urlsToCache);
      })
  );
});

Here you can see we call caches.open with our desired cache name, after this we call cache.addAll and pass in our array of files. This is a chain of promises (caches.open and cache.addAll). event.waitUntil takes a promise and uses it to know how long installation takes, and whether it succeeded.

在这里你能看到caches.ope方法传入一个缓存名称的参数 ,之后我们回调cache.addAll 并传入文件数组,这是promises链式 (caches.open and cache.addAll)。event.waitUntil需要promise进行异步调用,引文它不知道安装需要多长时间才能安装成功

If all the files are successfully cached, then the service worker will be installed. If any of the files fail to download, then the install step will fail. This allows you to rely on having all the assets that you defined, but does mean you need to be careful with the list of files you decide to cache in the install step. Defining a long list of files will increase the chance that one file may fail to cache, leading to your service worker not getting installed.

如果全部文件已经缓存成功,service workser说明安装已经完成,如果其中任何的文件下载失败,安装步骤将会失败,尽管允许你定义这些资源,但是定义缓存文件越多失败几率就越高

How to Cache and Return Requests

怎么从缓存响应请求

Now that you’ve installed a service worker, you probably want to return one of your cached responses right?

现在我们已经完成service worker的安装,你大概还想知道怎么样缓存响应返回的内容

After a service worker is installed and the user navigates to a different page or refreshes, the service worker will begin to receive fetch events, an example of which is below.

Service Worker已经安装,用户刷新页面,Service Worker将会接收fetch事件

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.match(event.request)
      .then(function(response) {
        // Cache hit - return response
        if (response) {
          return response;
        }

        return fetch(event.request);
      }
    )
  );
});

Here we’ve defined our fetch event and with in the event.respondWith, we pass in a promise from caches.match. caches.match will look at the request and find any cached results from any of the caches your service worker created.

我们fetch事件里面定义event.respondWith传入caches.match参数,caches.match查看请求能否找到缓存资源

If we have a matching response, we return the cached value, otherwise we return the result of a call to fetch, which will make a network request and return the data if anything can be retrieved from the network. This is a simple example and uses any cached assets we cached during the install step.

如果匹配到响应的缓存资源将直接返回资源,如果没有将回调fetch请求网络资源,当一个网络请求并且检索网络返回数据,这是一个简单使用缓存的示例

If we wanted to cache new requests cumulatively, we can do so by handling the response of the fetch request and then adding it to the cache, like below.

如果我们想缓存新的请求,我们可以处理请求响应添加到缓存中

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.match(event.request)
      .then(function(response) {
        // Cache hit - return response
        if (response) {
          return response;
        }

        // IMPORTANT: Clone the request. A request is a stream and
        // can only be consumed once. Since we are consuming this
        // once by cache and once by the browser for fetch, we need
        // to clone the response
        var fetchRequest = event.request.clone();

        return fetch(fetchRequest).then(
          function(response) {
            // Check if we received a valid response
            if(!response || response.status !== 200 || response.type !== 'basic') {
              return response;
            }

            // IMPORTANT: Clone the response. A response is a stream
            // and because we want the browser to consume the response
            // as well as the cache consuming the response, we need
            // to clone it so we have 2 stream.
            var responseToCache = response.clone();

            caches.open(CACHE_NAME)
              .then(function(cache) {
                cache.put(event.request, responseToCache);
              });

            return response;
          }
        );
      })
    );
});

What we are doing is this:

  1. Add a callback to .then on the fetch request 添加一个回调.then在fetch请求中
  2. Once we get a response, we perform the following checks 每次获取相应我们都会检查
    1.Ensure the response is valid. 确定响应是否有效
    2.Check the status is 200 on the response. 在响应中检查状态码是否是200
    3.Make sure the response type is basic, which indicates that it’s a request from our origin. This means that requests to third party assets aren’t cached as well. 确认响应是否是基本类型,
  3. If we pass the checks, we clone the response. The reason for this is that because the response is a Stream, the body can only be consumed once. Since we want to return the response for the browser to use, as well as pass it to the cache to use, we need to clone it so we can send one to the browser and one to the cache.
  4. 如果我们通过检查,就复制的响应。这样做的原因是,由于response响应是一个流,只能使用一次,我们希望在到达浏览器之前使用response,这样就可以将该信息发送给浏览器和缓存

Learn More

jakearchibald

Thank

Matt Gaunt