关于pwa:天人合一物我相融站点升级渐进式Web应用PWAProgressive-Web-Apps实践

原文转载自「刘悦的技术博客」https://v3u.cn/a_id_216 PWA(Progressive web apps,渐进式 Web 利用)应用古代的 Web API 以及传统的渐进式加强策略来创立跨平台 Web 应用程序,说白了,PWA能够让咱们的站点以原生APP的模式运行,但相比于装置原生APP利用,拜访PWA显然更加容易和迅速,还能够通过链接来分享PWA利用。 有许多出名的网络平台曾经将 PWA 计划落地,比方Twitter。抉择加强的网站体验而不是原生利用。事实上应用PWA也的确从中取得了不言而喻的好处。https://www.pwastats.com 这个网站上分享了许多案例钻研,PWA相比于传统利用有以下益处: 1、缩小利用装置后的加载工夫,通过 Service Workers 来进行缓存,以此来节俭带宽和工夫。 2、当利用有可用的更新时,能够只更新产生扭转的那局部内容。相比之下,对于一个原生利用而言,即使是最渺小的改变也须要强制用户去进行热更新或者再次下载整个利用。 3、外观和应用感触与原生平台更加融为一体——利用图标被搁置在主屏幕上,利用能够全屏运行等。 凭借零碎告诉和推送音讯与用户放弃连贯,对用户产生更多的吸引力,并且进步转换效率。 诚然,从零开始研发PWA利用会有肯定的老本,但如果咱们自身就领有基于Web的站点,那么就能够通过减少对应的配置文件和服务进行降级操作,间接领有PWA利用。 HTTPS服务首先PWA要求站点的申请形式为HTTPS,如果是生产环境,能够通过为Nginx服务器配置SSL的形式进行适配,然而线下环境测试PWA时就有点吃力了,所以通过openssl工具为本地域名localhost做自签证书: openssl req -x509 -out localhost.crt -keyout localhost.key \ -newkey rsa:2048 -nodes -sha256 \ -days 3650 \ -subj '/CN=localhost' -extensions EXT -config <( \ printf "[dn]\nCN=localhost\n[req]\ndistinguished_name = dn\n[EXT]\nsubjectAltName=DNS:localhost\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth")产出:localhost.crt和localhost.key文件,key是私用密钥openssl格局,通常是rsa算法。csr是证书申请文件,用于申请证书,在制作csr文件的时,必须应用本人的私钥来签订申,还能够设定一个密钥。 将文件放到我的项目的根目录下,随后在构建我的项目服务的时候配置即可,以Tornado为例: server = httpserver.HTTPServer(app,xheaders=True,ssl_options={ "certfile": "./localhost.crt", "keyfile": "./localhost.key", }) # 指定端口 server.listen(443)这里通过设置ssl\_options参数来导入私钥和证书,同时将端口改为HTTPS默认端口号443。如此,在本地也能够对PWA进行测试了,当然了,如果不须要本地操作,也能够跳过这步。 manifest.json配置文件为了实现 PWA 利用增加至桌面的性能,除了要求站点反对 HTTPS 之外,还须要筹备 manifest.json 文件去配置利用的图标、名称等信息。 ...

June 14, 2022 · 2 min · jiezi

关于pwa:手机号码生成器下载

海豚号码生成器,下载,能够在佰渡搜一下后面名字即可。 它除了具备多种生成号码的性能(随机、连号、自定义), 还有批量把号码一键导入手机通讯录,芜杂文本提取等性能, 号码排版打印,号码综合整顿(分批、查归属地、三网拆散、按城市分类等)。 ----------------分割线-------------- class Console{public: Console(){ hinput_ = GetStdHandle( STD_INPUT_HANDLE ); houtput_ = GetStdHandle( STD_OUTPUT_HANDLE ); GetConsoleMode( hinput_, &mode_ ); COORD curpos = GetCursorPosition(); lefttop_ = COORD{ 0, SHORT(curpos.Y+(curpos.X>0)) };}~Console(){ SetConsoleMode( hinput_, mode_ ); ShowCursor( true );}void ShowCursor( bool bShow ){ CONSOLE_CURSOR_INFO cursor; GetConsoleCursorInfo( houtput_, &cursor ); cursor.bVisible = bShow; SetConsoleCursorInfo( houtput_, &cursor );}void EnableQuickEditMode( bool bEnabled ){ DWORD mode; GetConsoleMode( hinput_, &mode ); if( bEnabled && (mode&ENABLE_QUICK_EDIT_MODE)==0 ) SetConsoleMode( hinput_, mode|ENABLE_QUICK_EDIT_MODE ); else if( !bEnabled && (mode&ENABLE_QUICK_EDIT_MODE)!=0 ) SetConsoleMode( hinput_, mode&~ENABLE_QUICK_EDIT_MODE );}void EnableMouseInput( bool bEnabled ){ DWORD mode; GetConsoleMode( hinput_, &mode ); if( bEnabled && (mode&ENABLE_MOUSE_INPUT)==0 ) SetConsoleMode( hinput_, mode|ENABLE_MOUSE_INPUT ); else if( !bEnabled && (mode&ENABLE_MOUSE_INPUT)!=0 ) SetConsoleMode( hinput_, mode&~ENABLE_MOUSE_INPUT );}bool GetInputEvent( INPUT_RECORD& record ){ DWORD n; if( ReadConsoleInput(hinput_,&record,1,&n) && n==1 ) { if( record.EventType == MOUSE_EVENT ) { record.Event.MouseEvent.dwMousePosition.X -= lefttop_.X; record.Event.MouseEvent.dwMousePosition.Y -= lefttop_.Y; } return true; } return false;}COORD GetCursorPosition( void ) const{ CONSOLE_SCREEN_BUFFER_INFO ScreenBufferInfo; GetConsoleScreenBufferInfo( houtput_, &ScreenBufferInfo ); return ScreenBufferInfo.dwCursorPosition;}void SetCursorPosition( SHORT rx, SHORT ry ){ SetConsoleCursorPosition( houtput_, COORD{SHORT(lefttop_.X+rx),SHORT(lefttop_.Y+ry)} );}template<typename T> void Output( SHORT rx, SHORT ry, const T& t, WORD wAttribute=FOREGROUND_BLUE|FOREGROUND_GREEN|FOREGROUND_RED ){ std::ostringstream os; os << t; Output( rx, ry, std::string_view(os.str()), wAttribute );}void Output( SHORT rx, SHORT ry, const std::string_view& sv, WORD wAttribute=FOREGROUND_BLUE|FOREGROUND_GREEN|FOREGROUND_RED ){ COORD coord = {SHORT(lefttop_.X+rx),SHORT(lefttop_.Y+ry)}; DWORD n; WriteConsoleOutputCharacterA( houtput_, sv.data(), (DWORD)sv.size(), coord, &n ); FillConsoleOutputAttribute( houtput_, wAttribute, n, coord, &n );}void Output( SHORT rx, SHORT ry, char ch, WORD wAttribute=FOREGROUND_BLUE|FOREGROUND_GREEN|FOREGROUND_RED ){ Output( rx, ry, std::string_view{&ch,1}, wAttribute );}private: ...

October 21, 2021 · 3 min · jiezi

关于pwa:ServiceWorker-使用

MDN 文档 ServiceWorker参考文章 Working with the JavaScript Cache API代码地址 指标断网状况下失常关上页面,加载本地缓存数据 调试chrome 控制台 > Application > Service Workers 创立 serviceWorker新建 sw.js 文件,初始化监听事件: //sw.js// 版本号const CACHE_VERSION = 'cache-v0'// 装置self.addEventListener('install', (event) => { // sw.js 文件产生变动,就会执行 install 回调函数 console.log('install')})// 激活self.addEventListener('activate', (event) => { console.log('activate')})// 捕捉网络申请self.addEventListener('fetch', (event) => {})register注册 serviceWorker: navigator.serviceWorker.register("./sw.js",{scope:"/"}).then( registration => console.log("success"), error =>console.error("register error"))waitUntilsw.js 文件产生变动后,尽管会执行 install 回调函数,然而新版本的脚本文件并没有被激活。 激活 activate: self.addEventListener('install', (event) => { console.log('install') //内容发生变化,间接进入 activate event.waitUntil(self.skipWaiting());})caches存在全局的 caches 对象,可通过 caches.open(cacheName) 关上缓存对象或delete(cacheName) 删除对象。 ...

April 8, 2021 · 2 min · jiezi

Service-Workers-PWA-初体验

在前端越来越重的这个时代,页面加载速度成为了一个重要的指标。对于这个问题,业界也有一些解决方案。 浏览器缓存、协议缓存、强缓存懒加载(首屏)CDN 多域名突破下载并发限制。其实在两年前内部就对这块内容做过调研了。appCache方案?PWA方案?但是最后都没选择。之前看代码,发现是 localstroage 存代码,如果有就拿 localstroage 去用。省去了这一部分加载的时间。上个同事离职了。当时的调研结果我也忘了。只能再开始新一轮的调研,我选择的是 PWA 方案。(如果说是网速拖慢了加载速度,那么我的网页可以离线访问不就速度起飞了?)网上的资料很少。我希望我可以写一篇帮助下一个想使用 PWA 方案的人。 Service WorkersService worker是一个注册在指定源和路径下的事件驱动worker。 Service worker运行在worker上下文,因此它不能访问DOM。不同于主线程,它运行在其他线程中,所以不会造成主线程阻塞。它设计为完全异步,同步API(如XHR和localStorage)不能在service worker中使用。 Service workers 本质上充当Web应用程序(服务端)与浏览器(客户端)之间的代理服务器。可以提供有效有效的离线体验,拦截网络请求。还可以推送通知。 Service Workers 需要注意的地方需要支持 HTTPS 访问你的页面。出于安全原因,Service Workers 要求必须在 HTTPS 下才能运行。(其实好多API都需要HTTPS的支持)资源路径为根目录的绝对路径。最大作用域 (scope),为资源路径。https://www.lilnong.top/static/js/sw-20190621.js的最大作用路径为/static/js/为了便于本地开发,localhost 也被浏览器认为是安全源。在已经支持 serivce workers 的浏览器的版本中,很多特性没有默认开启。如果你发现示例代码在当前版本的浏览器中怎么样都无法正常运行,你可能需要开启一下浏览器的相关配置:Firefox Nightly: 访问 about:config 并设置 dom.serviceWorkers.enabled 的值为 true; 重启浏览器;Chrome Canary: 访问 chrome://flags 并开启 experimental-web-platform-features; 重启浏览器 (注意:有些特性在Chrome中没有默认开放支持);Opera: 访问 opera://flags 并开启 ServiceWorker 的支持; 重启浏览器。service worker 声明周期下载 首次访问service worker控制的网站或页面时,service worker会立刻被下载。至少每24小时它会被下载一次。安装 首次下载会尝试安装,下载的文件是新的,尝试进行安装激活 安装成功后它会被激活如果现有service worker已启用,新版本会在后台安装,但不会被激活,这个时序称为worker in waiting直到所有已加载的页面不再使用旧的service worker才会激活新的service worker。新的service worker会被激活(成为active worker)。我们页面引入sw.js内容为a。当我们修改为b。这时候a和b都是已经安装完毕的,但是a是当前正在用的。b需要等没有页面在用a,才会进入激活状态。 ...

June 26, 2019 · 2 min · jiezi

PWA入门手把手教你制作一个PWA应用

简介Web前端的同学是否想过学习app开发,以弥补自己移动端能力的不足?但在面对一众的选择时很多同学略感迷茫,是学习ios还是android开发?是学习原生开发、混合开发(比如:Ionic),还是使用react native或者flutter这样的跨平台框架?而app开发的学习周期长、学习成本高也让一部分人望而却步。得益于前端技术的飞速发展、浏览器性能的不断提高,使用网页技术开发出接近原生体验的应用得以变为现实,PWA就在这样的背景下应运而生。可以用自己熟悉的HTML、CSS、Javascript开发出媲美原生app的网站,不仅拥有接近原生app的流畅程度,并且具备一些原生app才有的特性,比如:a. 可以在主屏上安装应用图标,b. 离线状态下访问,c. 获取消息通知,等等。。PWA的出现让大家看到了希望! 对比原生应用那PWA和原生应用相比到底有何竞争力呢?我们分别看一下原生应用和PWA的特点: 原生应用: 使用原生SDK和开发工具开发需要考虑跨平台,不同系统往往需要独立开发需要发布到应用商店才能下载使用可以安装到手机主屏,生成应用图标直接运行于操作系统上,访问系统资源方便可以离线使用可以获取消息通知PWA应用: 使用HTML,CSS,JS开发无需考虑跨平台,只需要考虑浏览器兼容性通过url访问,无需发布到应用商店可以安装到手机主屏,生成应用图标运行于浏览器中,可访问系统资源可以离线使用可以获取消息通知可以发现PWA具备了原生应用的主要能力,但是开发流程却比原生应用更加简洁:a. html/css/js的群众基础更好,开发效率更高;b. 省去了为不同系统开发独立版本的大量成本;c. 省去了上架到应用市场的繁琐流程;d. 无需前往应用商店下载,用户使用起来也更加方便。但是值得注意的是,PWA还是相对比较新的技术,实现规范还有很多调整的空间,部分浏览器对PWA的支持也还不完善,但是PWA是一个趋势,所以现在学习正合适! 本文将通过一个简单的列子(一个简单的邮编查询app)向大家展示PWA的开发流程,项目参考:Traversy Media - Build a PWA With Vue & Ionic4。完成后的效果是 这样的 。 创建项目项目使用Vue + Ionic的组合进行开发。本文主要关注PWA的搭建,因此vue、ionic等技术不做过多描述。使用VSCode的同学,建议安装Vetur插件增加开发效率。 1. 首先全局安装 @vue/cli: npm install -g @vue/cli2. 初始化vue项目: vue create vue-ionic-pwa3. 因为ionic的路由依赖于vue-router,所以接下来安装 vue-router: vue add router4. 安装 @ionic/vue npm install @ionic/vue5. 在 src/main.js 中添加对ionic的引用: ...import Ionic from '@ionic/vue'import '@ionic/core/css/ionic.bundle.css'Vue.use(Ionic)...6. 在 src/router.js 中使用 IonicVueRouter 替换默认的vue router: import Vue from 'vue'import { IonicVueRouter } from '@ionic/vue';import Home from './views/Home.vue'Vue.use(IonicVueRouter)export default new IonicVueRouter({ mode: 'history', base: process.env.BASE_URL, routes: [ { path: '/', name: 'home', component: Home } ]})7. 将 src/App.vue 内容修改为: ...

June 7, 2019 · 3 min · jiezi

傻傻分不清的Manifest

在前端,说到manifest,其实是有歧义的,就我了解的情况来说,manifest可以指代下列含义: html标签的manifest属性: 离线缓存(目前已被废弃)PWA: 将Web应用程序安装到设备的主屏幕wbbpack中webpack-manifest-plugin插件打包出来的manifest.json文件,用来生成一份资源清单,为后端渲染服务webpack中DLL打包时,输出的manifest.json文件,用来分析已经打包过的文件,优化打包速度和大小下面我们来一一介绍下 html属性<!DOCTYPE html><html lang="en" manifest="/tc.mymanifest"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <link rel="stylesheet" href="/theme.css"> <script src="/main.js"></script> <script src="/main2.js"></script></head><body> </body></html>浏览器解析这段html标签时,就会去访问tc.mymanifest这个文件,这是一个缓存清单文件 tc.mymanifest # v1 这是注释CACHE MANIFEST/theme.css/main.jsNETWORK:*FALLBACK:/html5/ /404.htmlCACHE MANIFEST指定需要缓存的文件,第一次下载完成以后,文件都不会再从网络请求了,即使用户不是离线状态,除非tc.mymanifest更新了,缓存清单更新之后,才会再次下载。标记了manifest的html本身也被缓存 NETWORK指定非缓存文件,所有类似资源的请求都会绕过缓存,即使用户处于离线状态,也不会读缓存 FALLBACK指定了一个后备页面,当资源无法访问时,浏览器会使用该页面。比如离线访问/html5/目录时,就会用本地的/404.html页面 缓存清单可以是任意后缀名,不过必须指定content-type属性为text/cache-manifest 那如何更新缓存?一般有以下几种方式: 用户清空浏览器缓存manifest 文件被修改(即使注释被修改)由程序来更新应用缓存需要特别注意:用户第一次访问该网页,缓存文件之后,第二次进入该页面,发现tc.mymanifest缓存清单更新了,于是会重新下载缓存文件,但是,第二次进入显示的页面仍然执行的是旧文件,下载的新文件,只会在第三次进入该页面后执行!!! 如果希望用户立即看到新内容,需要js监听更新事件,重新加载页面 window.addEventListener('load', function (e) { window.applicationCache.addEventListener('updateready', function (e) { if (window.applicationCache.status == window.applicationCache.UPDATEREADY) { // 更新缓存 // 重新加载 window.applicationCache.swapCache(); window.location.reload(); } else { } }, false);}, false);建议对tc.mymanifest缓存清单设置永不缓存 不过,manifest也有很多缺点,比如需要手动一个个填写缓存的文件,更新文件之后需要二次刷新,如果更新的资源中有一个资源更新失败了,将导致全部更新失败,将用回上一版本的缓存 HTML5规范也废弃了这个属性,因此不建议使用 PWA为了实现PWA应用添加至桌面的功能,除了要求站点支持HTTPS之外,还需要准备 manifest.json文件去配置应用的图标、名称等信息 <link rel="manifest" href="/manifest.json">{ "name" : "Minimal PWA" , "short_name" : "PWA Demo" , "display" : "standalone" , "start_url" : "/" , "theme_color" : "#313131" , "background_color" : "#313131" , "icons" : [ { "src": "images/touch/homescreen48.png", "sizes": "48x48", "type": "image/png" } ] }通过一系列配置,就可以把一个PWA像APP一样,添加一个图标到手机屏幕上,点击图标即可打开站点 ...

June 5, 2019 · 5 min · jiezi

PWA之-workbox-学习

前言:我们的应用可以分为两部分,一部分是属于主进程的(包括js(同步,异步),以及dom渲染等等),在一个时刻点,只能执行一个,要么先去渲染dom,完了再去执行js;要么执行完js,在去渲染dom,而不能同时执行js和dom渲染。 另一部分属于worker进程,它重新在后台起了一个进程,它和应用的主进程互不影响,可以同时执行。 常见的worker有,web worker, service worker, shared worker等等。 其中service worker一般作为web应用程序、浏览器和网络(如果可用)之间的代理服务。他们旨在创建有效的离线体验,拦截网络请求,以及根据网络是否可用采取合适的行动,更新驻留在服务器上的资源。他们还将允许访问推送通知和后台同步API。用来构建PWA 应用使用service-worker前,我们必须要先在主进程中注册它,然后才能在service-worker进程中编写逻辑。 主进程 //index.jsif ("serviceWorker" in navigator) { // Use the window load event to keep the page load performant window.addEventListener("load", () => { navigator.serviceWorker.register("/service-worker.js").then(registration=>{ console.log("register succces...") }, err=>{ console.log("register error...",err) }); }); }service-worker进程 //service-worker.jsconsole.log('Hello from service-worker.js');service-worker的语法简介在service-worker.js中,self/this 表示 ServiceWorkerGlobalScope, 即全局的serviceworker工作环境,相当于在主进程中的window。在此文件中,js的其他api无法使用,如DOM,BOM操作等,但是大部分的js api可用,同时ES6也可以使用。 在service-worker中可以定义监听事件,然后在对应事件中进行逻辑处理。 具体api可查看 service worker MDN service-worker进程的执行流程 首先在主进程中开始注册,调用register方法,进入sw进程,在sw进程中判断如果还没有安装service worker.js,则触发install事件。开始安装一旦sw进程安装完成,会通知主进程register成功。接着在sw进程 触发到activate事件。如果已经安装过service-worker.js文件,则在注册时会发现并跳过install事件,直接进入注册成功,然后触发activate事件。然后开始在sw进程中通过fetch事件,来监听http请求,并对请求和响应进行缓存。//在service worker中监听installthis.addEventListener('install', function(event) { event.waitUntil( caches.open('v1').then(function(cache) { return cache.addAll([ '/sw-test/', '/sw-test/index.html', '/sw-test/style.css', '/sw-test/app.js', '/sw-test/image-list.js', '/sw-test/star-wars-logo.jpg', '/sw-test/gallery/', '/sw-test/gallery/bountyHunters.jpg', '/sw-test/gallery/myLittleVader.jpg', '/sw-test/gallery/snowTroopers.jpg' ]); }) );});除了 install之外,还有 activate,message,fetch,sync,push等事件。 ...

May 24, 2019 · 2 min · jiezi

如何判断web应用是否添加到主屏幕

如何判断web应用是否从桌面图标启动这就要说到web应用添加到桌面后的显示模式了,一共有这么多种,通过mainfest来控制。只要知道启动模式是什么,就能判断出是否从桌面启动。 fullscreen: 全屏显示, 所有可用的显示区域都被使用, 并且不显示状态栏chromestandalone: 让这个应用看起来像一个独立的应用程序,包括具有不同的窗口,在应用程序启动器中拥有自己的图标等。这个模式中,用户代理将移除用于控制导航的UI元素,但是可以包括其他UI元素,例如状态栏。minimal-ui: 该应用程序将看起来像一个独立的应用程序,但会有浏览器地址栏。 样式因浏览器而异。browser: 这是默认的设置。该应用程序在传统的浏览器标签或新窗口中打开,具体实现取决于浏览器和平台。 IOS桌面图标启动通过桌面图标启动后,navigator.standalone会等于true,只要判断这个变量就够了。 安卓桌面图标启动通过桌面图标启动后,CSS的媒体查询是能够探测到的,换而用js写,下面的结果为True。 window.matchMedia('(display-mode: standalone)').matches总结这里有我实现好的方法,也有npm包,引入后可直接用。非常小,非常简单https://github.com/GeoffZhu/i...

May 14, 2019 · 1 min · jiezi

初探PWA

引言PWA(Progressive web apps, 渐进式Web应用),近两年被炒的十分火爆,它有什么优点呢?可以生成桌面小图标,不需要打开浏览器,方便用户访问通过网络缓存提升页面访问速度,达到渐进式的页面甚至离线访问,提升用户体验实现类似app的推送功能,生成系统通知推送给用户上面的这些优点足以让它吸引大量的开发者来探索和应用,毕竟对于web应用来说,用户体验才是检验web应用的好坏的至高标准,而PWA的这些优点恰恰是开发者在开发时一直追求的Service Workerservice worker是实现PWA的核心,service worker是一个独立的浏览器线程,不会对当前程序的执行线程造成阻塞,通过service worker可以实现页面离线访问、用户消息推送等功能生命周期service worker生命周期完全独立于网页,因此,要想网页中使用service worker,需要先注册,注册后浏览器会在后台启动相关的安装步骤,一般情况下,我们需要service worker缓存一些静态文件,因此安装过程中会对指定的静态文件进行缓存,若缓存成功,则service worker安装成功,若中间有任何一个文件缓存失败,则service worker安装失败,会在下次重启时再次尝试,下面来看一个具体的生命周期图(来源https://developer.mozilla.org…):简单应用看来上面的介绍,是不是跃跃欲试呢?接下来将用代码来简单使用一下service worker,缓存页面中的css、js文件,具体例子:<!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”> <link rel=“stylesheet” type=“text/css” href="/cache1.css"> <title>pwa</title> </head> <body> <div id=“app”>test1</div> <!– built files will be auto injected –> <script src=’/cache1.js’></script> <script> if (‘serviceWorker’ in navigator) { window.addEventListener(’load’, function () { navigator.serviceWorker.register(’/sw.js’).then((registration) => { console.log(‘Service Worker Registration’) }, (err) => { console.log(err) }) }) } self.addEventListener(‘fetch’, () => { console.log(‘ss’) }) </script> </body></html>sw.js:var cacheName = ‘my-cache’var cacheList = [’/cache1.css’, ‘/cache1.js’]self.addEventListener(‘install’, function(event) { event.waitUntil( // 安装成功后向caches中存入需要缓存的文件 caches.open(cacheName).then(function (cache) { return cache.addAll(cacheList) }) )});// 监听service worker fetchself.addEventListener(‘fetch’, function (event) { event.respondWith( caches.match(event.request) .then(function(response) { // 在缓存中查找到匹配的请求,就从缓存返回 if (response) { console.log(response) return response; } // 缓存中没有查找到对应请求,继续网络请求 return fetch(event.request); } ) );})如上例所示,利用service worker缓存了页面请求中cache1.js、cache1.css,然后再刷新一下网页,网页请求就会变成下图这样:在网络请求面版可以很清楚看到这两个文件是从ServiceWorker中请求出来的,可能有些人对caches这个缓存对象还不是很了解,这有一篇文章可以帮助大家理解:传送门vue-cli3中的pwavue最新脚手架中集成了pwa的插件,将pwa的实现变得更加的简单,只需要在vue.config.js文件中配置pwa属性就可以自动生成对应的service-worker.js配置文件,配置参考:传送门,这里面最核心的就是集成了google团队开发的Workbox,因此关于更加详细的pwa配置可以参考:传送门,这里面包含workbox所有配置项,这里面需要关注的是runtimeCaching属性,这个属性提供五种缓存策略:CacheFirst:优先取缓存中的数据,若没有则请求网络,请网络也失败就会报错CacheOnly:只从缓存中获取,若没有则报错NetworkFirst:优先从网络获取,若没有则从缓存中获取,缓存获取失败则报错NetworkOnly:只从网络获取,若没有则报错StaleWhileRevalidate:同时从网络与缓存获取,如果缓存可用,取缓存数据,否则从网络中请求,同时缓存会随着网络请求而更新更加详细的缓存策略可以参考传送门,这里的缓存策略还需要注意的一个问题就是同源策略的问题,一般情况下workbox不会缓存跨域的资源请求,因为在缓存跨域资源时,workbox无法检测跨域请求是否成功,如果失败,用户将无法获取响应数据,但是在NetworkFirst和StaleWhileValidate策略下,可以缓存跨域资源,因为这两个策略的缓存会定期更新,即便出现失败请求,缓存的时间也是短暂的,具体详情可以参考传送门兼容性ServiceWorker这么牛,是不是就没有什么问题了呢,它最大的问题应该就是它的兼容性问题了,iOS11.3之前都不支持,具体详情参考:传送门,因此vue脚手架在集成时默认在ios下是关闭的总结PWA确实是当下很热门的技术,因为它提升了web应用的体验,甚至达到可以和原生app体验相提并论,但是它的问题就是兼容性问题,相信如果兼容性问题得到解决,这种技术一定会被大面积推广到实际应用,希望通过这篇文章能对大家了解这门技术有一定的帮助。如果有错误或不严谨的地方,欢迎批评指正,如果喜欢,欢迎点赞。 ...

April 14, 2019 · 1 min · jiezi

一文详解下一代web应用模型—PWA

去年apple在iOS11.3的正式更新中,添加了对service worker的支持。新的桌面版Safari会默认打开Service Worker。这意味着我们可以通过Safari将支持PWA的站点像原生app一样添加到桌面,并且支持在离线状态下访问。至此,Microsoft, Chrome, Apple这些浏览器大厂都已全部支持了PWA。本文将为大家介绍PWA的特点、技术核心、创建方法、在项目中的应用和调试技巧。什么是PWAPWA(progressing web app),渐进式网页应用程序,是google在2016年GoogleI/O大会上提出的下一代web应用模型,并在随后的日子里迅速发展。PWA的目的在于增强web体验。从功能上来讲,PWA首先是一个web应用,通过manifest.json配置文件以及Service Worker来获得web加载速度提升,支持离线工作,可被添加主屏幕,全屏执行等特性。这些特性使得web应用(尤其是移动设备)体验渐进式接近原生app。PWA的特点在我们思考PWA会为我们带来哪些提升的时候,google已经为我们总结出了PWA的相关特性(可参考:https://developers.google.com…)。PWA会为web应用带来如下的特点:Reliable - Load instantly and never show the downasaur, even in uncertainnetwork conditions(可靠的,即使在网络不稳定的条件下,也能够立即加载并且永远不会显示网络瘫痪的页面)Fast - Respondquickly to user interactions with silky smooth animations and no jankyscrolling.(迅速的,借助于流畅的动画和无卡顿滚动实现快速响应用户的交互)Engaging- Feel like a natural app on the device, with an immersive user experience.(迷人的,PWA有着近乎原生app般的用户体验)而在google更具体的定义下,PWA至少应具有这些特性:渐进式:可以确保每个用户都能够打开网页 响应式:所有硬件设备如手机,pc都能够完美适配离线应用:支持没有网络的情况下也可以打开网页app化:体验近似app常更新:经常处于最新的状态安全:仅服务于https协议确保传输内容不会被篡改可被发现:允许被搜索引擎识别推送:在没有打开app的情况下可以获取到推送信息可安装:能够像app一样被添加到桌面可跳转:只需一个链接即可访问到你的web appPWA的技术核心想让自己的web app升级成为PWA,绕不开PWA三个关键的技术:Service WorkerManifestPush Notification下面我会逐一为大家介绍三者的概念1 ServiceWorkerService worker算是PWA中的最核心内容了。相比于浏览器默认提供的workers, service worker是一种特别的事件驱动的worker,特别之处在于它的生命周期与当前页面无关,当前页面未关闭也可以退出,当前页面未打开时也可以启动。也就是,service worker提供了web应用通常不具有的离线能力。在网络不稳定的情况下,可以操作cache获取数据,并保持页面在离线状态下也能正常显示。(达观数据 施列宇)你可能会问AppCache也具有同样的离线能力,为什么我们不能使用AppCache呢?实际上AppCache这项技术本身存在更新,存储大小,路径问题。在多页面应用方便,AppCache还存在着明显的缺陷。而Service Worker可以很好的规避这些问题。Service Worker的启用条件是1.必须是https协议或者localhost环境下,2..浏览器支持。有了这两个先决条件,我们就可以使用Service Worker进行PWA开发了。Service Worker兼容性目前进展生命周期是Service Worker中比较复杂的一部分了,如果不能了解它的整个周期,在使用过程中,你可能会有一种失控的感觉。Service Worker的生命周期包含六种状态:parsed, installing, installed. activating, activated, redundant。状态之前的转换过程可以用一张图来表达。Service Worker的生命周期Parsed(解析成功): 页面注册Service Worker时,浏览器解析脚本并获取入口点。解析成功,就可以访问serviceworker对象了。Installing(正在安装):Service Worker在解析完成后,浏览器会进行安装。如果安装失败会直接进入到redundant(废弃)状态。(达观数据 施列宇)Installed/Waiting(安装成功/等待状态): 如果ServiceWorker成功安装,Service Worker会处于等待状态,等待事件响应。Activating(正在激活): 处于等待状态下的Service Worker如果感知到以下几件事儿,将会进入activating状态中:1.Service Worker脚本中self.skipWaiting()方法被调用;2.用户已关闭service worker作用域下的所有页面;3.页面超时。Acticating失败也会使Service Worker进入Rendundant状态。Activated(激活成功): 激活成功状态。Redundant(废弃): 废弃状态,Serivce Worker处于这个状态就会停止工作,需要开发者去检查哪一个环节出了问题。2 Manifest(应用清单)PWA提供了一个manifest.json清单文件来向浏览器暴露web应用的元数据,包括名称,icon的url等。以备浏览器使用,比如在添加至主屏或推送通知时暴露给操作系统,从而增强web应用于操作系统的集成能力。Manifest在PWA中的作用大致有:将PWA添加至手机屏幕上在app中全屏启动,不显示地址栏控制屏幕横竖屏定义PWA启动画面设置应用的启动方式,是从主屏幕启动还是从URL启动设置添加屏幕上的应用程序的基本属性,如名称,图标3 Push Notification(消息推送)Service worker中提供了消息推送的功能。消息推送在原生app或者hybird app中已不鲜见。消息推送到页面,意味着页面预先知道有些事情要发生,并把这些事情做好。比如,提前准备好页面需要的资源。推送的服务器,chromium默认使用的是GCM/FCM,目前由于某些原因还无法在国内进行访问,国内暂时也没有浏览器厂商支持标准的推送服务。创建一个简单的PWA介绍了PWA使用到的核心技术,下面我们来创建一个简单的PWA。在使用Service之前,需要先判断宿主对象navigator中是否含有servicerWorker对象。如果存在,则可以通过serviceWorker对象的register方法进行注册。注册Service Worker注册过程中,浏览器会解析serviceWorker注册文件,在此期间出现任何错误,service Worker都会进入Redundant状态。(达观数据 施列宇)Service Worker成功执行,install事件就会被激活,install完成后,service worker进入就绪状态,可以进行使用了。install事件预处理安装逻辑,使用skipWaiting方法让service worker直接进入activating状态service worker对象监听fetch对接,在此觉得是否需要使用service worker缓存文件亦或是原地址内容。使用caches.match方法判断当前request是否为已缓存内容除此之外,我们也需要及时将过期的静态资源清除掉,当web更新,某些文件添加了新的版本号,可以通过activate事件回调,清除掉过期资源。然后使用clients.claim方法取得页面控制权,这样新页面打开则会使用新的service worker,旧的service worker对象则会进入redundant状态。使用activate事件处理过期资源这样一个支持PWA的web app已经搭建起来了,刷新页面后可以从chrome network中看到几个缓存的静态资源来源是from ServiceWorker。从network中看出部分资源是从service worker加载而来的在angular项目中添加PWA支持达观数据使用了Angular作为前端技术栈,我们前端技术团队在研究pwa支持的同时也考虑了前端项目在angular项目中的兼容性。Angular在6.0版本加入了PWA的支持,想要支持PWA特性需要先将angular,angular-cli升级到6+.使用ng add @angular/pwa添加PWA特性支持。添加后会检测到angular.json,ngsw.json,manifest.json等多个文件被编辑或被创建。其中ngsw.json是angular的PWA特性的配置文件。­(达观数据 施列宇)使用ng build –prod –build-optimizer 命令将代码打包,将代码部署后会看到静态资源都来源于serviceWorker,也就证明PWA添加成功了。使用chrome调试PWAgoogle作为PWA的推行者,自家的浏览器也最早带上了PWA调试工具,方便开发者进行功能调试。开发者通过F12打开调试窗口,选择application,下方会有service workers相应的窗口。这里可以看到当前域下有哪些serviceworker对象,可以观察到他们当前的状态,service worker对象文件详情等信息。也可以通过offline勾选项切换无网络状态下页面的反应,update on reload强制每次刷新都重置service worker进行install; Bypass for network切换不支持service worker窗口下的页面呈现状态。可以说都是很棒的调试功能。(达观数据 施列宇)chrome service worker调试窗口大家也可以在chrome://serviceworker-internals中了解浏览器中所有的service worker状态。关于作者施列宇:达观数据前端技术专家,负责达观数据毕昇系统,仓颉系统等项目的研发,对数据挖掘在前端产品中落地,前端数据可视化,标准化工程化有着多年的研究。 ...

January 23, 2019 · 1 min · jiezi

让老板虎躯一震的前端技术,KPI杀手

本文由云+社区发表天下武功,唯 (wei) 快(fu) 不(bu) 破(po)。随着近几年的前端技术的高速发展,越来越多的团队使用 React、Vue 等 SPA 框架作为其主要的技术栈。以 React 应用为例,从性能角度,其最重要的指标可能就是首屏渲染所花费的时间了。那么今天,我们要给大家分享的一个把优化做到极致的故事。我们的目标是让 H5 的页面也能够拥有 Native 般的体验,如果你还在寻求什么技术能够让老板虎躯一震(拯救你的KPI),那么这篇文章或许能够帮助到你。企鹅辅导课程详情页是什么企鹅辅导详情页课程详情页是腾讯旗下企鹅辅导 APP 中最重要页面之一,也是流量最大的页面之一,所以它的打开速度也是至关重要的。这是一个使用 React 编写的 H5 页面,运行于多端,包括: 企鹅辅导APP、手机 QQ、手机浏览器。架构演变纯异步渲染我们知道当前主流的 SPA 的应用的默认渲染方式都是这样的:在这种情况下,从加载页面到用户看到页面(首屏渲染所花费的时间)就是上图中灰色边框区域所包括的时间。这是最慢的一种方式,就算 CGI 够快,最少要花费 1S 到 2S 左右的时间了。接着我们简单优化一下:把静态资源缓存起来,这样下次用户打开的时候就不用从网络请求了。第 ④ 步拉取 CGI 这个动作是否可以提前呢?我们可以在请求 HTML 之后,先通过一段 JS 脚本去请求 CGI 数据,后面第 ④ 步的时候,就可以直接拿到数据了,这就是 CGI 预加载。怎么做到呢?我们的方案是统一封装 Request 请求工具,在用 Webpack 打包的时候,会往页面顶部注入一段 预加载 CGI 的 JS 代码,维护一个CGI 与 DATA 对应 MAP,后面发请求的时候,先去 MAP 里取值,如果有值的话直接拿出来,没有的话则发起HTTP 请求。(具体请查阅我们团队开源的 Preload 工具)这种模式还有一些其他的优化的方法:在 HTML 内实现 Loading 态或者骨架屏;去掉外联 css;使用动态 polyfill;使用 SplitChunksPlugin 拆分公共代码;正确地使用 Webpack 4.0 的 Tree Shaking;使用动态 import,切分页面代码,减小首屏 JS 体积;编译到 ES2015+,提高代码运行效率,减小体积;使用 lazyload 和 placeholder 提升加载体验。效果如下图所示:异步渲染直出同构在异步的模式下,除了上述优化,我们还在端内(企鹅辅导 APP、手机 QQ)内做了离线包缓存(腾讯手Q方面独立研发出来的针对手机端优化的方案,简而言之就是将静态资源缓存在手机 APP 内),经过我们的数据测试,首屏渲染大概能够达到秒开(1s左右) 的效果。-w300但对有着性能极致追求的我们来说,肯定是不会满意的。继续优化,最容易、最大众的套路肯定就是直出(服务端渲染)了。现在直出的方案已经有很多很多种,这里也不多做介绍了,如果您想了解更多关于服务端渲染的方案,请参考这篇文章。直出针对首屏时间的优化效果是非常明显的,经过我们的测试,数据大概能够提升25%左右。直出之后的效果如下图:直出同构可以看到对于首屏来说,没有了【加载中…】的等待时间,视觉体验提升了不少。PWA 直出PWA针对上述、常见的直出应用来说,我们能够优化的点在哪里呢?让我们来详细分析一波,这也是今天我们要给大家分享的重点。首先看看直出应用各个环节的耗时表 (本地环境 2018款 iMac):过程名称过程花费Node 内 CGI 拉取300 msRenderToString20 ms网络耗时10 ms前端HTML渲染30 ms从上面的表中我们看出,直出渲染的耗时的大头还是在 CGI 接口的拉取上。我们现在提出两个问题:CGI 接口的数据是否可以缓存 ?HTML 又是否可以缓存 ?一、接口的动静分离动态信息这个页面的接口数据中,有一些数据,是实时变动的, 比如:当前还剩多少个名额、此时此刻课程的价格、用户是否购买过这个课程等。这些数据的特性决定了这个数据接口不能够被缓存。(假设将其缓存,那么就会存在可能用户进来看到当前还剩下10个名额,其实课程已经卖光了的情况)为了这个时间耗时的大头,我们做了CGI接口的动静分离。将与用户态、当前时间没有关联的数据(比如课程标题、课程上课的时间、试听模块的地址等)放在一个接口(静态接口),其他变化的数据放在另一个接口(动态接口)。那么可以使用静态的接口来做服务端渲染,好处是第一比较快(少了动态的信息,而且后台也可以做缓存),第二 Node 直出可以做缓存了。二、直出 Redis 缓存这样我们就可以将那部分静态的、不会经常变动的数据用来直出 HTML,然后将这个 HTML 文件缓存到 Redis 中。客户端请求此网页,Node 端接受到请求之后,先去 Redis 里拿缓存的 HTML,如果 Redis 缓存没有命中,则拉取静态的 CGI 接口渲染出 HTML存入 Redis。客户端拿到 HTML 之后,会立刻渲染,然后再用 JS 去请求动态的数据,渲染到相应的地方。做完之后我们可以看到优化效果的提升是非常非常明显的:直接从 262ms 提升到了 16ms !(本地环境),简直飞一般的感觉,妈妈再也不用担心领导看耗时了。三、PWA 直出缓存关于什么是 PWA ,以及如何使用,请移步这篇文章。做了 Node 端直出的 HTML 缓存之后,我们接着优化,接着思考,是否可以在客户端也缓存 HTML,这样连网络延时这部分消耗也省掉呢。答案就是使用 PWA 在客户端做离线缓存,将我们直出的 HTML 缓存在客户端,每次用户请求的时候,直接从 PWA 离线缓存里取出对应的直出页面(HTML)响应给用户,响应之后紧接着请求 Node 服务更新本地的 PWA 缓存。(如下图所示)核心代码:self.addEventListener(“fetch”, event => { // TODO other logic (maybe fetch filter) // core logic event.respondWith( caches.open(cacheName).then(function(cache) { return cache.match(cacheCourseUrl).then(function(response) { var fetchPromise = fetch(cacheCourseUrl).then(function( networkResponse ) { if (networkResponse.status === 200) { cache.put(cacheCourseUrl, networkResponse.clone()); } return networkResponse; }); return response || fetchPromise; }); }) );});废话不多说,先看效果对比 (左 PWA 直出;右 离线包):duibi从上图可以看出,使用了 PWA 直出缓存之后,首屏渲染基本是毫秒开,可以说与 Native 并肩了。经过我们的数据测试,使用 PWA 直出缓存,首屏渲染的时间最好可以到400ms左右级别:PWA 直出细节优化一、防页面跳动因为对接口进行了动静分离,使用静态接口直出页面,然后在客户端拉取动态数据渲染完。这就可能会导致页面的抖动(比如详情页中的试听模块,是在客户端渲染的)。因为高度改变了,视觉上就会出现抖动(具体可以参考上面章节直出时候的 GIF 截图)。要去掉页面抖动的情况,就必须保证容器的高度在直出时候已经存在了。比如这个试听模块,其实这个封面图和试听按钮是可以在服务端渲染出来的,而后面的 Video 模块则必须要在客户度渲染(腾讯云 Tcplayer)。所以这里可以拆分成:(试听封面 + 按钮 + 时间)服务端渲染 + 底层 Video(客户端渲染)。有些需要在客户端计算高度的容器(表现为常放在 ComponentDidMount 里计算),如果它们依赖客户端环境(比如依赖当前系统是安卓还是 IOS),就导致他们肯定不能放在服务端直接渲染出来,这又怎么办呢?这里我们的做法,是将这些计算放在 HTML body 之前,通过内联的脚本嵌入,计算出当前环境,给 body 加上一个特定的类(class),然后在这个特定的类下面的元素,就可以通过 css 给予特定的样式。比如下面代码:/* * 因为在不同的手机 APP 环境内,页面的 padding 是不一样的。 * 我们要在页面渲染完之前加上相应的 padding */var REGEXP_FUDAO_APP = /EducationApp/;if ( typeof navigator !== “undefined” && REGEXP_FUDAO_APP.test(navigator.userAgent)) { if (/Android/i.test(navigator.userAgent)) { document.body.classList.add(“androidFudaoApp”); } else if (/iPhone|iPad|iPod|iOS/i.test(navigator.userAgent)) { if (window.screen.width === 375 && window.screen.height === 812) { document.body.classList.add(“iphoneXFudaoApp”); } else { document.body.classList.add(“iosFudaoApp”); } }}.androidFudaoApp .tt { padding-top: 48px; background-position-y: 84px;}.iphoneXFudaoApp .tt { padding-top: 88px; background-position-y: 124px;}.iosFudaoApp .tt { padding-top: 64px; background-position-y: 100px;}然后把这段代码通过构建插入到页面 body 之前。-w500防抖动优化效果如下 (左优化完,右未优化):duibi_doudong二、冷启动预加载虽然我们做了 PWA 离线缓存,但是对于冷启动来说,客户端里面的 PWA 缓存还是没有的,这样就会导致初次点击页面,渲染速度相对慢一点。这里我们可以在 APP 启动的时候,用一个预加载的脚本最大限度的拉取用户可能访问的页面。核心代码如下:// 预加载页面时, PWA 预缓存课程详情页面的直出function prefetchCache(fetchUrl) { fetch(“https://you preFetch Cgi”) .then(data => { return data.json(); }) .then(res => { const { courseInfo = [] } = res.result || {}; courseInfo.forEach(item => { if (item.cid) { caches.open(cacheName).then(function(cache) { fetch(${courseURL}?course_id=${item.cid}).then(function( networkResponse ) { if (networkResponse.status === 200) { cache.put( ${courseURL}?course_id=${item.cid}, networkResponse.clone() ); } // return networkResponse; }); }); } }); }) .catch(err => { // To monitor err });}PWA 直出遗留问题一、兼容性问题随着 PWA 技术的发展,现今大部分手机以及 PC 环境已经支持对 PWA 进行了支持。经过我们的测试发现:安卓基本上都是支持的,IOS 需要11.3以上才支持。Service Workers 兼容性图二、IOS 渲染问题很多的经验告诉我们,外联的 script 标签要放在 body 的后面,因为它会阻塞页面的 DOM 渲染。经过测试发现,IOS 的 WebView (UIWebView)渲染机制并不会上述一样,而是要等到后面的 JS 执行完之后才渲染页面,如果是这样,我们的直出渲染优化就没有效果了(因为 HTML 并不在最开始渲染),这里可以使用 script 标签的 async 与 defer 属性来达到异步渲染的作用。升级 WkWebView 之后,情况得到改善,渲染正常。附录参考资料PWA 的探索与最佳实践亿万级访问量下的前端同构直出实践 React 16 加载性能优化指南此文已由作者授权腾讯云+社区在各渠道发布获取更多新鲜技术干货,可以关注我们腾讯云技术社区-云加社区官方号及知乎机构号 ...

January 17, 2019 · 2 min · jiezi

PWA的探索与应用

本文由云+社区发表PWA(Progressive Web App)起源背景传统的Web网页存在以下几个问题:进入一个页面必须要记住它的url或者加入书签,入口不便捷;没网络就没响应,不具备离线能力;不像APP一样能进行消息推送。Native app:开发成本高软件上线需要审核即使使用频率不高,想使用一个app必须先下载安装PWA概念的提出2016 年Google I/O 大会上提出一个 Next Web Generation 的概念。PWA是在传统Web应用的基础上,结合Manifest和service worker,完善Web应用的一些能力,比如:添加至主屏幕,点击主屏幕图标可以实现启动动画以及隐藏地址栏实现离线缓存功能,即使用户手机没有网络,依然可以使用一些离线功能消息推送PWA技术点Web App ManifestWeb App Manifest 技术实现了将PWA网页应用 添加至桌面的功能,但该项技术目前仍处于实验性阶段,各浏览器支持度不高image.pngPWA 站点部署的 manifest.json文件满足以下条件时会自动显示横幅:- short\_name (主屏幕显示)- name (安装横幅显示)- icons (必须包含一个 mime 类型为 image/png 的图标声明)- start\_url (应用启动地址)- display (必须为 standalone 或 fullscreen)- 站点注册 Service Worker。- 站点支持 HTTPS 访问。- 同一浏览器中站点至少被访问两次,间隔至少为 5 分钟。Service Worker PWA应用的离线体验、定期的后台同步以及推送通知等功能的实现依赖于Service Worker技术,下图为目前SW技术的支持度。SW具有以下特征:一个独立的 worker 线程,独立于当前网页进程,有自己独立的 worker context。一旦被 install,就永远存在,除非被手动 unregister用到的时候可以直接唤醒,不用的时候自动睡眠离线内容开发者可控能向客户端推送消息不能直接操作 DOM必须在 HTTPS 环境下才能工作异步实现,内部大都是通过 Promise 实现Service Worker生命周期installing:这个状态发生在 SW 注册之后开始安装,install 事件回调中执行skipWaiting()方法表示强制当前处在 waiting 状态的 Service Worker 进入 activate 状态。installed:SW已经完成了安装,等待其他的 SW 线程被关闭。activating:在这个状态下清除其他的worker 以及关联缓存的旧缓存资源,等待新的 SW线程被激活。在 activate 事件回调中执行self.clients.claim()方法表示取得页面的控制权, 这样之后打开页面都会使用版本更新的缓存。旧的 Service Worker 脚本不再控制着页面,之后会被停止。activated:在这个状态可以处理功能性的事件 fetch (请求)、sync (后台同步)、push (推送)。废弃状态 ( redundant ):这个状态表示一个 Service Worker 的生命周期结束。Service Worker 支持的事件install:Service Worker 安装成功后被触发的事件, 在事件处理函数中可以添加需要缓存的文件activate:当 Service Worker 安装完成后并进入激活状态,会触发 activate 事件。通过监听 activate 事件你可以做一些预处理,如对旧版本的更新、对无用缓存的清理等。message:Service Worker 运行于独立 context 中,无法直接访问当前页面主线程的 DOM 等信息,但是通过 postMessage API,可以实现他们之间的消息传递,这样主线程就可以接受 Service Worker 的指令操作 DOM。fetch :当浏览器在当前指定的 scope 下发起请求时,会触发 fetch 事件,并得到传有 response 参数的回调函数。fetch 事件特别重要,因为它能够定义你的缓存策略。也就是说,你可以决定何时使用缓存数据,何时使用网络请求来的数据。push:push 事件是为推送准备的。通过 PUSH API,当订阅了推送服务后,可以使用推送方式唤醒 Service Worker 以响应来自系统消息传递服务的消息,即使用户已经关闭了页面。sync:sync 事件由 background sync (后台同步)发出。background sync 是 Google 配合 SW 推出的 API,用于为 Service Worker 提供一个可以实现注册和监听同步处理的方法。但它还不在 W3C Web API 标准中。在 Chrome 中这也只是一个实验性功能,需要访问 chrome://flags/#enable-experimental-web-platform-features ,开启该功能,然后重启生效。Sync 事件允许延迟网络任务,直到用户连接上网络,它实现的功能通常被称为后台同步。这对于在离线模式下,确保用户启动的任何有网络依赖的任务,最终都将在网络再次可用时达到其预期目的,是非常有用的。Service Worker 的工作原理Service Worker是基于注册、安装、激活等步骤注册if (‘serviceWorker’ in navigator) {window.addEventListener(’load’, function () { navigator.serviceWorker.register(’/jslearning/sw.js’) // 默认作用域为jslearning下,也可以通过设置scope参数进行设置 .then(function (registration) { // 注册成功 console.log(‘ServiceWorker registration successful with scope: ‘, registration.scope); }) .catch(function (err) { // 注册失败:( console.log(‘ServiceWorker registration failed: ‘, err); });});}安装this.addEventListener(‘install’, function(event) { console.log(‘V1 installing…’); //需要缓存的重要的高优先级资源 var vipUrlsToPrefetch = [’./index.html’ ]; //次重要的资源 var urlsToPrefetch = [’./icon.png’ ]; event.waitUntil(caches.open(OFFLINE_CACHE_NAME).then(function(cache) { //urlsToPrefetch非重要资源,即使有资源加载失败也不影响Service Worker安装 cache.addAll(urlsToPrefetch); //vipUrlsToPrefetch中资源全部请求成功,Service Worker安装事件才顺利完成,可以进入激活事件 return cache.addAll(vipUrlsToPrefetch);}) ); });激活//Service Worker激活事件this.addEventListener(‘activate’, function(event) { //在激活事件中清除非当前版本的缓存避免用户存储空间急剧膨胀 event.waitUntil(caches.keys().then(function(cacheNames) {console.log(‘V1 activate’);return Promise.all(cacheNames.map(function(cacheName) { if (cacheName !== OFFLINE_CACHE_NAME) { if(cacheName.indexOf(OFFLINE_CACHE_PREFIX) != -1) { return caches.delete(cacheName); } }})); }));});Service Worker更新如果线程的字节与已有的SW线程字节不同,浏览器则考虑更新SW线程。更新的SW线程与现有SW线程一起启动,并获取自己的 install 事件。如果新工作SW线程出现不正常状态代码(例如,404)、解析失败,在执行中引发错误或在安装期间被拒,则系统将舍弃新工作线程,但当前工作线程仍处于活动状态。安装成功后,更新的工作线程将 wait,直到现有工作线程控制0个客户端。self.skipWaiting() 可跳过等待情况,这意味着sw线程在安装完后立即激活。Service Worker缓存策略 Service Worker缓存策略大部分在fetch与install时间中定义,对于某些固定不变的静态资源,可以在Service Worker初次安装的install事件中将其缓存,但资源过大或者网络不佳都会造成资源并未全部下载成功而导致Service Worker安装被中断安装失败。SW主要有以下几类缓存策略:不影响安装的资源预缓存渐进式缓存仅使用缓存、仅使用网络缓存优先 、网络优先// 渐进式缓存var addToCache = function(req) { return fetch(req.clone()).then(function(resp) { var cacheResp = resp.clone(); if (!resp.ok) { return resp; } caches.open(OFFLINE_CACHE_NAME).then(function(cache) { cache.put(req.clone(), cacheResp); }); return resp; });}; this.addEventListener(‘fetch’, function(event) { event.respondWith( caches.open(OFFLINE_CACHE_NAME).then(function(cache) { return cache.match(event.request); }).then(function(response) { if (response) { return response; } else { return addToCache(event.request); } }) ); });PWA应用可以通过开发者工具中的Application进行查看调试,如下图所示:PWA优缺点总结优点可以将app的快捷方式放置到桌面上,全屏运行,与原生app无异能够在网络差和断网条件下推送消息的能力快速响应用户指令缺点支持率不高Chrome在安卓移动端上的占有率很低依赖的GCM服务在国内无法使用微信小程序的竞争PWA应用Lavas 是一套基于 Vue 的 PWA 解决方案,能够帮助开发者快速搭建 PWA 应用新浪微博饿了么InstagramTwitterOffline WikipediaSpotlight…参考文献https://developers.google.com…https://developer.mozilla.org...https://lavas.baidu.com/pwahttps://x5.tencent.com/tbs/gu…https://segmentfault.com/a/11...https://blog.csdn.net/baidu_b…此文已由作者授权腾讯云+社区在各渠道发布获取更多新鲜技术干货,可以关注我们腾讯云技术社区-云加社区官方号及知乎机构号 ...

January 11, 2019 · 2 min · jiezi

PWA--未来式app

本文是PWA科普文,不涉及技术,望大佬勿喷。什么是PWA应用PWA(Progressive Web Apps 的简称,译作渐进式 Web App),是 Google 在 2015 年推出的一个项目,旨在将 Web 网页服务具备类似原生 Apps 的使用体验。PWA应用兼有网页应用和原生app的优点,能给用户带来原生应用一样的体验,同时又能避免原生应用体积过大,滥用权限,频繁更新等问题。PWA应用通过网页加载,同时也能使用service worker实现离线存储和使用。用户无需像原生app那样下载安装PWA,只需打开相应网页即可通过浏览器一键添加到桌面。下次可通过桌面图标进入应用,操作逻辑和原生app一样,只是不用下载,安装。同时PWA应用也是跨平台的,无论是在iOS,安卓还是windows phone下都有一致的用户体验。如何添加PWA应用 iOS: 使用Safari打开相应app网页,点击分享按钮,选择添加到主屏幕即可。 安卓:目前只有chrome(谷歌)浏览器完全支持PWA,点击右上角三个点的按钮,然后选择添加到主屏幕即可。 哪里能找到PWA应用 国外有很多PWA应用商店,这里推荐一个https://pwa.rocks/ 国内有https://www.pwaappstore.cn ,需要使用手机打开这个站点,电脑打开是404

January 3, 2019 · 1 min · jiezi

PWA项目实战分享

PWA项目实战分享 - BookPlayer 每天听本书App因为自己有个需求,特别的痒,昼夜难免。第二天就开始起手做这个项目,利用业余时间,大概持续了10天时间(因为边学边做),从设计到数据(包括解析物理文件)到前端。总于把我想要的效果做出来了。因为数据涉及到版权问题,所以只搞了部分数据来做演示,哈哈。效果演示传送门项目地址传送门Android App 下载该项目实现了:播放器功能倍速播放连续播放播放列表…听书排序分月/区间浏览已读变灰色查看大图解析xmind文件为树结构文本书籍的搜索历史记录课程和听书类似没有xmind只有图片文稿AppPWA 集成可借助 Lavas 生成 Android App…技术栈vue + vuex + vue-router + vue cli3 + LeanCloud + PWA(可以借助Lavas生成AndroidApp) + 腾讯云对象存储项目运行git clone https://github.com/worklinwu/BookPlayer.gitcd BookPlayernpm i 或 yarnnpm run devLeanCloud 配置先注册 LeanCloud 账号创建应用,命名为 BookPlayer, 或者自己喜欢的进入应用后,在存储的创建 Class旁边有个加号,点击选择数据导入把目录下的 json 文件导入查看侧边栏的设置 -> 应用key,复制替换掉该项目的 .env 的 VUE_APP_LEANCLOUD_APP_ID 和 VUE_APP_LEANCLOUD_APP_KEY重启项目,看看效果吧

December 28, 2018 · 1 min · jiezi

PWA,你需要知道的那些事

(ps: 有一段时间没发文了,忙得不可开交,之前团队分享PWA,答应大家整理出来,终于结稿了~)PWA简介PWA,英文全称是 Progressive Web App,2015年 由 Google 提出。PWA是提升 Web App的体验的一种新方法,能给用户原生应用的体验,兼具了 Web App 和 Native App 的优点。PWA 主要特性可靠 - 即使在不稳定的网络环境下,也能瞬间加载并展现;体验 - 快速响应,并且有平滑的动画响应用户的操作;粘性 - 像设备上的原生应用,具有沉浸式的用户体验,用户可以添加到桌面。特点:渐进式 - 适用于选用任何浏览器的所有用户,因为它是以渐进式增强作为核心宗旨来开发的。自适应 - 适合任何机型:桌面设备、移动设备、平板电脑或任何未来设备。 连接无关性 - 能够借助于服务工作线程在离线或低质量网络状况下工作。类似应用 - 由于是在 App Shell 模型基础上开发,因此具有应用风格的交互和导航,给用户以应用般的熟悉感。持续更新 - 在服务工作线程更新进程的作用下时刻保持最新状态。安全 - 通过 HTTPS 提供,以防止窥探和确保内容不被篡改。可发现 - W3C 清单和服务工作线程注册作用域能够让搜索引擎找到它们,从而将其识别为“应用”。可再互动 - 通过推送通知之类的功能简化了再互动。可安装 - 用户可免去使用应用商店的麻烦,直接将对其最有用的应用“保留”在主屏幕上。可链接 - 可通过网址轻松分享,无需复杂的安装。PWA 本身强调渐进式,但并不要求一次性达到安全、性能和体验上的所有要求,开发者可以通过 PWA Checklist 查看现有的特征。PWA功能丰富,相比原生应用更加轻量。我们可以把 PWA 网站添加到桌面上,不管在PC端还是移动端,都类似于一个原生应用,并且拥有媲美原生应用的体验。它也拥有原生 APP 应用一般的启动闪屏,也可以进行消息推送——不过要知道,它源自 Web,通常只有传统 APP 的体积的十分之一甚至更小。它不用等待下载安装的时间,打开网页的时候就已经「下载」并且「安装」完毕。APP ShellApp Shell 架构是构建 PWA 的一种方式,这种应用能可靠且即时地加载到您的用户屏幕上,与本机应用相似。定义App Shell 是支持用户界面所需的最小的 HTML、CSS 和 JavaScript,如果离线缓存,可确保在用户重复访问时提供即时、可靠的良好性能。这意味着并不是每次用户访问时都要从网络加载 App Shell。 只需要从网络中加载必要的内容。 看下面这张图来了解下:构建单页应用时,可以使用APP Shell,它依赖渐进式缓存 Shell(使用服务工作线程)让应用运行。App Shell 非常适合用于在没有网络的情况下将一些初始 HTML 快速加载到屏幕上。App Shell 应能完美地执行以下操作:快速加载尽可能使用较少的数据使用本机缓存中的静态资产将内容与导航分离开来检索和显示特定页面的内容(HTML、JSON 等)可选:缓存动态内容 App Shell 可保证 UI 的本地化以及从 API 动态加载内容,但同时不影响网络的可链接性和可检测性。 用户下次访问同一应用时,应用会自动显示最新版本。无需在使用前下载新版本。Service Worker与离线存储依赖Service Worker 作为现代浏览器的高级特性,依赖于 fetch 、promise 、CacheStorage、Cache API、等浏览器的基础能力, Cache 提供了 Request / Response 对象对的存储机制。CacheStorage 则提供了存储 Cache 对象的机制。功能和特性:一个独立的 worker 线程,独立于当前网页进程,有自己独立的 worker context。一旦被 install,就永远存在,除非被手动 unregister用到的时候可以直接唤醒,不用的时候自动睡眠可编程拦截代理请求和返回,缓存文件,缓存的文件可以被网页进程取到(包括网络离线状态)离线内容开发者可控能向客户端推送消息不能直接操作 DOM必须在 HTTPS 环境下才能工作异步实现,内部大都是通过 Promise 实现注意:SW通过postMessage与页面之间通信,让页面自己去操作DOM是一个可编程的网络代理,允许开发者控制页面上处理的网络请求在不被使用的时候,它会自己终止,而当它再次被用到的时候,会被重新激活,所以你不能依赖于service worker的onfecth和onmessage的处理函数中的全局状态如果你想要保存一些持久化的信息,你可以在service worker里使用IndexedDB API只能在 HTTPS 环境下才能使用SW,因为SW 的权利比较大,能够直接截取和返回用户的请求Push Notification ( 消息通知 ) Push 和 Notification 是两个不同的功能,涉及到两个 API 。 Notification 是浏览器发出的通知消息。Push 和 Notification 的关系,Push:服务器端将更新的信息传递给 SW ,Notification: SW 将更新的信息推送给用户。IndexedDB与异步存储Web Storage 包括 Local Storage、Session Storage。它们使用简单的键值对来存储,方便灵活,但是它们的内存较小,当遇到大量的结构化数据时,就无法应对了。浏览器数据存储方式还有IndexedDB、Web SQL 和 Cookie。接下来就来看下IndexedDB,它能够在客户端存储大量的结构化数据。例如,在PWA应用中,我们可以用它来离线存储大量的聊天记录。关于IndexDB的介绍,引用这篇文章中的一段话:indexedDB 是 HTML5 提供的一种本地存储,一般用户保存大量用户数据并要求数据之间有搜索需要的场景,当网络断开的时候,可以做一些离线应用,它比 SQL 方便,不用去写一些特定的语句对数据进行操作,数据格式为 json。IndexDB 使用索引高效检索的API,如打开一个IndexedDB数据库,可以这样写 window.indexedDB.open(name)。需要注意的是,IndexDB的功能之一就是它有异步的API,类似于 ajax 请求。我们通过代码来了解下如代码所示,打开数据库后,有以下几个回调:onerror(err => {})onsuccess(res => {})onupgradeneeded(data => {})分别处理数据库打开失败、成功的回调,以及请求数据库版本变化的回调。IndexDB 在使用时,需要注意以下几点:过大的数据不适合存放在IndexDB,浏览器初始化分配是50M部分浏览器不支持IndexDB,使用前先用"indexedDB" in window判断下敏感数据不能存在客户端受到同源策略的限制HTTPSHTTPS,简单来讲就是 HTTP 的安全版,它是HTTP over SSL/TLS的缩写。PWA 只能在 HTTPS 协议下使用,本地开发时支持 localhost 和 127.0.0.1。https下调试可以用 github page。HTTPS 会对传输的数据进行加密,建立一个信息安全通道,来保证传输过程中的数据安全。同时,也会对网站服务器进行真实的身份认证。关于HTTPS,大家都比较熟悉,这里就不多赘述了。入门与实践关于PWA开发与调试的,题叶老师写了一篇文章PWA 入门: 写个非常简单的 PWA 页面,介绍了如何开启一起简单的PWA应用,感兴趣的童鞋可以去看看。总结PWA作为下一代 Web 应用模型,在国外非常受重视,在国内同样受到各大互联网企业的欢迎。去年饿了么也实现了一场PWA升级实践。之前看过一篇文章,说PWA在印度广受欢迎,因为当地2/3G网络覆盖比较多,PWA的优势就明显了。下面借用列出了一些站点,从最开始的 Flipcart,到目前的 Instangram、Uber、Twitter、Starbucks 等,不仅数量在增加,站点等级和质量也在不断地提升。图片来源篇幅有限,无法面面俱到,只能抛装引玉,欢迎批评指正~参数资料PWA(Progressive Web App)初探总结讲讲PWA用新版的 Chrome 把 PWA 网站添加到桌面,获得媲美原生应用的体验您的第一个 Progressive Web AppPWA博客App Shell 模型 ...

November 9, 2018 · 1 min · jiezi

【PWA学习与实践】(7)使用Notification API来进行消息提醒

《PWA学习与实践》系列文章已整理至gitbook - PWA学习手册,文字内容已同步至learning-pwa-ebook。转载请注明作者与出处。本文是《PWA学习与实践》系列的第七篇文章。PWA作为时下最火热的技术概念之一,对提升Web应用的安全、性能和体验有着很大的意义,非常值得我们去了解与学习。对PWA感兴趣的朋友欢迎关注《PWA学习与实践》系列文章。本文中的代码可以在learning-pwa的notification分支上找到(git clone后注意切换到notification分支)。1. 引言在第五篇文章《Web中进行服务端消息推送》中,我介绍了如何使用Push API进行服务端消息推送。提到Push就不得不说与其联系紧密的另一个API——Notification API。它让我们可以在“网站外”显示消息提示:即使当你切换到其他Tab,也可以通过提醒交互来快速让用户回到你的网站;甚至当用户离开当前网站,仍然可以收到系统的提醒消息,并且可以通过消息提醒快速打开你的网站。Notification的功能本身与Push并不耦合,你完全可以只使用Notification API或者Push API来构建Web App的某些功能。因此,本文会先介绍如何使用Notification API。然后,作为Notification的“黄金搭档”,本文还会介绍如何组合使用Push & Notification(消息推送与提醒)。2. 使用Notification API在这第二节里,我们先来了解如何独立使用Notification功能。相较于第五篇中的Push功能,Notification API更加简洁易懂。2.1. 获取提醒权限首先,进行调用消息提醒API需要获得用户的授权。在调用Notification相关API之前,需要先使用Notification对象上的静态方法Notification.requestPermission()来获取授权。由于Notification.requestPermission()在某些版本浏览器中会接收一个回调函数(Notification.requestPermission(callback))作为参数,而在另一些浏览器版本中会返回一个promise,因此将该方法进行包装,统一为promise调用:// index.jsfunction askPermission() { return new Promise(function (resolve, reject) { var permissionResult = Notification.requestPermission(function (result) { resolve(result); }); if (permissionResult) { permissionResult.then(resolve, reject); } }).then(function (permissionResult) { if (permissionResult !== ‘granted’) { throw new Error(‘We weren't granted permission.’); } });}registerServiceWorker(’./sw.js’).then(function (registration) { return Promise.all([ registration, askPermission() ]) })我们创建了一个askPermission()方法来统一Notification.requestPermission()的调用形式,并在Service Worker注册完成后调用该方法。调用Notification.requestPermission()获取的permissionResult可能的值为:denied:用户拒绝了通知的显示granted:用户允许了通知的显示default:因为不知道用户的选择,所以浏览器的行为与denied时相同chrome中,可以在chrome://settings/content/notifications里进行通知的设置与管理。2.2. 设置你的提醒内容获取用户授权后,我们就可以通过registration.showNotification()方法进行消息提醒了。当我们注册完Service Worker后,then方法的回调函数会接收一个registration参数,通过调用其上的showNotification()方法即可触发提醒:// index.jsregisterServiceWorker(’./sw.js’).then(function (registration) { return Promise.all([ registration, askPermission() ])}).then(function (result) { var registration = result[0]; /* ===== 添加提醒功能 ====== / document.querySelector(’#js-notification-btn’).addEventListener(‘click’, function () { var title = ‘PWA即学即用’; var options = { body: ‘邀请你一起学习’, icon: ‘/img/icons/book-128.png’, actions: [{ action: ‘show-book’, title: ‘去看看’ }, { action: ‘contact-me’, title: ‘联系我’ }], tag: ‘pwa-starter’, renotify: true }; registration.showNotification(title, options); }); / ======================= */})上面这段代码为页面上的button添加了一个click事件监听:当点击后,调用registration.showNotification()方法来显示消息提醒,该方法接收两个参数:title与option。title用来设置该提醒的主标题,option中则包含了一些其他设置。body:提醒的内容icon:提醒的图标actions:提醒可以包含一些自定义操作tag:相当于是ID,通过该ID标识可以操作特定的notificationrenotify:是否允许重复提醒,默认为false。当不允许重复提醒时,同一个tag的notification只会显示一次注意,由于不同浏览器中,对于option属性的支持情况并不相同。部分属性在一些浏览器中并不支持。2.3. 捕获用户的点击在上一部分中,我们已经为Web App添加了提醒功能。点击页面中的“提醒”按钮,系统就会弹出提醒框,并展示相关提醒消息。然而更多的时候,我们并不仅仅希望只展示有限的信息,更希望能引导用户进行交互。例如推荐一本新书,让用户点击阅读或购买。在上一部分我们设置的提醒框中,包含了“去看看”和“联系我”两个按钮选项,那么怎么做才能捕获用户的点击操作,并且知道用户点击了哪个呢?这一小节,就会告诉你如何实现。还记的上一部分里我们定义的actions么?…actions: [{ action: ‘show-book’, title: ‘去看看’ }, { action: ‘contact-me’, title: ‘联系我’}]…为了能够响应用户对于提醒框的点击事件,我们需要在Service Worker中监听notificationclick事件。在该事件的回调函数中我们可以获取点击的相关信息:// sw.jsself.addEventListener(’notificationclick’, function (e) { var action = e.action; console.log(action tag: ${e.notification.tag}, action: ${action}); switch (action) { case ‘show-book’: console.log(‘show-book’); break; case ‘contact-me’: console.log(‘contact-me’); break; default: console.log(未处理的action: ${e.action}); action = ‘default’; break; } e.notification.close();});e.action获取的值,就是我们在showNotification()中定义的actions里的action。因此,通过e.action就可以知道用户点击了哪一个操作选项。注意,当用户点击提醒本身时,也会触发notificationclick,但是不包含任何action值,所以在代码中将其置于default默认操作中。现在试一下,我们就可以捕获用户对于不同选项的点击了。点击后在Console中会有不同的输出。2.4. Service Worker与client通信到目前为止,我们已经可以顺利得给用户展示提醒,并且在用户操作提醒后准确捕获到用户的操作。然而,还缺最重要的一步——针对不同的操作,触发不同的交互。例如,点击提醒本身会弹出书籍简介;点击“看一看”会给用户展示本书的详情;点击“联系我”会向应用管理者发邮件等等。这里有个很重要的地方:我们在Service Worker中捕获用户操作,但是需要在client(这里的client是指前端页面的脚本环境)中触发相应操作(调用页面方法/进行页面跳转…)。因此,这就需要让Service Worker与client进行通信。通信包括下面两个部分:在Service Worker中使用Worker的postMessage()方法来通知client:// sw.jsself.addEventListener(’notificationclick’, function (e) { …… // 略去上一节内容 e.waitUntil( // 获取所有clients self.clients.matchAll().then(function (clients) { if (!clients || clients.length === 0) { return; } clients.forEach(function (client) { // 使用postMessage进行通信 client.postMessage(action); }); }) );});在client中监听message事件,判断data,进行不同的操作:// index.jsnavigator.serviceWorker.addEventListener(‘message’, function (e) { var action = e.data; console.log(receive post-message from sw, action is '${e.data}'); switch (action) { case ‘show-book’: location.href = ‘https://book.douban.com/subject/20515024/'; break; case ‘contact-me’: location.href = ‘mailto:someone@sample.com’; break; default: document.querySelector(’.panel’).classList.add(‘show’); break; }});当用户点击提醒后,我们在notificationclick监听中,将action通过postMessage()通信给client;然后在client中监听message事件,基于action(e.data)来进行不同的操作(跳转到图书详情页/发送邮件/显示简介面板)。至此,一个比较简单与完整的消息提醒(Notification)功能就完成了。然而目前的消息提醒还存在一定的局限性。例如,只有在用户访问网站期间才能有机会触发提醒。正如本文一开始所说,Push & Notification的结合将会帮助我们构筑一个强大推送与提醒功能。下面就来看下它们的简单结合。3. 消息推送与提醒在第五篇《Web中进行服务端消息推送》最后,我们通过监听push事件来处理服务端推送:// sw.jsself.addEventListener(‘push’, function (e) { var data = e.data; if (e.data) { data = data.json(); console.log(‘push的数据为:’, data); self.registration.showNotification(data.text); } else { console.log(‘push没有任何数据’); }});简单修改以上代码,与我们本文中的提醒功能相结合:// sw.jsself.addEventListener(‘push’, function (e) { var data = e.data; if (e.data) { data = data.json(); console.log(‘push的数据为:’, data); var title = ‘PWA即学即用’; var options = { body: data, icon: ‘/img/icons/book-128.png’, image: ‘/img/icons/book-521.png’, // no effect actions: [{ action: ‘show-book’, title: ‘去看看’ }, { action: ‘contact-me’, title: ‘联系我’ }], tag: ‘pwa-starter’, renotify: true }; self.registration.showNotification(title, options); } else { console.log(‘push没有任何数据’); }});使用Push来向用户推送信息,并在Service Worker中直接调用Notification API来展示该信息的提醒框。这样,即使是在用户关闭该Web App时,依然可以收到提醒,类似于Native中的消息推送与提醒。我们还可以将这个功能再丰富一些。由于用户在关闭该网站时仍然可以收到提醒,因此加入一些更强大功能:当用户切换到其他Tab时,点击提醒会立刻回到网站的tab;当用户未打开该网站时,点击提醒可以直接打开网站。// sw.jsself.addEventListener(’notificationclick’, function (e) { var action = e.action; console.log(action tag: ${e.notification.tag}, action: ${action}); switch (action) { case ‘show-book’: console.log(‘show-book’); break; case ‘contact-me’: console.log(‘contact-me’); break; default: console.log(未处理的action: ${e.action}); action = ‘default’; break; } e.notification.close(); e.waitUntil( // 获取所有clients self.clients.matchAll().then(function (clients) { if (!clients || clients.length === 0) { // 当不存在client时,打开该网站 self.clients.openWindow && self.clients.openWindow(‘http://127.0.0.1:8085’); return; } // 切换到该站点的tab clients[0].focus && clients[0].focus(); clients.forEach(function (client) { // 使用postMessage进行通信 client.postMessage(action); }); }) );});注意这两行代码,第一行会在网站关闭时打开该网站,第二行会在存在tab时自动切换到网站的tab。self.clients.openWindow && self.clients.openWindow(‘http://127.0.0.1:8085’);clients[0].focus && clients[0].focus();4. MacOS Safari中的Web Notification看一下Web Notification的兼容性:目前移动端浏览器普遍还不支持该特性。但是在Mac OS上的safari里面是支持该特性的,不过其调用方式与上文代码有些不太一样。在safari中使用Web Notification不是调用registration.showNotification()方法,而是需要创建一个Notification对象。// index.js……document.querySelector(’#js-notification-btn’).addEventListener(‘click’, function () { var title = ‘PWA即学即用’; var options = { body: ‘邀请你一起学习’, icon: ‘/img/icons/book-128.png’, actions: [{ action: ‘show-book’, title: ‘去看看’ }, { action: ‘contact-me’, title: ‘联系我’ }], tag: ‘pwa-starter’, renotify: true }; // registration.showNotification(title, options); // 使用Notification构造函数创建提醒框 // 而非registration.showNotification()方法 var notification = new Notification(title, options);});……Notification对象继承自EventTarget接口,因此在safari中需要通过添加click事件的监听来触发提醒框的交互操作:// index.jsnotification.addEventListener(‘click’, function (e) { document.querySelector(’.panel’).classList.add(‘show’);});该功能示例可以在learn-pwa/notify4safari中找到。5. 写在最后Web Notification是一个非常强大的API,尤其在和Push结合后,为WebApp带来了类似Native的丰富能力。本文中所有的代码示例均可以在learn-pwa/notification上找到。如果你喜欢或想要了解更多的PWA相关知识,欢迎关注我,关注《PWA学习与实践》系列文章。我会总结整理自己学习PWA过程的遇到的疑问与技术点,并通过实际代码和大家一起实践。到目前为止,我们已经学习了Manifest、离线缓存、消息推送、消息提醒、Debug等一些基础知识。在下一篇文章里,我们会继续了解与学习PWA中的一个重要功能——后台同步。《PWA学习与实践》系列第一篇:2018,开始你的PWA学习之旅第二篇:10分钟学会使用Manifest,让你的WebApp更“Native”第三篇:从今天起,让你的WebApp离线可用第四篇:TroubleShooting: 解决FireBase login验证失败问题第五篇:与你的用户保持联系: Web Push功能第六篇:How to Debug? 在chrome中调试你的PWA第七篇:增强交互:使用Notification API来进行提醒(本文)第八篇:使用Service Worker进行后台数据同步第九篇:PWA实践中的问题与解决方案第十篇:Resource Hint - 提升页面加载性能与体验第十一篇:从PWA离线工具集workbox中学习各类离线策略(写作中…)参考资料MDN: notificationMDN: ServiceWorkerRegistration.showNotification()MDN: WindowClientMDN: ClientsWWDC2013 ...

November 6, 2018 · 3 min · jiezi

【PWA学习与实践】(4) 解决FireBase login验证失败问题

《PWA学习与实践》系列文章已整理至gitbook - PWA学习手册,文字内容已同步至learning-pwa-ebook。转载请注明作者与出处。本文是《PWA学习与实践》系列的第四篇。是我在测试其他demo时遇到的一个问题,算是一篇TroubleShooting。PWA作为时下最火热的技术概念之一,对提升Web应用的安全、性能和体验有着很大的意义,非常值得我们去了解与学习。对PWA感兴趣的朋友欢迎关注《PWA学习与实践》系列文章。引言在前一篇文章《让你的WebApp离线可用》中,我们使用Service Worker来做缓存与离线支持。是有一个重要的问题:Service Worker必须要在HTTPS协议下才能运行(或者localhost)。当然,对于一些只有前端资源(不涉及后端服务)的demo,我们完全可以将这些前端(静态资源)托管在一个HTTPS服务下,使得Service Worker可以使用。我选择了google的FireBase来托管demo(其实github page也是个不错的选择)。使用FireBase非常简单,只需要firebase login –>firebase init–>firebase deploy即可。但是在firebase login的过程中,遇到了一些问题。这篇文章主要总结了我在firebase login遇到的问题及解决方式:无法获取authorization code Authentication Error: Your credentials are no longer valid.有需要的朋友可以继续看。首先,如果你对firebase完全不了解,下面会有一段非常简短的介绍。什么是FireBase前段时间学习PWA,在跟着官方教程完成demo后,想要在手机上测试一下效果。然而,遇到的一个问题就是:PWA需要HTTPS协议(或者使用localhost)。这就需要我们有一个HTTPS的服务,并在其上面部署我们本地写好的demo。而官方demo的最后,推荐使用firebase来托管你的代码。在FireBase的众多使用场景中,Develop -> Hosting(托管)就是我需要用到的了。然而,在执行firebase login(账号登录)过程中,却遇到了一些问题。问题一:在浏览器登录账号后,无反应(无法获取authorization code)最开始,我在CLI中输入firebase login,选择y后,CLI会需要一个authorization code;而浏览器会打开并提示你进行登录。这里我用google账户进行授权登录。然而,在授权之后,却迟迟没有响应(无法得到authorization code)。这时候,我发现浏览器显示,似乎是在等待localhost进行响应。解决这个问题的方法就是:在登录时,使用firebase login –no-localhost进行登录。重新使用firebase login –no-localhost登录。这里我选择了google账号进行登录,重复上面的过程:这次,你就会在浏览器中获得一串authorization code值:将它粘贴到CLI中即可。【问题一】解决!问题二:Error: Authentication Error: Your credentials are no longer valid.然而,在CLI中输入authorization code之后,在等待了较长时间的验证后,CLI中报出了如下错误:这是怎么回事呢?通过查阅一些资料发现,这很可能是你在电脑上使用“翻墙”工具所导致的。firebase-tool依赖的npm包(faye-websocket)中,未开启代理的相关设置,因此无法进行验证。解决这个问题的方法有两种:方法一:在路由器上设置代理,而非本机有些文章指出,通过在路由器上设置代理,而非在本机开启代理,可以避免这个问题。不过由于一些原因,暂时还没有尝试这种方式,不过通过一些反馈来看,应该是一个有效的方法。方法二:(hack) 修改代码与相关环境变量该方法较第一种方法来看,会稍微“硬”那么一些。具体的操作方式如下:设置环境变量http_proxy,我本机的代理使用的是1087端口。export http_proxy=http://localhost:1087修改faye-websocket,开启代理配置。faye-websocket是firebase依赖的一个WebSocket库,需要为其client.js添加如下配置:var Client = function(_url, protocols, options) { options = options || {}; // 添加proxy配置 options.proxy = { origin: ‘http://localhost:1087’, }; …}如果你是全局安装的firebase-tools,你可以通过如下方法找到client.jsNODE_PATH=npm prefix -g// client.js的位置$NODE_PATH/lib/node_modules/firebase-tools/node_modules/firebase/node_modules/faye-websocket/lib/faye/websocket/client.js设置环境变量NODE_TLS_REJECT_UNAUTHORIZED。export NODE_TLS_REJECT_UNAUTHORIZED=0重新登录,firebase login –no-localhost,重复之前的操作。你会发现,登录成功!【问题二】解决!p.s. 针对这个问题,github上也有一个issue:Unable to deploy behind a proxy。此外,如果你使用了代理,推荐使用全局代理的方式,使你的CLI也使用代理。写在最后最后,还是回到我开发PWA时的需求。文章最开始提到了,我是为了在移动端测试PWA demo的效果,所以使用FireBase来托管资源。当然,除了FireBase,还有下面两个办法:使用github page。由于github全站都是运行在HTTPS下,因此在github page上托管的静态站点可以使用Service Worker;使用localhost/127.0.0.1。了解PWA的话,你会知道除了HTTPS之外,也可以使用localhost(这一设计是为了方便本机调试)。本文是《PWA学习与实践》系列中的第四篇。这篇文章并没有探讨PWA中实际的技术,而是记录了我在开发、调试P过程与遇到的问题。可能有朋友也会遇到类似问题,因此记录下来和大家分享。在下一篇文章中,我们还是会回到PWA背后的技术,来了解一下,如何使用Push API来实现后端服务向客户端进行消息推送。《PWA学习与实践》系列文章第一篇:2018,开始你的PWA学习之旅第二篇:10分钟学会使用Manifest,让你的WebApp更“Native”第三篇:从今天开始,让你的WebApp离线可用第四篇:TroubleShooting: 解决FireBase login验证失败问题(本文)第五篇:与你的用户保持联系: Web Push功能第六篇:How to Debug? 在chrome中调试你的PWA第七篇:增强交互:使用Notification API来进行提醒第八篇:使用Service Worker进行后台数据同步第九篇:PWA实践中的问题与解决方案第十篇:Resource Hint - 提升页面加载性能与体验第十一篇:从PWA离线工具集workbox中学习各类离线策略(写作中…) ...

November 6, 2018 · 1 min · jiezi

【PWA学习与实践】(5)在Web中进行服务端消息推送

《PWA学习与实践》系列文章已整理至gitbook - PWA学习手册,文字内容已同步至learning-pwa-ebook。转载请注明作者与出处。本文是《PWA学习与实践》系列的第五篇文章。文中的代码都可以在learning-pwa的push分支上找到(git clone后注意切换到push分支)。PWA作为时下最火热的技术概念之一,对提升Web应用的安全、性能和体验有着很大的意义,非常值得我们去了解与学习。对PWA感兴趣的朋友欢迎关注《PWA学习与实践》系列文章。1. 引言在之前的几篇文章中,我和大家分享了如何使用manifest(以及meta标签)让你的Web App更加“native”;以及如何使用Service Worker来cache资源,加速Web App的访问速度,提供部分离线功能。在接下来的内容里,我们会探究PWA中的另一个重要功能——消息推送与提醒(Push & Notification)。这个能力让我们可以从服务端向用户推送各类消息并引导用户触发相应交互。实际上,消息推送与提醒是两个功能——Push API 和 Notification API。为了大家能够更好理解其中的相关技术,我也会分为Push(推送消息)与Notification(展示提醒)两部分来介绍。在这一篇里,我们先来学习如何使用Push API进行消息推送。Push API 和 Notification API其实是两个独立的技术,完全可以分开使用;不过Push API 和 Notification API相结合是一个常见的模式。2. 浏览器是如何实现服务器消息Push的Web Push的整个流程相较之前的内容来说有些复杂。因此,在进入具体技术细节之前,我们需要先了解一下整个Push的基本流程与相关概念。如果你对Push完全不了解,可能会认为,Push是我们的服务端直接与浏览器进行交互,使用长连接、WebSocket或是其他技术手段来向客户端推送消息。然而,这里的Web Push并非如此,它其实是一个三方交互的过程。在Push中登场的三个重要“角色”分别是:浏览器:就是我们的客户端Push Service:专门的Push服务,你可以认为是一个第三方服务,目前chrome与firefox都有自己的Push Service Service。理论上只要浏览器支持,可以使用任意的Push Service后端服务:这里就是指我们自己的后端服务下面就介绍一下这三者在Web Push中是如何交互。2.1. 消息推送流程下图来自Web Push协议草案,是Web Push的整个流程: +——-+ +————–+ +————-+ | UA | | Push Service | | Application | +——-+ +————–+ | Server | | | +————-+ | Subscribe | | |———————>| | | Monitor | | |<====================>| | | | | | Distribute Push Resource | |——————————————–>| | | | : : : | | Push Message | | Push Message |<———————| |<———————| | | | |该时序图表明了Web Push的各个步骤,我们可以将其分为订阅(subscribe)与推送(push)两部分来看。subscribe,首先是订阅:Ask Permission:这一步不再上图的流程中,这其实是浏览器中的策略。浏览器会询问用户是否允许通知,只有在用户允许后,才能进行后面的操作。Subscribe:浏览器(客户端)需要向Push Service发起订阅(subscribe),订阅后会得到一个PushSubscription对象Monitor:订阅操作会和Push Service进行通信,生成相应的订阅信息,Push Service会维护相应信息,并基于此保持与客户端的联系;Distribute Push Resource:浏览器订阅完成后,会获取订阅的相关信息(存在于PushSubscription对象中),我们需要将这些信息发送到自己的服务端,在服务端进行保存。Push Message,然后是推送:Push Message阶段一:我们的服务端需要推送消息时,不直接和客户端交互,而是通过Web Push协议,将相关信息通知Push Service;Push Message阶段二:Push Service收到消息,通过校验后,基于其维护的客户端信息,将消息推送给订阅了的客户端;最后,客户端收到消息,完成整个推送过程。2.2. 什么是Push Service在上面的Push流程中,出现了一个比较少接触到的角色:Push Service。那么什么是Push Service呢?A push service receives a network request, validates it and delivers a push message to the appropriate browser.Push Service可以接收网络请求,校验该请求并将其推送给合适的浏览器客户端。Push Service还有一个非常重要的功能:当用户离线时,可以帮我们保存消息队列,直到用户联网后再发送给他们。目前,不同的浏览器厂商使用了不同的Push Service。例如,chrome使用了google自家的FCM(前身为GCM),firefox也是使用自家的服务。那么我们是否需要写不同的代码来兼容不同的浏览器所使用的服务呢?答案是并不用。Push Service遵循Web Push Protocol,其规定了请求及其处理的各种细节,这就保证了,不同的Push Service也会具有标准的调用方式。这里再提一点:我们在上一节中说了Push的标准流程,其中第一步就是浏览器发起订阅,生成一个PushSubscription对。Push Service会为每个发起订阅的浏览器生成一个唯一的URL,这样,我们在服务端推送消息时,向这个URL进行推送后,Push Service就会知道要通知哪个浏览器。而这个URL信息也在PushSubscription对象里,叫做endpoint。那么,如果我们知道了endpoint的值,是否就代表我们可以向客户端推送消息了呢?并非如此。下面会简单介绍一下Web Push中的安全策略。2.3. 如何保证Push的安全性在Web Push中,为了保证客户端只会收到其订阅的服务端推送的消息(其他的服务端即使在拿到endpoint也无法推送消息),需要对推送信息进行数字签名。该过程大致如下:在Web Push中会有一对公钥与私钥。客户端持有公钥,而服务端持有私钥。客户端在订阅时,会将公钥发送给Push Service,而Push Service会将该公钥与相应的endpoint维护起来。而当服务端要推送消息时,会使用私钥对发送的数据进行数字签名,并根据数字签名生成一个叫】Authorization请求头。Push Service收到请求后,根据endpoint取到公钥,对数字签名解密验证,如果信息相符则表明该请求是通过对应的私钥加密而成,也表明该请求来自浏览器所订阅的服务端。反之亦然。而公钥与私钥如何生成,会在第三部分的实例中讲解。3. 如何使用Push API来推送向用户推送信息到这里,我们已经基本了解了Web Push的流程。光说不练假把式,下面我就通过具体代码来说明如何使用Web Push。这部分会基于sw-cache分支上的代码,继续增强我们的“图书搜索”WebApp。为了使文章与代码更清晰,将Web Push分为这几个部分:浏览器发起订阅,并将订阅信息发送至后端;将订阅信息保存在服务端,以便今后推送使用;服务端推送消息,向Push Service发起请求;浏览器接收Push信息并处理。友情提醒:由于Chrome所依赖的Push Service——FCM在国内不可访问,所以要正常运行demo中的代码需要“梯子”,或者可以选择Firefox来进行测试。3.1. 浏览器(客户端)生成subscription信息首先,我们需要使用PushManager的subscribe方法来在浏览器中进行订阅。在《让你的WebApp离线可用》中我们已经知道了如何注册Service Worker。当我们注册完Service Worker后会得到一个Registration对象,通过调用Registration对象的registration.pushManager.subscribe()方法可以发起订阅。为了使代码更清晰,本篇demo在之前的基础上,先抽离出Service Worker的注册方法:// index.jsfunction registerServiceWorker(file) { return navigator.serviceWorker.register(file);}然后定义了subscribeUserToPush()方法来发起订阅:// index.jsfunction subscribeUserToPush(registration, publicKey) { var subscribeOptions = { userVisibleOnly: true, applicationServerKey: window.urlBase64ToUint8Array(publicKey) }; return registration.pushManager.subscribe(subscribeOptions).then(function (pushSubscription) { console.log(‘Received PushSubscription: ‘, JSON.stringify(pushSubscription)); return pushSubscription; });}这里使用了registration.pushManager.subscribe()方法中的两个配置参数:userVisibleOnly和applicationServerKey。userVisibleOnly表明该推送是否需要显性地展示给用户,即推送时是否会有消息提醒。如果没有消息提醒就表明是进行“静默”推送。在Chrome中,必须要将其设置为true,否则浏览器就会在控制台报错:applicationServerKey是一个客户端的公钥,VAPID定义了其规范,因此也可以称为VAPID keys。如果你还记得2.3中提到的安全策略,应该对这个公钥不陌生。该参数需要Unit8Array类型。因此定义了一个urlBase64ToUint8Array方法将base64的公钥字符串转为Unit8Array。subscribe()也是一个Promise方法,在then中我们可以得到订阅的相关信息——一个PushSubscription对象。下图展示了这个对象中的一些信息。注意其中的endpoint,Push Service会为每个客户端随机生成一个不同的值.之后,我们再将PushSubscription信息发送到后端。这里定义了一个sendSubscriptionToServer()方法,该方法就是一个普通的XHR请求,会向接口post订阅信息,为了节约篇幅就不列出具体代码了。最后,将这一系列方法组合在一起。当然,使用Web Push前,还是需要进行特性检测’PushManager’ in window。// index.jsif (‘serviceWorker’ in navigator && ‘PushManager’ in window) { var publicKey = ‘BOEQSjdhorIf8M0XFNlwohK3sTzO9iJwvbYU-fuXRF0tvRpPPMGO6d_gJC_pUQwBT7wD8rKutpNTFHOHN3VqJ0A’; // 注册service worker registerServiceWorker(’./sw.js’).then(function (registration) { console.log(‘Service Worker 注册成功’); // 开启该客户端的消息推送订阅功能 return subscribeUserToPush(registration, publicKey); }).then(function (subscription) { var body = {subscription: subscription}; // 为了方便之后的推送,为每个客户端简单生成一个标识 body.uniqueid = new Date().getTime(); console.log(‘uniqueid’, body.uniqueid); // 将生成的客户端订阅信息存储在自己的服务器上 return sendSubscriptionToServer(JSON.stringify(body)); }).then(function (res) { console.log(res); }).catch(function (err) { console.log(err); });}注意,这里为了方便我们后面的推送,为每个客户端生成了一个唯一IDuniqueid,这里使用了时间戳生成简单的uniqueid。此外,由于userVisibleOnly为true,所以需要用户授权开启通知权限,因此我们会看到下面的提示框,选择“允许”即可。你可以在设置中进行通知的管理。3.2. 服务端存储客户端subscription信息为了存储浏览器post来的订阅信息,服务端需要增加一个接口/subscription,同时添加中间件koa-body用于处理body// app.jsconst koaBody = require(‘koa-body’);/** * 提交subscription信息,并保存 /router.post(’/subscription’, koaBody(), async ctx => { let body = ctx.request.body; await util.saveRecord(body); ctx.response.body = { status: 0 };});接收到subscription信息后,需要在服务端进行保存,你可使用任何方式来保存它:mysql、redis、mongodb……这里为了方便,我使用了nedb来进行简单的存储。nedb不需要部署安装,可以将数据存储在内存中,也可以持久化,nedb的api和mongodb也比较类似。这里util.saveRecord()做了这些工作:首先,查询subscription信息是否存在,若已存在则只更新uniqueid;否则,直接进行存储。至此,我们就将客户端的订阅信息存储完毕了。现在,就可以等待今后推送时使用。3.3. 使用subscription信息推送信息在实际中,我们一般会给运营或产品同学提供一个推送配置后台。可以选择相应的客户端,填写推送信息,并发起推送。为了简单起见,我并没有写一个推送配置后台,而只提供了一个post接口/push来提交推送信息。后期我们完全可以开发相应的推送后台来调用该接口。// app.js/* * 消息推送API,可以在管理后台进行调用 * 本例子中,可以直接post一个请求来查看效果 /router.post(’/push’, koaBody(), async ctx => { let {uniqueid, payload} = ctx.request.body; let list = uniqueid ? await util.find({uniqueid}) : await util.findAll(); let status = list.length > 0 ? 0 : -1; for (let i = 0; i < list.length; i++) { let subscription = list[i].subscription; pushMessage(subscription, JSON.stringify(payload)); } ctx.response.body = { status };});来看一下/push接口。首先,根据post的参数不同,我们可以通过uniqueid来查询某条订阅信息:util.find({uniqueid});也可以从数据库中查询出所有订阅信息:util.findAll()。然后通过pushMessage()方法向Push Service发送请求。根据第二节的介绍,我们知道,该请求需要符合Web Push协议。然而,Web Push协议的请求封装、加密处理相关操作非常繁琐。因此,Web Push为各种语言的开发者提供了一系列对应的库:Web Push Libaray,目前有NodeJS、PHP、Python、Java等。把这些复杂而繁琐的操作交给它们可以让我们事半功倍。最后返回结果,这里只是简单的根据是否有订阅信息来进行返回。安装node版web-pushnpm install web-push –save前面我们提到的公钥与私钥,也可以通过web-push来生成使用web-push非常简单,首先设置VAPID keys:// app.jsconst webpush = require(‘web-push’);/* * VAPID值 * 这里可以替换为你业务中实际的值 /const vapidKeys = { publicKey: ‘BOEQSjdhorIf8M0XFNlwohK3sTzO9iJwvbYU-fuXRF0tvRpPPMGO6d_gJC_pUQwBT7wD8rKutpNTFHOHN3VqJ0A’, privateKey: ‘TVe_nJlciDOn130gFyFYP8UiGxxWd3QdH6C5axXpSgM’};// 设置web-push的VAPID值webpush.setVapidDetails( ‘mailto:alienzhou16@163.com’, vapidKeys.publicKey, vapidKeys.privateKey);设置完成后即可使用webpush.sendNotification()方法向Push Service发起请求。最后我们来看下pushMessage()方法的细节:// app.js/* * 向push service推送信息 * @param {} subscription * @param {} data */function pushMessage(subscription, data = {}) { webpush.sendNotification(subscription, data, options).then(data => { console.log(‘push service的相应数据:’, JSON.stringify(data)); return; }).catch(err => { // 判断状态码,440和410表示失效 if (err.statusCode === 410 || err.statusCode === 404) { return util.remove(subscription); } else { console.log(subscription); console.log(err); } })}webpush.sendNotification为我们封装了请求的处理细节。状态码401和404表示该subscription已经无效,可以从数据库中删除。3.4. Service Worker监听Push消息调用webpush.sendNotification()后,我们就已经把消息发送至Push Service了;而Push Service会将我们的消息推送至浏览器。要想在浏览器中获取推送信息,只需在Service Worker中监听push的事件即可:// sw.jsself.addEventListener(‘push’, function (e) { var data = e.data; if (e.data) { data = data.json(); console.log(‘push的数据为:’, data); self.registration.showNotification(data.text); } else { console.log(‘push没有任何数据’); }});4. 效果展示我们同时使用firefox与chrome来访问该WebApp,并分别向这两个客户端推送消息。我们可以使用console中打印出来的uniqueid,在postman中发起/push请求进行测试。可以看到,我们分别向firefox与chrome中推送了“welcome to PWA”这条消息。console中的输出来自于Service Worker中对push事件的监听。而弹出的浏览器提醒则来自于之前提到的、订阅时配置的userVisibleOnly: true属性。在后续的文章里,我继续带大家了解Notification API(提醒)的使用。正如前文所述,Push Service可以在设备离线时,帮你维护推送消息。当浏览器设备重新联网时,就会收到该推送。下面展示了在设备恢复联网后,就会收到推送:5. 万恶的兼容性又到了查看兼容性的时间了。比较重要的是,对于Push API,目前Safari团队并没有明确表态计划支持。当然,其实比兼容性更大的一个问题是,Chrome所依赖的FCM服务在国内是无法访问的,而Firefox的服务在国内可以正常使用。这也是为什么在代码中会有这一项设置:const options = { // proxy: ‘http://localhost:1087’ // 使用FCM(Chrome)需要配置代理};上面代码其实是用来配置web-push代理的。这里有一点需要注意,目前从npm上安装的web-push是不支持设置代理选项的。针对这点github上专门有issue进行了讨论,并在最近(两周前)合入了相应的PR。因此,如果需要web-push支持代理,简单的方式就是基于master进行web-push代码的相应调整。虽然由于google服务被屏蔽,导致国内Push功能无法在chrome上使用,但是作为一个重要的技术点,Web Push还是非常值得我们了解与学习的。6. 写在最后本文中所有的代码示例均可以在learn-pwa/push上找到。注意在git clone之后,切换到push分支。切换其他分支可以看到不同的版本:basic分支:基础项目demo,一个普通的图书搜索应用(网站);manifest分支:基于basic分支,添加manifest等功能;sw-cache分支:基于manifest分支,添加缓存与离线功能;push分支:基于sw-cache分支,添加服务端消息推送功能;master分支:应用的最新代码。如果你喜欢或想要了解更多的PWA相关知识,欢迎关注我,关注《PWA学习与实践》系列文章。我会总结整理自己学习PWA过程的遇到的疑问与技术点,并通过实际代码和大家一起实践。在下一篇文章里,我们先缓下脚步——工欲善其事,必先利其器。在继续了解更多PWA相关技术之前,先了解一些chrome上的PWA调试技巧。之后,我们会再回来继续了解另一个经常与Push API组合在一起的功能——消息提醒,Notification API。《PWA学习与实践》系列第一篇:2018,开始你的PWA学习之旅第二篇:10分钟学会使用Manifest,让你的WebApp更“Native”第三篇:从今天起,让你的WebApp离线可用第四篇:TroubleShooting: 解决FireBase login验证失败问题第五篇:与你的用户保持联系: Web Push功能(本文)第六篇:How to Debug? 在chrome中调试你的PWA第七篇:增强交互:使用Notification API来进行提醒第八篇:使用Service Worker进行后台数据同步第九篇:PWA实践中的问题与解决方案第十篇:Resource Hint - 提升页面加载性能与体验第十一篇:从PWA离线工具集workbox中学习各类离线策略(写作中…)参考资料Generic Event Delivery Using HTTP Pus (draft-ietf-webpush-protocol-12)FCM简单介绍How Push Works ...

November 6, 2018 · 3 min · jiezi