PWA:从零到一

1,023 阅读10分钟

本篇博客是针对PWA以及它所涉及关键技术的入门介绍,希望大家在读完博客以后能达到以下两个目的:

  • 解决关于PWA的三个问题 (What、Why、How)
  • 结合Angular从零构建一个简单的PWA应用

Q1: PWA是什么?

1. 简介

PWA(Progressive Web Apps 的简称,渐进式网页应用程序),是 Google 在 2015 年推出的一个项目,旨在将 Web 网页服务具备类似原生 App 的使用体验。

从定义上看PWA的本质还是网页应用程序,只是通过一些特定的技术模拟原生App的体验,实现了网页应用的渐进增强。渐进式意味着在不支持PWA的浏览器上,应用还是可以作为普通的Web App使用。

2. 使用步骤

接下来我们演示一下如何将使用了PWA技术的网站添加到手机主屏幕,再通过桌面入口进入以查看PWA应用。

操作系统:iOS 13.4.1 浏览器:Safari 网站:新浪微博饿了么等使用了PWA技术的网站

操作步骤:

  1. 在手机端中Safari浏览器输入网址 m.weibo.cn/beta, 打开新浪微博网站。

  2. 点击浏览器下方操作按钮,弹出操作弹框,选中添加到主屏幕按钮,显示出网站快捷入口信息后点击添加按钮

添加主屏幕操作步骤 添加主屏幕操作步骤
3. 在手机桌面已生成快捷入口,找到新浪微博图标,点击进入即可查看PWA应用。
添加主屏幕操作步骤 添加主屏幕操作步骤

说明: 由于Android与iOS上各浏览器添加到主屏幕的方式差距较大,需要大家自行探索其他浏览器添加到桌面的方式

3. 关键技术

PWA是一系列Web技术的合集,下面是其中比较关键的三个技术:

  • Manifest: 应用清单
  • Service Worker: 离线缓存
  • Push Notification: 推送通知

Q2: 为什么选择PWA?

1. 优势

我们先来看一下传统Web App与Native App存在的问题,以及相对于前两者PWA的优势在哪。

Web App

  • 手机没有入口,用户每次进入需要记住网址
  • 不具备离线能力,用户在断网的情况下无法查看网站
  • 不具备Native App的消息推送能力
  • 界面显示效果和交互体验较差

Native App

  • 软件上线需审核
  • 软件更新需要将新版本上传到不同的应用商店
  • 使用App必须下载

PWA

  • 类似原生应用的显示和交互体验:桌面图标、离线缓存、消息推送、沉浸式界面
  • 具备Web App操作和发布简单的优点:可持续更新、可链接
  • 渐进增强,在不支持PWA的浏览器上仍可以作为普通Web App进行使用
  • 无需下载,只需添加桌面入口,用完即走

2. 问题

  • 用户缺乏将网页“添加主屏幕”的意识,而且不同浏览器“添加主屏幕”的方式不一样
  • 兼容性问题:iOS仅Safari支持部分属性、Android部分浏览器支持

3. 总结

从上面的分析和对比可以看出:PWA规避了Web App和Native App的缺陷,同时将两者的优点结合,成为了功能和体验更强的Web App。虽然存在一定操作和兼容性问题,但是在不支持PWA的浏览器上我们还是可以像普通Web App一样进行使用,不会影响产品的主体功能。读者可以根据产品实际情况选择是否使用PWA。


Q3: 怎么构建PWA应用?

1.前置知识

在构建一个PWA应用前,我们需要对PWA涉及的关键技术有一个大概了解。在前面的章节我们已经知道了PWA涉及到了三个关键技术:Manifest、Service Worker、Push Notification。接下来我们会先简单介绍一下前两项技术,关于Push Notification的相关内容大家可以在入门后再去深入学习。

1.1 Manifest

Web App Manifest(应用清单) 是一个 W3C 规范,定义了一个包含应用信息的JSON List。

Manifest中通过nameicons等属性定义了PWA应用添加至桌面后的入口显示名称、图标、启动页配置和应用显示方式等。通过这个简单的JSON配置文件,可以实现类似原生应用的沉浸式界面显示效果。

普通网站与使用了Manifest配置的PWA效果对比图:

普通网站显示效果 使用了Manifest的网站显示效果

兼容性

Manifest中各配置项在iOS和Android的各个浏览器支持度有较大差异。总体来说,Android上Chrome浏览器支持度较高,所有配置项基本都支持。iOS上仅Safari浏览器支持部分属性,需要通过定义一些Meta标签来实现对应效果。

关于Manifest具体的配置项含义及使用我们会在后面的实例中再进行讲解,下面我们先来看看关于另一项关键技术Service Worker的介绍。


1.2 Service worker

Service Worker 就是一段运行在 Web 浏览器中,并为应用管理缓存的脚本。

它的功能就像一个网络代理,拦截所有由应用发出的 HTTP 请求,并选择如何给出响应。Service Worker通过提前缓存网站静态资源及拦截并缓存HTTP请求,实现网站的离线可用。

serviceWorker示例图

断网后普通网站与使用了Service Worker缓存网站的对比图:

普通网站断网后显示 使用了ServiceWorker的网站断网后显示

特点

  • 运行在worker线程中,与主浏览器线程独立,所以无法访问DOM等
  • 事件驱动,通过监听Service Worker各生命周期事件来进行缓存
  • 只能使用HTTPS和Localhost,具备较高安全性

兼容性

Android上大部分浏览器都支持,iOS仅Safari浏览器支持。

sw_caniuse.png

原理与实现

下面来分析一下Service Worker的运行周期以及用原生js如何实现Service Worker对网站静态资源的缓存。

  1. 生命周期
    下图演示了Service Worker从注册到激活的整个生命周期,只有理解清楚它存在的各个阶段,才能理解原生js通过Service worker实现网站缓存的原理。

sw_lifeCycle.png

  1. 注册阶段 要想使用Service worker,首先需要在index.html界面注册Service Worker脚本即sw.js文件。只有注册成功后,sw.js文件上的缓存设置及事件监听才会生效。

sw_register.png

  1. 安装阶段
    当Service worker注册成功且浏览器解析完成后,就会进入到Service Worker的安装阶段,这个时候我们在sw.js文件里通过监听install事件,就能进行静态资源的缓存了。

sw_install.png

  1. 激活阶段
    当Service Worker安装成功后,Service Worker就被完全激活了,此时在sw.js就可以通过监听fetch事件来进行请求的缓存和处理。关于它的具体实现,大家可以自行深入学习实践,在这不做进一步讲解。

2.用Angular来构建PWA应用

对构建PWA项目的关键技术有了基本了解之后,接下来我们通过一个pwa-demo实例来演示怎么在Angular的基础上构建一个PWA应用。

2.1 构建步骤

  1. 创建一个Angular项目
$ ng new pwa-demo
  1. 添加pwa支持
$ ng add @angular/pwa
  1. 添加本地服务器
$ npm install http-server -g
  1. 构建项目
$ ng build --prod
  1. 启动服务
http-server -p 8080 -c-1 dist/pwa-demo

2.2 项目结构

通过向pwa-demo项目中添加pwa支持,会多出两个文件:manifest.webmanifestngsw-config.json,分别用于Manifest配置和Service Worker配置。

pwa_project_struct.png

2.3 manifest配置

我们来看看如何通过PWA的应用信息配置,实现类似原生应用的桌面入口配置及界面效果。

manifest中比较常用的配置项及含义如下:

  • name: 网站应用全名,显示在Android欢迎页上
  • short_name: 显示在主屏入口上的短名
  • icons: 加到主屏幕之后的图标
  • start_url: 应用启动的主页面地址
  • display: 应用的显示方式 fullscreen / standalone / minimal-ui / browser
  • theme_color: 浏览器UI的颜色
  • background_color: 启动页背景色

说明:

  • 为了使pwa-demo项目有沉浸式界面显示效果,设置displaystandalone,表示隐藏浏览器导航栏与底部操作栏,并作为独立的应用显示。
  • 设置theme_color需要在index.html头部设置<meta name="theme-color" content="#11214f">才会生效
  • start_url设为 /,意味着从网站中任意页面点击添加到主屏幕,从主屏幕快捷进入都是进入到网站的根路径
  • icons需要提供多个像素的icon图片,Chrome 会要求你至少提供 192x192px 图标和 512x512px 图标,浏览器会选择最合适像素的图标进行入口图标的显示

manifest.webmanifest文件中的各配置项:

{
  "name": "森亿星空影院",
  "short_name": "星空影院",
  "theme_color": "#ffffff",
  "background_color": "#11214f",
  "display": "standalone",
  "scope": "/",
  "start_url": "/",
  "icons": [
    {
      "src": "assets/icons/icon-72x72.png",
      "sizes": "72x72",
      "type": "image/png",
      "purpose": "maskable any"
    },
    {
      "src": "assets/icons/icon-96x96.png",
      "sizes": "96x96",
      "type": "image/png",
      "purpose": "maskable any"
    },
    {
      "src": "assets/icons/icon-128x128.png",
      "sizes": "128x128",
      "type": "image/png",
      "purpose": "maskable any"
    },
    {
      "src": "assets/icons/icon-144x144.png",
      "sizes": "144x144",
      "type": "image/png",
      "purpose": "maskable any"
    },
    {
      "src": "assets/icons/icon-152x152.png",
      "sizes": "152x152",
      "type": "image/png",
      "purpose": "maskable any"
    },
    {
      "src": "assets/icons/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png",
      "purpose": "maskable any"
    },
    {
      "src": "assets/icons/icon-384x384.png",
      "sizes": "384x384",
      "type": "image/png",
      "purpose": "maskable any"
    },
    {
      "src": "assets/icons/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "maskable any"
    }
  ]
}

index.html中引入manifest.webmanifest文件并针对iOS做兼容配置一些meta标签:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>PwaDemo</title>
  <base href="/">
  <!--viewport-fit=cover 为适配iphoneX,使页面占满整个屏幕-->
  <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
  <!--控制状态栏、浏览器地址栏颜色-->
  <meta name="theme-color" content="#11214f">
  <!--兼容ios mainfest相关配置-->

  <!--应用展示方式 display 通过设置yes进入standalone模式-->
  <meta name="apple-mobile-web-app-capable" content="yes">
  <!--状态栏样式  default 白色 / black 黑色 / black-translucent 灰色半透明 颜色随body颜色改变-->
  <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
  <!--桌面应用名称 short_name-->
  <!--<meta name="apple-mobile-web-app-title" content="星空影院">-->
  <!--桌面图标 icons-->
  <link rel="apple-touch-icon" href="assets/icons/icon-96x96.png">

  <!--引入mainfest文件-->
  <link rel="manifest" href="manifest.webmanifest">

  <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body style="background: #11214f url('./assets/images/guidePage.jpg') no-repeat fixed top; background-size: 100% 100%; height: 100%;">
  <app-root></app-root>
  <noscript>Please enable JavaScript to continue using this application.</noscript>
</body>
</html>

2.4 Service Worker配置

在Angular中使用Service Worker比较简单,因为Angular框架本身已经封装好了关于Service Worker的缓存实现,开发人员只需通过ngsw-config.json来针对项目实际情况配置不同的缓存策略。下面我们来看看如何通过ServiceWorker缓存配置,实现网站静态资源离线可用的效果。

Service Worker中配置项及含义如下:

  • assetGroups: 静态资源组
    • name : 标识静态资源组
    • installMode: 资源最初的缓存方式 prefetch / lazy
    • updateMode: 更新后的缓存方式 prefetch / lazy
    • resources: 需要缓存的资源列表
  • dataGroups 数据资源组

说明:

  • prefetch表示提前缓存,即不管资源有没有用到,都提前先缓存下来。lazy表示当应用请求后再进行缓存
  • pwa-demo应用中配置了两种缓存策略,对htmlcssjs的初次加载进行提前缓存,其他静态资源则请求时再进行缓存

ngsw-config.json文件中的各配置项:

{
  "$schema": "./node_modules/@angular/service-worker/config/schema.json",
  "index": "/index.html",
  "assetGroups": [
    {
      "name": "app",
      "installMode": "prefetch",
      "resources": {
        "files": [
          "/favicon.ico",
          "/index.html",
          "/*.css",
          "/*.js"
        ]
      }
    }, {
      "name": "assets",
      "installMode": "lazy",
      "updateMode": "prefetch",
      "resources": {
        "files": [
          "/assets/**",
          "/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"
        ]
      }
    }
  ]
}

app.module.ts中注册ngsw-worker.js:

import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { ServiceWorkerModule } from '@angular/service-worker';
import { environment } from '../environments/environment';
// ...

@NgModule({
  declarations: [
    // ...
  ],
  imports: [
    // ...
    ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production })
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

如上步骤就构建了一个简单的PWA项目,项目源码地址在文末会贴出,大家可以下载下来自行尝试。


小结

简单总结一下文章开头提出的三个问题:

  • PWA是什么?

渐进式网页程序,通过离线缓存、应用清单、推送通知等技术实现类原生应用的效果。

  • 为什么选择PWA?

兼具原生应用与WEB应用的优点,避免了原生应用的软件上线麻烦,又对基本的WEB应用进行了功能的渐进增强。

  • 怎么构建PWA?
  1. 在WEB应用基础上配置Manifest配置文件
  2. 注册Service Worker,在单独的Worker线程实现应用文件和接口请求的缓存
  3. 通过 Push 和 Notification API 实现消息推送效果

demo

github.com/liner60/ang…

资源