一、service worker是什么?

二、service worker的作用?

1.离线缓存(重点)2.消息推送(重点)3.后台数据同步4.响应来自其它源的资源请求,5.集中接收计算成本高的数据更新,比如地理位置和陀螺仪信息,这样多个页面就可以利6.用同一组数据7.在客户端进行CoffeeScript,LESS,CJS/AMD等模块编译和依赖管理(用于开发目的)8.后台服务钩子9.自定义模板用于特定URL模式10.性能增强,比如预取用户可能需要的资源,比如相册中的后面数张图片

三、service worker的局限

1、https: Service Worker必须是https协议的,但本地环境下http://localhost或者http://127.0.0.1也可以的。2、浏览器兼容性:

四、service worker的调试

1、chrome://serviceworker-internals

2、网页中Application

五、service worker的生命周期

Service Worker生命周期的反应: installing → installed → activating → activated'install'用来缓存文件,'activate'用来缓存更新

六、service worker的用法

1、html中

    if ('serviceWorker' in navigator) {
       // 开始注册service workers
       navigator.serviceWorker.register('./sw-demo-cache.js', {
           scope: './'
       }).then(function (registration) {
           console.log('注册成功');
           var serviceWorker;
           if (registration.installing) {
               serviceWorker = registration.installing;
               console.log('安装installing');
           } else if (registration.waiting) {
               serviceWorker = registration.waiting;
               console.log('等待waiting');
           } else if (registration.active) {
               serviceWorker = registration.active;
               console.log('激活active');
           }
           console.log('=>serviceWorker:', serviceWorker);
           if (serviceWorker) {
               console.log(serviceWorker.state);
               serviceWorker.addEventListener('statechange', function (e) {
                   console.log(' 状态变化为', e.target.state);
               });
                // 创建信道
               var channel = new MessageChannel();
               // port1留给自己
               channel.port1.onmessage = e => {
                   console.log('main thread receive message...');
                   console.log(e);
               }
               console.log('给对方', window.RES_MAP);
               // port2给对方
               serviceWorker.postMessage(window.RES_MAP, [channel.port1]);
               serviceWorker.addEventListener('statechange', function (e) {
                   // logState(e.target.state);
               });
           }
       }).catch(function (error) {
           console.log('注册没有成功');
       });
   } else {
       console.log('不支持');
   }

2、引进sw-demo-cache.js

// sw
self.addEventListener('message', ev => {
  console.log('sw receive message..');
  console.log(ev);
  fileMap = ev.data.RES_MAP;
  var arr1 = [].slice.call(fileMap); // ['a', 'b', 'c']
  // 取main thread传来的port2
  ev.ports[0].postMessage('Hi, hello too');
});

// var fs = require('fs');
// console.log(fs);
// 缓存
self.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open(VERSION).then(function(cache) {
      return cache.addAll([
        './index.html',
      ]);
    })
  );
});

// 缓存更新
self.addEventListener('activate', function(event) {
  console.log('two now ready to handle fetches!');
  event.waitUntil(
    caches.keys().then(function(cacheNames) {
      return Promise.all(
        cacheNames.map(function(cacheName) {
          console.log('cacheName:', cacheName);
          // 如果当前版本和缓存版本不一致
          if (cacheName !== VERSION) {
            return caches.delete(cacheName);
          }
        })
      );
    })
  );
});

// 捕获请求并返回缓存数据
self.addEventListener('fetch', function (event) {
  try{
    event.respondWith(
        caches.match(event.request).then(function(res){
            if(res){
                return res;
            }
            requestBackend(event);
        })
    )
  } catch {
    console.log(event);
  }
});

function requestBackend(event){
  var url = event.request.clone();
  return fetch(url).then(function(res){
      //if not a valid response send the error
      if(!res || res.status !== 200 || res.type !== 'basic'){
          return res;
      }
      var response = res.clone();
      console.log('VERSION:', VERSION);
      caches.open(VERSION).then(function(cache){
          cache.put(event.request, response);
      });

      return res;
  })
}

3、webapck中获取文件目录引入第三个模块glob,递归获取打包后的文件目录

exports.resMap = function () {
    var entryFiles = glob.sync(PAGE_PATH + '/*/*.js')
    var map = {}
    entryFiles.forEach((filePath) => {
        var filename = filePath.substring(filePath.lastIndexOf('\/') + 1, filePath.lastIndexOf('.'))
        map[filename] = filePath;
    })
    var entryFiles2 = glob.sync(PAGE_PATH2 + '/*')
    var map2 = {}
    findPath(entryFiles2, map2);
    console.log('map2', map2);
    return map2;
};


function findPath(entryFiles2, map2) {
    entryFiles2.forEach(filePath => {
        var filename = filePath.substring(filePath.lastIndexOf('/') + 1, filePath.lastIndexOf('.'));
        if (filePath.indexOf('.') <= 0) {
            let pathRes = path.resolve(__dirname, filePath);
            let files = glob.sync(pathRes + '/*');
            findPath(files, map2);
            map2[filename] = filePath;
        }
        map2[filename] = filePath;
    });
}

4、导出目录通过webpack的DefinePlugin插件,导出上步获取的目录5、web和service worker的通信通过postMessage实现web和service worker间的通信

             // 创建信道
                var channel = new MessageChannel();
                // port1留给自己
                channel.port1.onmessage = e => {
                    console.log('main thread receive message...');
                    console.log(e);
                }
                console.log('给对方', window.RES_MAP);
                // port2给对方
                serviceWorker.postMessage(window.RES_MAP, [channel.port1]);
                serviceWorker.addEventListener('statechange', function (e) {
                    // logState(e.target.state);
                });
// sw
self.addEventListener('message', ev => {
  console.log('sw receive message..');
  console.log(ev);
  fileMap = ev.data.RES_MAP;
  var arr1 = [].slice.call(fileMap); // ['a', 'b', 'c']
  // 取main thread传来的port2
  ev.ports[0].postMessage('Hi, hello too');
});

小结

1、service worker让离线缓存成为可能,offline情况下也可以访问页面。然而销毁比较困难,更新会有问题。目前只能chrome://serviceworker-internals手动销毁,还待研究。

参考:借助Service Worker和cacheStorage缓存及离线开发Service Worker 简介

01-04 13:20