PWA技术

PWA

什么是PWA

webapp用户体验差(不能离线访问),用户粘性低(无法保存入口),pwa就是为了解决这一系列问题(Progressive Web Apps),让webapp具有快速,可靠,安全等特点

PWA一系列用到的技术

  • Web App Manifest
  • Service Worker
  • Push Api & Notification Api
  • App Shell & App Skeleton

Web App Manifest

将网站添加到桌面、更类似native的体验

Web App Manifest设置

<link rel="manifest" href="/manifest.json">
{
    "name":"珠峰课堂", // 应用名称  
    "short_name":"课堂", // 桌面应用的名称  ✓
    "display":"standalone", // fullScreen (standalone) minimal-ui browser ✓
    "start_url":"", // 打开时的网址  ✓
    "icons":[], // 设置桌面图片 icon图标 修改图标需要重新添加到桌面icons:[{src,sizes,type}]
    "background_color":"#aaa", // 启动画面颜色
    "theme_color":"#aaa" // 状态栏的颜色
}

ios meta/link 私有属性设置

图标icon
<link rel="apple-touch-icon" href="apple-touch-icon-iphone.png"/>
添加到主屏后的标题 和 short_name一致
<meta name="apple-mobile-web-app-title" content="标题"> 
隐藏safari地址栏 standalone模式下默认隐藏
<meta name="apple-mobile-web-app-capable" content="yes" /> 
设置状态栏颜色
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> 

横幅安装:用户在浏览器中访问至少两次,两次访问间隔至少时间为五分钟(safari不支持横幅)

Service Worker

为了提升用户体验

Service Worker特点:

  • 不能访问/操作dom

  • 会自动休眠,不会随浏览器关闭所失效(必须手动卸载)

  • 离线缓存内容开发者可控

  • 必须在https或者localhost下使用

  • 所有的api都基于promise

  • 安装( installing ):这个状态发生在 Service Worker 注册之后,表示开始安装,触发 install 事件回调指定一些静态资源进行离线缓存。

  • 安装后( installed ):Service Worker 已经完成了安装,并且等待其他的 Service Worker 线程被关闭。

  • 激活( activating ):在这个状态下没有被其他的 Service Worker 控制的客户端,允许当前的 worker 完成安装,并且清除了其他的 worker 以及关联缓存的旧缓存资源,等待新的 Service Worker 线程被激活。

  • 激活后( activated ):在这个状态会处理 activate 事件回调 (提供了更新缓存策略的机会)。并可以处理功能性的事件 fetch (请求)、sync (后台同步)、push (推送)。

  • 废弃状态 ( redundant ):这个状态表示一个 Service Worker 的生命周期结束。

serviceWorker中的方法

  • self.skipWaiting():表示强制当前处在 waiting 状态的 Service Worker 进入 activate 状态
  • event.waitUntil():传入一个 Promise 为参数,等到该 Promise 为 resolve 状态为止。
  • self.clients.claim():在 activate 事件回调中执行该方法表示取得页面的控制权, 这样之后打开页面都会使用版本更新的缓存。旧的 Service Worker 脚本不再控制着页面,之后会被停止。

实现浏览器离线缓存的功能

  • 注册缓存

    window.addEventListener('load',function(){
      // 页面加载完成后 注册serviceWorker
      if('serviceWorker' in navigator){
          navigator.serviceWorker.register('./sw.js').then(registeration=>{
              console.log(registeration.scope);
          });
          navigator.serviceWorker.addEventListener('controllerchange',()=>{
              console.log('change')
          })
      }
      if(!navigator.onLine){
          window.addEventListener('online',()=>{
              console.log('更新');
          })
      }
    });
    
  • 离线缓存 应用cache缓存请求

// self 当前线程中的this
// 拦截用户发送的所有请求
let CACHE_NAME = `cache_version_` + 81;
let CACHAE_LIST = [
    '/',
    '/index.css',
    '/index.html',
    'main.js',
    '/getImage'
];
// 独立的线程 可以使用fetch 但是不能使用ajax
// 获取数据后进行缓存
function fetchAndSave(req){
    return fetch(req).then(res=>{ // res 是流 
        // 做缓存操作
        let r = res.clone();// res必须clone,因为使用一次就销毁
        caches.open(CACHE_NAME).then(cache=>cache.put(req,r));
        return res;
    })
}
self.addEventListener('fetch', e => {
    // 用相应来替换 如果获取不到才用缓存
    let url = new URL(e.request.url);
    if(url.origin !== self.origin){
        return;
    }
    if(e.request.url.includes('/getImage')){ // 调用了接口
        // 如果遇到了接口  更新缓存
        e.respondWith(
            fetchAndSave(e.request).catch(err=>{
                //  如果没网 在缓存中 匹配结果 返回请求
                return caches.match(e.request);
            })
        )
        return;
    }
    e.respondWith(
        fetch(e.request).catch(err=>{
            //  如果没网 在缓存中 匹配结果 返回请求
            return caches.match(e.request);
        })
    )
}); // 用缓存替换

// serviceWorker安装的阶段
function preCache() {
    // 开启缓存空间
    return caches.open(CACHE_NAME).then(cache => {
        return cache.addAll(CACHAE_LIST);
    })
}
self.addEventListener('install', (e) => {
    // 安装的过程中需要缓存 手动skipWaiting
    e.waitUntil(
        preCache().then(skipWaiting) 
    )
});
function clearCache() {
    return caches.keys().then(keys => {
        return Promise.all(keys.map(key => {
            if (key !== CACHE_NAME) {
                return caches.delete(key);
            }
        }))
    })
}
// 激活当前serviceWorker
self.addEventListener('activate', (e) => {
    e.waitUntil(
        Promise.all([
            clearCache(),
            self.clients.claim() // 立即使serviceWorker生效
        ])
    )
})

在vue中使用pwa

https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-pwa

vue create pwa-project
npm run build // 才具备pwa效果

vue-cli3.0配置pwa

在public目录下可以更改manifest配置文件

module.exports = {
  pwa: {
    name: 'My App',
    themeColor: '#f2f2f2',
    msTileColor: '#aaaaa',
    appleMobileWebAppCapable: 'yes',
    appleMobileWebAppStatusBarStyle: 'black',

    workboxPluginMode: 'InjectManifest',
    workboxOptions: {
      // swSrc is required in InjectManifest mode.
      // 建立dev目录,放置sw.js 内容如下
       swSrc: 'dev/sw.js',
    }
  }
}

需要更改registerServiceWorker文件 更改为sw.js

// 设置缓存前缀
workbox.core.setCacheNameDetails({prefix: "pwa-project"});
// 设置预缓存列表
self.__precacheManifest = [].concat(self.__precacheManifest || []);
workbox.precaching.suppressWarnings();
// 增加缓存列表策略 
workbox.precaching.precacheAndRoute(self.__precacheManifest, {});

基于workbox 缓存工具包 https://developers.google.com/web/tools/workbox

  • 内置manifest.json
  • 内置serviceWorker
  • 内置了缓存策略
    • cachefirst 缓存优先
    • cacheonly 仅缓存
    • networkfirst 网络优先
    • networkonly 仅网络
    • StaleWhileRevalidate 从缓存取,用网络数据更新缓存

增加缓存策略

workbox.routing.registerRoute(
    function(obj){ 
        // 包涵api的就缓存下来
        // 返回true则缓存
        return obj.url.href.includes('/user')
    },
    workbox.strategies.staleWhileRevalidate()
);

app-skeleton

配置webpack插件 vue-skeleton-webpack-plugin

const SkeletonWebpackPlugin = require('vue-skeleton-webpack-plugin');

单页骨架屏幕

// skeleton.js
import Vue from 'vue';
import Skeleton from './Skeleton.vue';
export default new Vue({
    components: {
        Skeleton:Skeleton
    },
    template: `
        <Skeleton></Skeleton>    
    `
});

在vue.config.js中配置一条configureWebpack规则

plugins: [
    new SkeletonWebpackPlugin({
        webpackConfig: {
            entry: {
                app: resolve('./src/entry-skeleton.js')
            }
        }
    })
]

多页骨架屏幕

// skeleton.js
import Vue from 'vue';
import Skeleton1 from './Skeleton1';
import Skeleton2 from './Skeleton2';

export default new Vue({
    components: {
        Skeleton1,
        Skeleton2
    },
    template: `
        <div>
            <skeleton1 id="skeleton1" style="display:none"/>
            <skeleton2 id="skeleton2" style="display:none"/>
        </div>
    `
});

在vue.config.js中配置一条configureWebpack规则


new SkeletonWebpackPlugin({
    webpackConfig: {
        entry: {
            app: path.join(__dirname, './src/skeleton.js'),
        },
    },
    router: {
        mode: 'history',
        routes: [
            {
                path: '/',
                skeletonId: 'skeleton1'
            },
            {
                path: '/about',
                skeletonId: 'skeleton2'
            },
        ]
    },
    minimize: true,
    quiet: true,
})

实现骨架屏插件

class MyPlugin {
    // compiler代表的是当前整个webpack打包对象
    apply(compiler) {
        // compilation代表当前打包本次打包的
        compiler.plugin('compilation', (compilation) => {
            compilation.plugin(
                'html-webpack-plugin-before-html-processing',
                (data) => {
                    data.html = data.html.replace(`<div id="app"></div>`, `
                        <div id="app">
                            <div id="home" style="display:none">首页 骨架屏</div>
                            <div id="about" style="display:none">about页面骨架屏</div>
                        </div>
                        <script>
                            if(window.hash == '#/about' ||  location.pathname=='/about'){
                                document.getElementById('about').style.display="block"
                            }else{
                                document.getElementById('home').style.display="block"
                            }
                        </script>
                    `);
                    return data;
                }
            )
        });
    }
}

// 在configureWebpack plugin:[new MyPlugin()]

vue的预渲染插件

npm install prerender-spa-plugin 
const PrerenderSPAPlugin = require('prerender-spa-plugin')

plugins: [
    new PrerenderSPAPlugin({
        staticDir: path.join(__dirname, 'dist'),
        routes: [ '/', '/about',],
    })
]

Notification & Push Api

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值