在浏览网站的时候,你有看到过上面第一张图的“添加到主屏幕(add to home screen)”弹出框吗?当你按下这个按钮时,这个 “APP” 就会自动在后台安装,然后出现在你手机的 APP 列表里,接下来你就可以在手机操作它了,在网页端运行的各种操作在手机上也能运行。

这样你就从 Web 应用”下载“了一个移动 APP,甚至不需要打开手机的应用商店就下载好了。

安装这样一个 APP 竟然如此简单!不过更厉害的是,你甚至可以在没有网络的时候离线浏览这个 APP 里的内容。

这就是 PWA(Progressive Web App),你可以直接通过浏览器安装,它可以像原生 APP 一样在线或离线在手机上使用。

那么,Progressive 在这里是什么意思?为什么我认为它比原生 APP 更好?它与传统的 Web 应用又有什么不同?我们来更深入地研究什么是 PWA。

什么是 PWA

PWA 这一概念是由 Alex Russell 和 Frances Berriman 提出的。引用 Alex 的话:

Progressive Web Apps are just websites that took all the right vitamins. PWA 其实就是新增了各种有用特性的网页。


它不是什么新的框架或技术,而是一套最佳实践,让一个 Web 应用在功能上与桌面或移动 APP 类似,进而打造无缝集成的体验,以至于用户都难以分辨 PWA 与原生移动 APP 之间的区别。

为什么我们需要 PWA

网速:你所居住的地方可能没有网速问题,但是全球 60% 的人口仍在使用 2G 互联网。即使在美国,有些人还是不得不使用拨号上网。

网页加载速度:如果网页加载太慢,你知道用户等多少时间就会点击“关闭”按钮吗?3 秒钟!如果网页加载太慢,则有 53% 的用户会选择直接关掉标签页。

APP 安装率:用户不想安装原生 APP,普通用户一个月内平均安装的 APP 数量不到 1 个。

用户粘性:用户使用原生 APP 的时间很多,但移动端网页的访问量几乎是原生 APP 的三倍。使用原生 APP 的用户把 80% 的时间都花在了前三名的原生 APP 上。

image-12
移动端 Web 应用和原生 APP 用户粘性


PWA 有很多优势,这里列出它最有帮助的几项:

速度快:PWA 可以持续提供快速的用户体验,从开始下载到可以使用,整个过程都非常快。并且它可以缓存数据,即使没有网络,用户也可以再次快速启动这个 APP。

无缝集成的用户体验:PWA 用起来就像原生 APP 一样,它们可以放在用户的主屏幕上,发送推送通知,并可以访问设备的各项功能。

支持离线使用: 在 Service Worker 的帮助下,即使网络出现问题,PWA 也可以在用户的屏幕上正常渲染图片。

用户粘性:PWA 可以向用户推送通知,唤醒用户,与用户保持连接。

如何搭建一个 PWA

按照 Google 的标准,将一个 Web 应用转换为 PWA,至少需要具备以下四点:

Web App Manifest

image-13
manifest.json 示例

这只是一个提供 Web 应用相关的 meta 信息的 json 文件,它有诸如 APP 图标(用户在将其安装到设备中后会看到的图标),APP 的背景颜色,APP 的名称,简称等信息。我们可以自己编写 manifest 文件,也可以使用工具生成一个 manifest 文件。

image-14
用 Google 的工具自动生成 manifest 文件

Service Workers

Service Worker 是事件驱动的服务,在 APP 的后台运行,并充当网络和 APP 之间的代理,能够拦截网络请求并在后台为我们缓存信息。这可用于加载数据以供离线使用。它是一个 JavaScript 脚本,用于侦听诸如获取和安装之类的事件,并执行任务。

self.addEventListener('fetch', event => {
    //caching for offline viewing
    const {request} = event;
    const url = new URL(request.url);
    if(url.origin === location.origin) {
        event.respondWith(cacheData(request));
    } else {
        event.respondWith(networkFirst(request));
    }
});
async function cacheData(request) {
    const cachedResponse = await caches.match(request);
    return cachedResponse || fetch(request);
}
serviceworker.js 示例

图标

这是当用户在设备上安装 PWA 时显示的 APP 图标, 一般的 jpeg 图片就可以了。我在上面贴出的 manifest 文件里有列出多种尺寸的图标,非常有用。

使用 HTTPS

为了让 Web 应用成为 PWA,必须要通过安全网络为它提供服务。借助 Cloudfare 和 LetsEncrypt 之类的服务,获取 SSL 证书确实非常容易。成为安全站点不仅是最佳实践,而且还将 Web 应用建立为值得用户信任的站点,以获得用户的信任和依赖,并避免中间人攻击。

我们已经知道了什么是 PWA,接下来,我们不用任何框架,只用 DOM 操作自己动手构建一个 PWA。

我们先来迅速回顾上文,将一个 Web 应用转换为 PWA,至少需要以下 4 点:

- 一个manifest文件——manifest.json

- 至少带有一个fetch事件的service worker——serviceworker.js

- 一个图标——icon.jpeg

- 通过 HTTPS 通信——https://www.myawesomesite.com

在这篇教程中,我将介绍前面两点——创建一个manifest文件并挂载一个service worker。

目标

在下面的例子中,我们将创建一个简单的 PWA。我把复杂度降到最低,以便你更好地理解 PWA 的概念。在完成之后,你可以试着把这些 PWA 的概念应用到你的 Angualr/React/Vue 或者 Vanilla JS 应用里。

我们这次要做一个表情包应用,从giphy.com上获取最新的流行的表情包,然后展示在 APP 上。这是一个可以离线使用的 APP,在完成后,用户将可以在没有网的时候浏览这些表情包。

现在我们开始学习其中重要的部分。

如下,这是一个简单的index.html,它显示今天流行的表情包,没什么特别的。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>All the memes!</title>
    <link rel="stylesheet" href="/styles.css">
</head>
<body>
<header>
    <h1 class="center">Top trending memes today</h1>
</header>
<main>
    <div class="container"></div>
</main>
<script src="app.js"></script>

</body>
</html>

接下来,我们添加一个可以获取giphy.com上的表情包的功能,这是获取表情包的函数。

async function fetchTrending() {
    const res = await fetch(`https://api.giphy.com/v1/gifs/trending?api_key=${apiKey}&limit=25`);
    const json = await res.json();

    main.innerHTML = json.data.map(createMeme).join('\n');
}

我们来把它变成 PWA 吧

第 1 步:manifest 文件

看过前面的内容,你应该知道manifest是个json文件。它存储着 APP 的一些元信息,比如图标名称、背景颜色、APP 名称等等。这是一个拥有上述信息的manifest.json文件:

{
  "name": "Meme",
  "short_name": "Meme",
  "icons": [{
    "src": "images/icons/icon-128x128.png",
      "sizes": "128x128",
      "type": "image/png"
    }, {
      "src": "images/icons/icon-144x144.png",
      "sizes": "144x144",
      "type": "image/png"
    }, {
      "src": "images/icons/icon-152x152.png",
      "sizes": "152x152",
      "type": "image/png"
    }, {
      "src": "images/icons/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    }, {
      "src": "images/icons/icon-256x256.png",
      "sizes": "256x256",
      "type": "image/png"
    }],
  "start_url": "/index.html",
  "display": "standalone",
  "background_color": "#3E4EB8",
  "theme_color": "#2F3BA2"
}

你还可以用一个工具来生成一个manifest文件。这是我发现的一个很有用的工具:

image-15
Web App manifest generator

把这添加到我们的 APP 里非常简单,只要在index.html里添加这样一行:

<link rel="manifest" href="/manifest.json">


第 2 步:Service Worker

我们创建以下 serviceworker.js文件。首先,我们要让 Service Worker 在安装完成后马上被挂载。然后,我们再缓存一些静态资源,比如style.cssapp.js。接下来,我们再通过fetch来让 APP 可以离线使用。

const staticAssets = [
    './',
    './styles.css',
    './app.js'
];

self.addEventListener('install', async event => {
    const cache = await caches.open('static-meme');
    cache.addAll(staticAssets);
});

self.addEventListener('fetch', event => {
    const {request} = event;
    const url = new URL(request.url);
    if(url.origin === location.origin) {
        event.respondWith(cacheData(request));
    } else {
        event.respondWith(networkFirst(request));
    }

});

async function cacheData(request) {
    const cachedResponse = await caches.match(request);
    return cachedResponse || fetch(request);
}

async function networkFirst(request) {
    const cache = await caches.open('dynamic-meme');

    try {
        const response = await fetch(request);
        cache.put(request, response.clone());
        return response;
    } catch (error){
        return await cache.match(request);

    }

}

我们把代码拆开来看:一个 Service Worker 可以帮我们缓存数据并获取资源,如果缓存里有数据,就从缓存里获取,如果没有,就通过网络获取。对于你自己的 APP 来说,你要好好想想,你有什么功能是需要能够离线使用的,然后你再去缓存那些相关的数据。对这里的例子来说,在没有网络的时候,APP 会展示之前缓存过的图片。

我们要把 Service Worker 加进index.html,为此,我们需要借助浏览器的navigator库来挂载 Service Worker。

window.addEventListener('load', async e => {
    await fetchTrending();

    if ('serviceWorker' in navigator) {
        try {
            navigator.serviceWorker.register('serviceWorker.js');
            console.log('SW registered');

        } catch (error) {
            console.log('SW failed');

        }
    }
});

我们来验证一下有没有成功挂载。进入浏览器的开发者工具里的 Application 标签,这个标签在我们开发 PWA 时非常常用。刷新一下,你应该就可以在这里发现一个 Service Worker:

image-16
Service Worker 成功挂载了

现在我们再刷新一下,在第一次载入时,数据应该已经被 Service Worker 成功缓存了。试试断开网络,离线状态我们还是可以看到这些图片。

我们的 APP 现在已经可以离线使用啦!如果你有开启 HTTPS 并上传了一个图标,那恭喜你,你已经成功搭建一个 PWA 了!

下一步

如果你有兴趣自己做一个 PWA,我强烈推荐你看看这个 Codelabs

原文:https://www.freecodecamp.org/news/progressive-web-apps-101-the-what-why-and-how-4aa5e9065ac2/https://www.freecodecamp.org/news/progressive-web-apps-102-building-a-progressive-web-app-from-scratch-397b72168040/,作者:Shruti Kapoor