30分钟从零开始教会你什么是PWA

clipboard.png

前置知识储备

PWA ( Progressive Web Apps )是网络上谈论最多的技术变革之一,在IT界从业者中得到了史无前例的势头。若是你是为web构建的,我相信PWA是添加到你的工做词汇中的最新“流行语”。这并不奇怪,由于PWA已经实现了在手机上安装web应用程序的高不可攀的梦想。javascript

关于PWA的建设和它的优点,已经有了不少的焦点和“极客之谈”。大多数介绍PWA的尝试,特别是对新手来讲,彷佛都是行话,或者代码太多,可能会使他们不敢迈出第一步。在这篇文章中,我想要以一个简单的案例来教会各位如何起步。css

关于PWA的概念以及前世此生我这边不会过多赘述,网络上有不少更加专业的文章供你学习,这篇文章只负责教会你如何使用它。html

比起用一篇文章打消你的全部关于PWA的困惑来讲我更但愿你能简单了解概念以后将个人案例敲打一遍后再回过头去深刻了解PWA。java

什么是PWA
下一代 Web 应用模型 —— Progressive Web App
PWA官网webpack

起步

// 建立一个简单的项目

mkdir pwa-project
cd pwa-project
touch index.html
touch app.js
touch style.css

相信各位都是使用chrome最新版的高端技术人才,这里为了省略webpack一些繁琐的配置咱们直接编写es6代码运行在chrome便可git

编写代码

假设咱们有一个新闻站点,须要展现标题,图片,文章,而且根据不一样的来源切换内容
根据这个要求咱们能够编写如下代码
// index.html

<!DOCTYPE html>
<html lang="en">
<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>News</title>
    <link rel="stylesheet" href="./style.css">
</head>
<body>
    <header>
        <h1>News</h1>
        <select id="sourceSelector"></select>
    </header>

    <main></main>

    <script src="./app.js"></script>
</body>
</html>
// style.css

html {
    line-height: 1.15;
    /* 1 */
    -webkit-text-size-adjust: 100%;
    /* 2 */
}

body {
    margin: 0;
}

h1 {
    font-size: 2em;
    margin: 0.67em 0;
}

a {
    background-color: transparent;
    text-decoration: none;
}

button,
select {
    text-transform: none;
}

这里为了模拟真实用户数据咱们能够去👇这里申请apikey获取一些真实数据
各位也能够直接copy代码来学习,代码逻辑很简单这里不作讲解。es6

// app.js

const apiKey = 'fa35a325ddfa4c4798102ebb76809bbb';
const main = document.querySelector('main');
const sourceSelector = document.querySelector('#sourceSelector');
const defaultSource = 'techcrunch'

// 页面加载后执行逻辑
window.addEventListener('load', async e => {
    updateNews();
    await updateSources();
    sourceSelector.value = defaultSource;

    sourceSelector.addEventListener('change', e => {
        updateNews(e.target.value);
    });
    
    // 判断浏览器是否支持serviceWorker
    if ('serviceWorker' in navigator) {
        try {
            // 尝试注册serviceWorker到sw.js文件中
            navigator.serviceWorker.register('sw.js');
            console.log('SW registered');
        } catch (error) {
            console.log('SW reg failed');
        }
    }
});

// 获取新闻来源
async function updateSources() {
    const res = await fetch(`https://newsapi.org/v2/sources?apiKey=${apiKey}`);
    const json = await res.json();

    sourceSelector.innerHTML = json.sources
        .map(src => `<option value="${src.id}">${src.name}</option>`)
        .join('\n');
}

// 根据来源获取新闻数据
async function updateNews(source = defaultSource) {
    const res = await fetch(`https://newsapi.org/v2/top-headlines?sources=${source}&apiKey=${apiKey}`);
    const json = await res.json();

    main.innerHTML = json.articles.map(createArticle).join('\n');
}

// 建立文章
function createArticle(article) {
    return `
        <div class="article">
            <a href="${article.url}">
                <h2>${article.title}</h2>
            </a>
            <img src="${article.urlToImage}" />
            <p>${article.description}</p>
        </div>
    `;
}

启动

如今启动咱们的项目
执行npx http-server打开咱们的localhost:8080端口(端口号根据具体状况而定)github

咱们点开控制台看看是否是报错了?web

clipboard.png

在点开这里咱们能够发现找不到sw.js这个文件,由于咱们根本没有嘛!😂chrome

clipboard.png

既然没有那咱们写一个不就行了-_-!!

编写sw.js

首先咱们什么都不写,直接建立sw.js文件到项目中就不会报错了,可是什么都没写就意味着你什么都没有作。
那咱们到底能够用这个文件作什么事呢?

缓存咱们的静态资源文件

// sw.js

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

// sw.js首次被注册时候触发
self.addEventListener('install', async event => {
    const cache = await caches.open('news-static');
    cache.addAll(staticAssets);
})

如今咱们再次刷新页面(记得清理缓存哈)就能够看到咱们的静态资源都被缓存起来了。

clipboard.png

什么?没有缓存起来?
那你确定是没有告诉浏览器刷新时候要更新你的sw.js文件。能够勾选这里,而后再次刷新。

clipboard.png

可是缓存是缓存起来了,咱们要是不拿来用那也没啥卵用。
因此咱们要拦截请求并告诉浏览器咱们要使用这些缓存。

// sw.js (添加如下代码)

// sw监听到fetch事件时候触发
self.addEventListener('fetch', event => {
    const req = event.request;

    event.respondWith(cacheFirst(req));
});

// 使用浏览器缓存
async function cacheFirst(req) {
    const cachedResponse = await caches.match(req);
    return cachedResponse || fetch(req);
}

编写完这些以后咱们能够先勾选offline按钮,以后刷新页面,发现咱们的站点依然有数据。

咱们点开network看看请求。

clipboard.png

咱们发现咱们拦截了http请求而且将浏览器的缓存数据返回回去了!

是否是很神奇😊!

到这一步,相信你已经见识到了PWA的一些能力了。

正确使用缓存

在上面的代码中实际上是有一些问题的,咱们在离线状态下使用缓存是ok的,但是若是咱们处于联网状态状况下,咱们还须要返回缓存吗?固然不须要,不只不须要,咱们还应该用服务器的最新数据更新咱们的缓存。

因此咱们要根据网络优先的原则修改下sw逻辑。

self.addEventListener('fetch', event => {
    const req = event.request;
    const url = new URL(req.url);
    
    // 当本地开发时候能够这么配置
    if (url.origin === location.origin) {
        event.respondWith(cacheFirst(req));
    } else if ((req.url.indexOf('http') !== -1)) {
        // chrome的https协议限制,接口必须知足https
        event.respondWith(networkFirst(req));
    }
});

// 缓存优先
async function cacheFirst(req) {
    const cachedResponse = await caches.match(req);
    return cachedResponse || fetch(req);
}

// 网络优先
async function networkFirst(req) {
    // 将请求到的数据缓存在id为news-dynamic中
    const cache = await caches.open('news-dynamic');

    try {
        const res = await fetch(req); // 获取数据
        cache.put(req, res.clone()); // 更新缓存
        return res;
    } catch (error) {
        return await cache.match(req); // 报错则使用缓存
    }
}

至此咱们的案例基本上完成了,可是仍是能够再次优化一下。

假设用户尚未查看过咱们的站点页面其余内容,也就是说咱们的缓存不完整,这个时候能够提供一个mock数据提示用户等待能够联网的时候再来查看当前页面。(具体文件能够从个人github仓库中获取)

PWA就这么点能力吗?

其实PWA还提供给咱们将web站点以app图标形式放置在桌面以及移动手机中。

这里配置与编写浏览器插件过程有点相似。

咱们须要一个manifest.json文件,并在index.html中引入。

manifest.json能够在这个站点中生成。

// html文件须要引入一行代码

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

配置成功后咱们能够在控制台中查看

clipboard.png

有了这个文件和相应图标后咱们就能够添加至桌面端了,是否是很酷炫~

未完待续

截止项目代码可在个人仓库中获取。(可否给个小星星鼓励下呢😂)

后续会补充在具体项目中应该怎么集成pwa,这篇文章只分析了pwa的核心代码~

相关文章
相关标签/搜索