实现PWA项目

388 阅读4分钟

认识PWA

虽然PWA在国内被小程序打压的没什么存在感,但是,本着技多不压身的原则了解学习一下PWA。

  • 认识:PWA渐进式Web应用【Progressive Web Apps】, Google推广
  • 特点:无需安装、更轻量、不占用大量空间,只需要一款支持 PWA 应用的浏览器,具备跨平台使用的特性
  • 优势:离线缓存功能,消息推送功能,URL分享功能,快捷添加功能,搜索引擎发现,不限屏幕大小

准备

npm install --global http-server     // 安装http-server
// 准备一个文件夹 【 PWA 】
const file = {
   html: 'index.html'  // 创建一个 Html 文件 
   css: 'main.css'     // 创建一个 css 文件
   json: 'manifest.json'  // 创建一个 json 文件
   js: 'sw.js'           // 创建一个 js 文件
   image: 'img.png/jpg'  // 创建图片 大小120*120 { 也可以创建图片文件夹 img }
}

html文件

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Hello PWA</title>
  <link rel="stylesheet" href="main.css">        
  <!-- 引入 manifest.json -->
  <link rel="manifest" href="manifest.json">
</head>
<body>
  <h3>Hello PWA</h3>
  <button id="notifications">点击提示通知</button>
</body>
<script>
    // 添加注册 Service Worker   注册完成后,sw.js 文件会自动下载,然后安装,最后激活。
  if (navigator.serviceWorker != null) {
    navigator.serviceWorker.register('sw.js').then(function (registartion) {
      console.log('支持sw:', registartion.scope)
    })
  }
 
// 通知
// 授权的结果有三种:default:用户没有做出选择的时候,授权结果会返回defalut       granted:用户已授权      denied:用户拒绝授权
    var button = document.getElementById("notifications");
    button.addEventListener('click', function (e) {
      Notification.requestPermission().then(function (result) {
        console.log('[Service Worker] 用户授权', result)
        if (result === 'granted') {
          randomNotification();
        }
      });
    });

    let settime = null
    function randomNotification() {
      var randomItem = Math.floor(Math.random() * 2);
      var title = '搜索';
      var content = `创建通知测试.`;
      var image = `img/image${randomItem + 1}.png`;
      var options = {
        body: content,
        icon: image
      }
      var notification = new Notification(title, options);
    }
</script>
</html>
Service Workers 实现了如何正确缓存网站资源并使其在用户设备离线时可用,还提供处理通知,在单独的线程上执行繁重的计算等。它运行在页面的 JavaScript 主线程独立的线程上,并且对 DOM 结构没有任何的访问权限。API 是非阻塞的,并且可以在不同的上下文之间发送和接收信息。
Service workers 可以控制网络请求,修改网络请求,返回缓存的自定义响应,或合成响应。
<!--特点:->
在页面中注册并安装成功后,运行于浏览器后台,不受页面刷新的影响,可以监听和拦截作用域范围内所有页面的 HTTP 请求。
网站必须使用 HTTPS。除了使用本地开发环境调试时(如域名使用 localhost)。
控制打开的作用域范围下所有的页面请求。
单独的作用域范围,单独的运行环境和执行线程。
不能操作页面 DOM,可以通过事件机制来处理。
事件驱动型服务线程

manifest.json 文件

{
  "name": "一个PWA示例",          // 网站应用的全名
  "short_name": "PWA示例",        // 用户主屏幕上的应用名字
  "start_url": "/index.html",     // 启动应用的网址
  "display": "standalone",        // 应用的显示方式;可以是全屏,独立,最小UI或者浏览器
  "background_color": "#fff",	  // 背景色,用于安装程序时和显示启动画面时
  "theme_color": "#3eaf7c",       // 浏览器的地址栏等 UI 元素的颜色
  "description": "Progressive Web App Demo",  // 应用的描述
  "icons": [                       // 主屏幕的图标
    {                              // 至少包含一个正方形
      "src": "/img1.jpg",
      "sizes": "120x120",
      "type": "image/png"
    },
    {                              // 提供不小于144像素的正方形
      "src": "/img2.jpg",
      "sizes": "521x521",
      "type": "image/png"      
    }
  ]
}

sw.js文件


let cacheName = 'pwa-sample3';
let cacheList = [
  '/',
  'index.html',
  'main.css',
  'img/image1.png',
  'img/image2.png',
  'img/image3.png'
];

// 借助 SW 注册完成安装 SW 时,抓取资源写入缓存中。使用方法 self.skipWaiting( ) ,为了在页面更新的过程当中,新的 SW 脚本能够立刻激活和生效。
self.addEventListener('install', function(e) {
  console.log('[Service Worker] Install');
  e.waitUntil(
    // 开启一个cache,得到一个cache对象    cache对象就可以存储资源
    caches.open(cacheName).then(function(cache) {
      console.log('[Service Worker] Caching all: app shell and content');
      return cache.addAll(cacheList);
    }).then(() => self.skipWaiting())
  );
});

// 更新静态资源,缓存的资源会跟随着版本的更新会过期的,所以会根据缓存的字符串名称清除旧缓存。在新安装的 SW 中通过调用 self.clients.claim( ) 取得页面的控制权,这样之后打开页面都会使用版本更新的缓存。而旧的 Service Worker 仍然会正确的运行,直到没有任何页面使用到它为止,这时候新的 Service Worker 将会被激活,然后接管所有的页面。
self.addEventListener('activate', async e => {
  const keys = await caches.keys()
  keys.forEach(key => {
    console.log('[Service Worker] activate',key);
    if (key !== cacheName) {
      caches.delete(key)
    }
  })
  await self.clients.claim()
})

// 处理动态资源
// fetch事件会在请求发送时触发
// 处理动态缓存,我们监听 fetch 事件,在 caches 中去 match 事件的 request ,如果 response 不为空的话就返回 response ,最后返回 fetch 请求,在 fetch 事件中我们可以手动生成 response 返回给页面。
self.addEventListener('fetch', e => {
  e.respondWith(networkFirst(e.request))
})

// 网络优先
async function networkFirst(req) {
  try {
    // 先从网络读取最新的资源
    const fresh = await fetch(req)
    console.log('[Service Worker] 网络读取');
    return fresh
  } catch (error) {
    // 去缓存中读取
    const cache = await caches.open(cacheName)
    const cached = await cache.match(req)
    console.log('[Service Worker] 离线读取');
    return cached
  }
}

// 缓存优先
async function cacheFirst (req) {
  // 先从缓存拿,拿不到再从网络中拿
  const cache = await caches.open(cacheName)
  const cached = await cache.match(req)
  if (cached) {
    return cached
  } else {
    const fresh = await fetch(req)
    // 网络中拿到的数组保存到缓存中
    cache.put(req, fresh.clone())
    return fresh
  }
}

使用 http-server -c-1启动项目