From 33058f2b292b3a581333bdfb21b8f671898c5060 Mon Sep 17 00:00:00 2001 From: Peter Bengtsson Date: Tue, 8 Dec 2020 14:40:17 -0500 Subject: initial commit --- .../progressive_web_apps/app_structure/index.html | 274 +++++++++++++++++++++ 1 file changed, 274 insertions(+) create mode 100644 files/zh-cn/web/progressive_web_apps/app_structure/index.html (limited to 'files/zh-cn/web/progressive_web_apps/app_structure/index.html') diff --git a/files/zh-cn/web/progressive_web_apps/app_structure/index.html b/files/zh-cn/web/progressive_web_apps/app_structure/index.html new file mode 100644 index 0000000000..2ffb7591e0 --- /dev/null +++ b/files/zh-cn/web/progressive_web_apps/app_structure/index.html @@ -0,0 +1,274 @@ +--- +title: PWA 结构 +slug: Web/Progressive_web_apps/App_structure +translation_of: Web/Progressive_web_apps/App_structure +--- +
{{PreviousMenuNext("Web/Apps/Progressive/Introduction", "Web/Progressive_web_apps/Offline_Service_workers", "Web/Progressive_web_apps")}}
+ +

现在,我们已经知道了 PWAs 背后的原理, 让我们来看一个推荐的 PWA 结构,这来自一个真实的应用 。我们从分析 js13kPWA 这个应用开始:为什么它要这样构建?这样做有什么好处?

+ +

应用架构

+ +

渲染网站主要有两种方法 - 在服务器上或在客户端上。它们都有其优点和缺点,你可以适当地混合使用这两种方法

+ + + +

将SSR与CSR混合可以获得最佳结果 - 您可以在服务器上渲染网站,缓存其内容,然后在客户端需要时更新渲染。 由于SSR,第一页加载很快,并且页面之间的导航是平滑的,因为客户端可以仅使用已更改的部分重新渲染页面。

+ +

PWA可以使用您喜欢的任何方法构建,但有些方法会更适合。最流行的方法是“app shell”概念,它完全按照上述方式混合SSR和CSR,此外还遵循“离线优先”方法,我们将在后续文章中详细解释并在我们的示例应用程序中使用。还有一种涉及Streams API的新方法,我们将简要提及。

+ +

App shell(可以理解为程序的外壳)

+ +

App shell意图尽快加载最小的用户界面,然后缓存它,以便在后续访问时可以离线使用,然后加载应用程序的所有内容。这样,下次有人从设备访问应用程序时,UI立即从缓存加载,并从服务器请求新内容(如果它已在缓存中不可用)。

+ +

这种结构很快,并且当用户立即看到内容而不是加载动画或空白页时也感觉很快。如果网络连接不可用,它还允许离线访问网站。

+ +

我们可以通过service worker控制从服务器请求的内容以及从缓存中检索的内容,这将在下一篇文章中详细解释 - 现在让我们关注结构本身。

+ +

我为什么要用它?

+ +

这种架构允许网站从所有 PWA 功能中获益最多 - 它可以缓存应用 app shell 并以极大地提高性能的方式管理动态内容。 除了基本 shell 之外,您还可以添加其他功能,例如添加到主屏幕推送通知,你可以放心如果用户的浏览器不支持应用程序仍然可以正常工作 - 这是渐进增强的美妙之处。

+ +

该网站感觉就像一个原生应用,具有即时交互和可靠的性能,同时保留了网络的所有好处。

+ +

可链接、渐进式和响应式

+ +

记住 PWA 的优点并在设计应用程序时牢记这一点非常重要。 app shell 方法允许网站:

+ + + +

不同的概念:流

+ +

使用 Streams API 可以实现完全不同的服务器端或客户端渲染方法。在service worker帮助下,流可以极大改进我们解析内容的方式。 

+ +

应用程序外壳模型要求在网站开始呈现之前可以使用所有资源。 它与HTML不同,因为浏览器实际上已经流式传输数据,您可以看到元素在网站上加载和呈现的时间。 但是,为了使JavaScript“可操作”,必须完整地下载它。

+ +

Streams API允许开发人员直接访问来自服务器的数据流 - 如果您想对数据执行操作(例如,向视频添加过滤器),则不再需要等待所有数据流 下载并转换为blob(或其他) - 您可以立即开始。 它提供细粒度控制 - 流可以启动,与另一个流链接,取消,检查错误等等。

+ +

从理论上讲,流媒体是一种更好的模型,但它也更复杂,在撰写本文时(2018年3月),Streams API仍然是一项正在进行的工作,并且尚未在任何主流浏览器中完全可用。 当它可用时,它将是提供内容的最快方式 - 在性能方面的好处将是巨大的。

+ +

有关工作示例和更多信息,请参阅 Streams API documentation.

+ +

示例应用的结构

+ +

js13kPWA 这个网站的结构比较简单: 他包含一个HTML页面(index.heml),一个CSS样式表(style.css),一些图片,js脚本和字体,它的文件结构如下所示:

+ +

Folder structure of js13kPWA.

+ +

HTML页面

+ +

从HTML页面的角度,有一个id为content的标签,这个标签外部的所有内容,都是app shell(程序外壳)

+ +
<!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标签包含了一些基本的内容,例如标题,网页描述,CSS引用,web manifest文件,game.js和app.js,整个程序的初始化工作,会在app.js中完成。body标签中包括header,main和footer三个部分,header中包含了一张图片,main标签包含了网页标题、描述和一个区域,用来放置网页的主体,我们称之为content,footer内部则放置作者信息和链接(原文:copy and links)

+ +

这个应用的唯一工作就是列出js13kGames 2017年比赛中的A-Frame(一个用来构建虚拟现实(VR)应用的网页开发框架,译者注) 项目列表。如你所见,这是一个很普通的单页应用,目的是用一个简单的东西来展示PWA的真实功能。

+ +

CSS部分

+ +

CSS部分也是尽可能的简单:运用@font-face来加载和使用自定义字体以及给HTML元素提供简单的样式,总体的目标是让页面在移动端和桌面设备上运行(通过使用响应式布局)

+ +

主要的JavaScript代码

+ +

app.js所做的一些工作我们会在下一篇文章中详细分析,首先它用下面的模板生成了content中的内容

+ +
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

+ +

最后我们来快速浏览一下 service worker相关的文件sw.js,他首先映入game.js这个文件file:

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

接着,程序会对app shell和主体内容(content)里面的数据创建一个缓存列表

+ +
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将从缓存中请求content中所需的数据,从而提供离线应用功能

+ +
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文件夹下面,以JavaScript对象的形式提供(games.js):

+ +
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文件夹下面都有属于它自己的图片。这些就是我们的内容数据,我们通过js将这些数据加载到主体内容中

+ +

下一步

+ +

下一篇文章我们会探讨更多的细节,关于service worker.如何帮助我们缓存app shell 和内容,从而让我们实现离线功能

+ +

{{PreviousMenuNext("Web/Apps/Progressive/Introduction", "Web/Apps/Progressive/Offline_Service_workers", "Web/Apps/Progressive")}}

-- cgit v1.2.3-54-g00ecf