美文网首页
PWA架构【翻译】

PWA架构【翻译】

作者: zydmayday | 来源:发表于2019-01-21 22:12 被阅读0次

PWA系列:

  1. PWA简介【翻译】
  2. PWA架构【翻译】
  3. 利用Service workders使得PWA支持离线工作【翻译】
  4. 让PWA可安装【翻译】
  5. 利用消息推送增强PWA用户粘度【翻译】

一个app的架构

通常有两种主要的方式来渲染一个网站——在客户端或者在服务端。这两种方式各有优缺点,你也可以折中取得一个好的平衡。

  • 服务端渲染(SSR)表示网站是在服务器端渲染的,所以第一次加载时通常较快,但是当需要跳转页面时,你需要下载所需要的全部内容才能完成加载。有很多工具可以帮助构建网站,但是会导致加载速度的问题,以及性能问题——因为每一次新页面的加载都需要服务端重新执行一次。
  • 客户端渲染(CSR)允许网页在访问不同的页面时几乎是瞬时地加载,作为代价需要在第一次加载和渲染时花费更多的时间。因此反过来说,第一次加载时较慢,之后会较快。

混合使用CSR和SSR是一个良好的方案——在服务端渲染网站,缓存,当需要更新页面内容时采用CSR来完成。第一次加载由于使用了SSR所以通常会较快,页面间的跳转也会因为使用了CSR而更加流畅。

你可以使用任何技术来实现PWA,但是通常有些技术会更好。最流行的一个要数应用壳(app shell)概念了,这种概念恰好融合了CSR和SSR技术,同时符合“离线第一”原则。之后我们会详细介绍细节。另外还有一个使用了Streams API的全新方案,稍后会细讲。

App shell

App shell保证先以最快的速度加载最基本的用户界面并缓存它,之后就算离线也能访问到应用,然后再进一步加载App的具体内容。这样,下次有用户从该设备访问app时,UI可以直接从缓存加载,同时新的内容可以从服务器获取(或者从缓存中)。

这样的架构会让用户能立刻看到页面内容,而不是有一个空白页一直在那里加载。同时在网络不通畅的时候也能顺利访问。

我们可以用service worker来控制是向服务器发起请求还是从缓存中获取数据,在下一篇文章中我们会详细进行介绍——现在我们只关注架构本身。

为什么用这个?

这样的架构使得我们可以享受到PWA的几乎所有的特性——它缓存了app shell并且通过动态管理显示内容大大的提高了性能。除了基础的shell之外,你也可以增加比如添加到主屏幕或者消息推送这样的特性,请放心即使用户的浏览器不支持这些特性app也可以正常运行——这就是渐进式增强的美学所在。

这样网站看起来就像个本地应用一般,有着快速的响应和坚实的性能,同时保留了作为web应用的优势。

可访问(being linkable),渐进式和响应式设计

我们需要在设计网站时时刻记住PWA的优势。app shell允许网站:

  • 可访问:即使看起来像个本地应用,请记住它仍然是个网站——你可以点击页面中的连接并分享给你的朋友。
  • 渐进式:先从“好用的,旧式的网站”出发,一步步渐进式的增加新特性,记住要随时侦测浏览器是否可用这些新增加的特性,同时注意处理任何由于浏览器不支持而导致的error。例如,service workers可以让离线工作成为可能,同时提高网站的体验,但是记住就算没有service worker网站也应该能运行良好。
  • 响应式:响应式页面设计也适用于渐进式web应用,主要是针对移动端设备。有许多不同的设备配置有浏览器——你需要让网站支持不同的屏幕尺寸,视窗(viewport)或者是不同的像素密度(pixel density),常用的技术有viewport meta tagCSS media queriesFlexbox

不同的概念:streams

还有另一个完全不同的实现SSR/CSR的技术叫做Streams API。借助service worker的小小帮助,streams可以极大的提高解析内容的速度。

app shell模型要求在渲染之前保证所有的资源可用。而之于HTML,浏览器实际上要先加载好所有的数据,然后你可以查看页面元素是何时加载并渲染在网站上的。为了让JavaScript可用,实际上需要将JS文件全部加载后才能使用。

Streams API允许开发者直接向服务器访问数据流——如果你想要对数据进行操作(比如为视频增加一个过滤器),你不需要等到数据完全加载并转换成一个blob(或者是别的格式)——你可以立刻进行操作。它提供了细粒度的控制——你可以从其他的流来启动一个流,或者链接流,取消流,error检查,and more。

理论上来说,streaming是个更好的模型,但是也更复杂,现时间点(2018年3月)Streams API仍在筹划中,大多数的主流浏览器也尚未支持。当该技术可用时,它将是处理内容的最快方法——对性能的提高将是巨大的。

查看Streams API documentation获取更多信息。

一个例子来解释架构

js13kPWA提供了一个简单的网站架构的例子:一个HTML文件,一些基础的CSS,一些图片,脚本和字体文件。文件夹如下:

[图片上传失败...(image-234357-1548861690300)]

HTML

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>js13kGames A-Frame entries</title>
    <meta name="description" content="A list of A-Frame entries submitted to the js13kGames 2017 competition, used as an example for the MDN articles about Progressive Web Apps.">
    <meta name="author" content="end3r">
    <meta name="theme-color" content="#B12A34">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta property="og:image" content="icons/icon-512.png">
    <link rel="shortcut icon" href="favicon.ico">
    <link rel="stylesheet" href="style.css">
    <link rel="manifest" href="js13kpwa.webmanifest">
    <script src="data/games.js" defer></script>
    <script src="app.js" defer></script>
</head>
<body>
<header>
    <p><a class="logo" href="http://js13kgames.com"><img src="img/js13kgames.png" alt="js13kGames"></a></p>
</header>
<main>
    <h1>js13kGames A-Frame entries</h1>
    <p class="description">List of games submitted to the <a href="http://js13kgames.com/aframe">A-Frame category</a> in the <a href="http://2017.js13kgames.com">js13kGames 2017</a> competition. You can <a href="https://github.com/mdn/pwa-examples/blob/master/js13kpwa">fork js13kPWA on GitHub</a> to check its source code.</p>
    <button id="notifications">Request dummy notifications</button>
    <section id="content">
        // Content inserted in here
    </section>
</main>
<footer>
    <p>© js13kGames 2012-2018, created and maintained by <a href="http://end3r.com">Andrzej Mazur</a> from <a href="http://enclavegames.com">Enclave Games</a>.</p>
</footer>
</body>
</html>

<head>包含了一些基本信息,例如title,description,一些CSS的links,web清单(manifest),games.js和app.js——这个是我们的JavaScript应用的初始化入口。<body>包含<header>(包含链接的图片),<main>(有标题,描述和一些其他的内容),<footer>(copy和links)。

用这种简单的结构我们可以撇去无关的枝节,关注实现的PWA特性本身。

CSS

@font-face让我们可以使用自定义的字体,还有一些其他的基本的CSS样式。主要的目的是让网站在各个设备上都能良好的显示。

主应用的JavaScript

我们会在下一篇文章中详说app.js做的工作。首先它用下述的模板生产了内容。

var template = "<article>\n\
    <img src='data/img/SLUG.jpg' alt='NAME'>\n\
    <h3>#POS. NAME</h3>\n\
    <ul>\n\
    <li><span>Author:</span> <strong>AUTHOR</strong></li>\n\
    <li><span>Twitter:</span> <a href='https://twitter.com/TWITTER'>@TWITTER</a></li>\n\
    <li><span>Website:</span> <a href='http://WEBSITE/'>WEBSITE</a></li>\n\
    <li><span>GitHub:</span> <a href='https://GITHUB'>GITHUB</a></li>\n\
    <li><span>More:</span> <a href='http://js13kgames.com/entries/SLUG'>js13kgames.com/entries/SLUG</a></li>\n\
    </ul>\n\
</article>";
var content = '';
for(var i=0; i<games.length; i++) {
    var entry = template.replace(/POS/g,(i+1))
        .replace(/SLUG/g,games[i].slug)
        .replace(/NAME/g,games[i].name)
        .replace(/AUTHOR/g,games[i].author)
        .replace(/TWITTER/g,games[i].twitter)
        .replace(/WEBSITE/g,games[i].website)
        .replace(/GITHUB/g,games[i].github);
    entry = entry.replace('<a href=\'http:///\'></a>','-');
    content += entry;
};
document.getElementById('content').innerHTML = content;

接下来,它注册了一个service worker:

if('serviceWorker' in navigator) {
    navigator.serviceWorker.register('/pwa-examples/js13kpwa/sw.js');
};

下一步,当点击按钮时允许消息推送:

var button = document.getElementById("notifications");
button.addEventListener('click', function(e) {
    Notification.requestPermission().then(function(result) {
        if(result === 'granted') {
            randomNotification();
        }
    });
});

最后,从游戏列表中随机抽取项目来进行推送。

function randomNotification() {
    var randomItem = Math.floor(Math.random()*games.length);
    var notifTitle = games[randomItem].name;
    var notifBody = 'Created by '+games[randomItem].author+'.';
    var notifImg = 'data/img/'+games[randomItem].slug+'.jpg';
    var options = {
        body: notifBody,
        icon: notifImg
    }
    var notif = new Notification(notifTitle, options);
    setTimeout(randomNotification, 30000);
}

service worker

我们来看最后一个文件:sw.js——首先从games.js中导入数据:

self.importScripts('data/games.js');

接下来,创建一个需要缓存的文件列表,包括app shell和内容:

var cacheName = 'js13kPWA-v1';
var appShellFiles = [
  '/pwa-examples/js13kpwa/',
  '/pwa-examples/js13kpwa/index.html',
  '/pwa-examples/js13kpwa/app.js',
  '/pwa-examples/js13kpwa/style.css',
  '/pwa-examples/js13kpwa/fonts/graduate.eot',
  '/pwa-examples/js13kpwa/fonts/graduate.ttf',
  '/pwa-examples/js13kpwa/fonts/graduate.woff',
  '/pwa-examples/js13kpwa/favicon.ico',
  '/pwa-examples/js13kpwa/img/js13kgames.png',
  '/pwa-examples/js13kpwa/img/bg.png',
  '/pwa-examples/js13kpwa/icons/icon-32.png',
  '/pwa-examples/js13kpwa/icons/icon-64.png',
  '/pwa-examples/js13kpwa/icons/icon-96.png',
  '/pwa-examples/js13kpwa/icons/icon-128.png',
  '/pwa-examples/js13kpwa/icons/icon-168.png',
  '/pwa-examples/js13kpwa/icons/icon-192.png',
  '/pwa-examples/js13kpwa/icons/icon-256.png',
  '/pwa-examples/js13kpwa/icons/icon-512.png'
];
var gamesImages = [];
for(var i=0; i<games.length; i++) {
  gamesImages.push('data/img/'+games[i].slug+'.jpg');
}
var contentToCache = appShellFiles.concat(gamesImages);

接下来,安装service worker,然后缓存上述的文件列表:

self.addEventListener('install', function(e) {
  console.log('[Service Worker] Install');
  e.waitUntil(
    caches.open(cacheName).then(function(cache) {
      console.log('[Service Worker] Caching all: app shell and content');
      return cache.addAll(contentToCache);
    })
  );
});

最后,当离线工作时,service worker会从缓存的内容中获取信息:

self.addEventListener('fetch', function(e) {
  e.respondWith(
    caches.match(e.request).then(function(r) {
      console.log('[Service Worker] Fetching resource: '+e.request.url);
      return r || fetch(e.request).then(function(response) {
        return caches.open(cacheName).then(function(cache) {
          console.log('[Service Worker] Caching new resource: '+e.request.url);
          cache.put(e.request, response.clone());
          return response;
        });
      });
    })
  );
});

JavaScript data

var games = [
    {
        slug: 'lost-in-cyberspace',
        name: 'Lost in Cyberspace',
        author: 'Zosia and Bartek',
        twitter: 'bartaz',
        website: '',
        github: 'github.com/bartaz/lost-in-cyberspace'
    },
    {
        slug: 'vernissage',
        name: 'Vernissage',
        author: 'Platane',
        twitter: 'platane_',
        website: 'github.com/Platane',
        github: 'github.com/Platane/js13k-2017'
    },
// ...
    {
        slug: 'emma-3d',
        name: 'Emma-3D',
        author: 'Prateek Roushan',
        twitter: '',
        website: '',
        github: 'github.com/coderprateek/Emma-3D'
    }
];

每一个条目的图片都保存在data/img文件夹中。

下一步

在下一篇文章中,我们会探索app shell的更多细节,并理解service worker是如何在离线环境中缓存并抓取数据的。

相关文章

网友评论

      本文标题:PWA架构【翻译】

      本文链接:https://www.haomeiwen.com/subject/azghjqtx.html