如何使用Node.js构建渐进式Web应用程序(PWA)

2020年12月30日11:14:38 发表评论 43 次浏览

本文概述

可视化渐进式Web应用程序(PWA)的一种方法是将其与行为和感觉像移动应用程序的Web技术进行比较。例如, 大多数移动应用程序都有启动屏幕, 以通知用户其正在加载, 离线时保持某种功能, 并且可以快速工作, 因为用户在下载应用程序时所需的大部分资产已经在电话上。

在本教程中, 我们将演示如何构建具有离线功能并符合所有Google Lighthouse检查的PWA。

项目设置

在开始之前, 我们先概述一些要求。请注意, 这些要求仅用于本教程。你可以混合, 匹配和交换它们中的任何一个以满足你的需求和目标。

对于此项目, 你需要:

– Node.js运行服务器

– Express运行HTTP服务器

– Nodemon调试服务器

– Chrome浏览器检查网站并调试你的PWA

– OpenSSL生成自签名证书(稍后会详细介绍)

资料夹

project-folder
  |_certs
  |_public
    |_images
    |_js

档案

project-folder
  |_certs
  |_public
    |_images
    |_js
      |_pwa.js
    |_index.html
  |_package.json
  |_server.js

package.json

生成package.json与npm初始化并填写问题。拿开包装, 继续进行操作npm install express nodemon。在package.json, 添加脚本"服务器调试":" nodemon --inspect server.js".

server.js

创建一个基本的HTTP服务器来生成你的index.html一旦你在浏览器中连接到本地主机。

const express = require('express')
const path = require('path')

const httpPort = 80

const app = express()

app.use(express.static(path.join(__dirname, 'public')))

app.get('/', function(req, res) {
  res.sendFile(path.join(__dirname, 'public/index.html'))
})

app.listen(httpPort, function () {
  console.log(`Listening on port ${httpPort}!`)
})

public / index.html

<html>
  <body>
    <span>This example is for the article of progressive web apps written for notlogy</span>
    <br>
    <span>You are now</span> <span><b class="page-status">online</b></span>
    <script src="/js/pwa.js"></script>
  </body>
</html>

公共/ js / pwa.js

document.addEventListener('DOMContentLoaded', init, false);
function init() {
  console.log('empty for now')
}

在浏览器中, 访问http://本地主机以查看仅包含你的初始消息的页面。右键单击→检查在控制台上查看你的日志。

建立PWA

现在, 你已经设置了基本页面, 如何让Google将该页面识别为功能齐全的PWA?

再次检查并选择审核选项卡, 然后选择Progressive Web App, 然后运行审核。你应该以如下形式结束:

PWA的灯塔审核

我们为制作了一个自定义演示.
不完全是。点击这里查看.

如何使用Node.js构建渐进式Web应用程序(PWA)1

如你所见, 大多数结果为红色。我们将对它们进行处理, 直到每个绿色。

有些已经是绿色的, 因为:

  • 加载页面不到10秒
  • 即使JavaScript不可用, 页面也会显示一些文本
  • 由于我们使用的是本地主机, 因此即使没有HTTPS也会检查页面

添加清单

添加清单将解决可安装性标准以及丢失的初始屏幕。

public / js / pwa.webmanifest

{
  "name": "Progressive Web App example", "short_name": "pwa-tutorial", "description": "Progressive Web App example to be used in conjuction with the article in notlogy", "icons": [
    {
      "src": "/../images/splash-screen.png", "sizes": "512x512", "type": "image/png"
    }
  ], "start_url": "/", "display": "fullscreen", "theme_color": "#764ABC", "background_color": "#764ABC"
}

public / index.html

<html>
  <head>
    <link rel="manifest" href="/js/pwa.webmanifest">
  </head>
  <body>
    <span>This example is for the article of progressive web apps written for notlogy</span>
    <br>
    <span>You are now</span> <span><b class="page-status">online</b></span>
    <script src="/js/pwa.js"></script>
  </body>
</html>

public / images / splash-screen.png

你还需要在images文件夹中添加512×512图片。称它为splash-screen.png.

开机画面图像,512x512

现在, 与清单和启动屏幕关联的红色检查应为绿色。

当用户在手机上打开PWA, 按"添加到主屏幕", 然后使用在手机上下载的应用程序打开PWA时, 便会显示该初始屏幕。

优化你的PWA

让我们继续进行简单的检查, 例如主题颜色, 在iOS设备上使用该应用程序时显示的图标以及一个确保应用程序响应的视口。

public / index.html

<html>
  <head>
    <link rel="manifest" href="/js/pwa.webmanifest">
    <link rel="apple-touch-icon" href="/images/apple-touch.png">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="theme-color" content="#764ABC"/>
  </head>
  <body>
    <span>This example is for the article of progressive web apps written for notlogy</span>
    <br>
    <span>You are now</span> <span><b class="page-status">online</b></span>
    <script src="/js/pwa.js"></script>
  </body>
</html>

public / images / apple-touch.png

以下是iOS设备上显示的图标。它应该是192×192。

PWA iOS图标

进行这些更改后, 再次运行Lighthouse。你应该会看到更多的绿色标记。

PWA的灯塔审核

在" PWA优化"下仍然有一个红色标记:必须为所有PWA提供HTTPS服务。此要求要求使用服务工作者之类的技术来确保页面是localhost或HTTPS。

我通常通过在反向代理上添加SSL来消除该错误。这意味着我总是在本地将该标准标记为红色, 但是出于本教程的目的—为了使你满意地看到所有绿色检查, 我们将生成一个自签名证书, 并将服务器更改为重定向到HTTPS。

要生成自签名证书, 请转到证书文件夹并在命令行上运行以下命令。

openssl req -x509 -out localhost.crt -keyout localhost.key \
  -newkey rsa:2048 -nodes -sha256 \
  -subj '/CN=localhost' -extensions EXT -config <( \
   printf "[dn]\nCN=localhost\n[req]\ndistinguished_name = dn\n[EXT]\nsubjectAltName=DNS:localhost\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth")

这将创建你丢失的文件。现在, 你可以更新服务器。

server.js

const express = require('express')
const path = require('path')
const fs = require('fs')
const https = require('https')

const httpPort = 80
const httpsPort = 443
const key = fs.readFileSync('./certs/localhost.key');
const cert = fs.readFileSync('./certs/localhost.crt');

const app = express()
const server = https.createServer({key: key, cert: cert }, app);

app.use((req, res, next) => {
  if (!req.secure) {
    return res.redirect('https://' + req.headers.host + req.url);
  }
  next();
})

app.use(express.static(path.join(__dirname, 'public')))

app.get('/', function(req, res) {
  res.sendFile(path.join(__dirname, 'public/index.html'))
})

app.listen(httpPort, function () {
  console.log(`Listening on port ${httpPort}!`)
})

server.listen(httpsPort, function () {
  console.log(`Listening on port ${httpsPort}!`)
})

我们正在做的是在端口80(HTTP)上创建服务器, 在端口443(HTTPS)上创建服务器。每当你尝试使用http://本地主机, 将触发HTTP, 并且中间件将检查连接(HTTPS)是否安全。如果不是, 则按预期重定向。

下一步是即使连接断开也可以使应用程序正常工作。为此, 我们将使用服务人员。

服务人员

一种服务人员是一段JavaScript代码, 用于处理你选择保存以备将来使用的资产和数据的缓存。

服务人员必须遵循一些规则才能使其正常工作:

- It only works with valid HTTPS or http://localhost
- It only grabs requests within its scope
- It only has access to the files on its folder or "below"

为了扩大范围, 请设想以下结构。

/public
  |_drinks
    |_drinks-service-worker.js
    |_drinks.js
    |_coffee
      |_coffee.js
      |_coffee-service-worker.js
    |_tea
      |_tea.js
      |_tea-service-worker.js

在此示例中, 茶和咖啡服务工作者都只会在调用其各自文件夹中的文件(例如tea.jsorcoffee.js。另一方面, 无论你叫什么电话, 饮料服务人员都会被触发。它的作用域是其文件夹中和"其下"的所有内容。

由于它是工作程序, 因此它无权访问DOM, 这意味着在服务工作程序文件中, 你无法使用以下方式访问任何内容:document.querySelector.

要注册你的工作人员, 请首先检查浏览器是否兼容。如果是, 请添加注册和错误功能。

公共/ js / pwa.js

document.addEventListener('DOMContentLoaded', init, false);
function init() {
  if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('/service-worker.js')
      .then((reg) => {
        console.log('Service worker registered -->', reg);
      }, (err) => {
        console.error('Service worker not registered -->', err);
      });
  }
}

public / service-worker.js

self.addEventListener('install', function(event) {
  console.log('used to register the service worker')
})

self.addEventListener('fetch', function(event) {
  console.log('used to intercept requests so we can check for the file or data in the cache')
})

self.addEventListener('activate', function(event) {
  console.log('this event triggers when the service worker activates')
})

你不需要服务人员参加其他活动, 但是从好的方面来说, 它们是信息, 同步和推.

由于安装是你尝试注册软件时触发的第一件事, 因此将事件更改为以下内容, 并指定要保存在缓存中的文件。

const CACHE_NAME = 'sw-cache-example';
const toCache = [
  '/', '/index.html', ];

self.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(function(cache) {
        return cache.addAll(toCache)
      })
      .then(self.skipWaiting())
  )
})

加skipWaiting当你更新软件时, 避免用户导航离开页面。

要查看你的服务人员, 请再次检查页面。在Chrome DevTools的"应用程序"标签中, 你可以查看服务人员的当前状态, 将页面设置为脱机状态以对其进行测试(破坏者警报:它什么也不会做)。检查当前缓存, 如果要重新启动, 请清除所有内容。

你可能还记得服务工作者需要有效的HTTPS证书。结果, 你可能会发现以下错误。

服务人员注册错误

避免这种情况的一种方法是通过带有标志的命令行运行Chromehttps://本地主机.

/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --user-data-dir=/tmp/foo --ignore-certificate-errors --unsafely-treat-insecure-origin-as-secure=https://localhost

这将产生一个更愉快的记录。

服务人员注册

每当我们更新服务工作者时, 我们都希望删除旧的服务人员, 而不是将其挂在客户端的浏览器中。去你的启用事件, 并将其更改为以下内容。

self.addEventListener('activate', function(event) {
  event.waitUntil(
    caches.keys()
      .then((keyList) => {
        return Promise.all(keyList.map((key) => {
          if (key !== CACHE_NAME) {
            console.log('[ServiceWorker] Removing old cache', key)
            return caches.delete(key)
          }
        }))
      })
      .then(() => self.clients.claim())
  )
})

这样可以确保删除旧的服务人员, 并声称你新安装的软件是从现在起可以使用的软件。

这使我们无所适从。我们根本没有使用过缓存的文件, 因此该更新fetch事件了。

self.addEventListener('fetch', function(event) {
  event.respondWith(
    fetch(event.request)
      .catch(() => {
        return caches.open(CACHE_NAME)
          .then((cache) => {
            return cache.match(event.request)
          })
      })
  )
})

这将检查页面上发出的每个请求。如果在缓存中找到匹配项, 请执行本地主机/, 例如, 由于我们已经对其进行了缓存-它将使用缓存的版本。

在这种情况下, /是个index.html文件, 其中将包含其他资源, 例如/js/pwa.js。这不在你的缓存中, 因此将向服务器发出常规请求以获取该文件的依赖项。

我们可以在缓存中存储任意数量的内容, 但请记住, 每个浏览器都有一个很大的限制。以我的经验, 安全值不超过50MB, 这在Web上是相当多的。

完成所有这三个事件后, 就可以创建一个文件, 以在客户端失去连接并使用纯粹的服务工作者时将页面状态从在线更改为离线。

public / js / status.js

document.addEventListener('DOMContentLoaded', init, false);

function init() {
  if (!navigator.onLine) {
    const statusElem = document.querySelector('.page-status')
    statusElem.innerHTML = 'offline'
  }
}

public / index.html

<html>
  <head>
    <link rel="manifest" href="/js/pwa.webmanifest">
    <link rel="apple-touch-icon" href="/images/apple-touch.png">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="theme-color" content="#764ABC"/>
  </head>
  <body>
    <span>This in an examples for here article of progressive web apps that can be found in notlogy</span>
    <br>
    <span>You are now</span> <span><b class="page-status">online</b></span>
    <script src="/js/pwa.js"></script>
    <script src="/js/status.js"></script>
  </body>
</html>

public / service-worker.js

const CACHE_NAME = 'sw-cache-example';
const toCache = [
  '/', '/index.html', '/js/status.js', ];

self.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(function(cache) {
        return cache.addAll(toCache)
      })
      .then(self.skipWaiting())
  )
})

self.addEventListener('fetch', function(event) {
  event.respondWith(
    fetch(event.request)
      .catch(() => {
        return caches.open(CACHE_NAME)
          .then((cache) => {
            return cache.match(event.request)
          })
      })
  )
})

self.addEventListener('activate', function(event) {
  event.waitUntil(
    caches.keys()
      .then((keyList) => {
        return Promise.all(keyList.map((key) => {
          if (key !== CACHE_NAME) {
            console.log('[ServiceWorker] Removing old cache', key)
            return caches.delete(key)
          }
        }))
      })
      .then(() => self.clients.claim())
  )
})

上面的代码添加了一个状态文件, 以检查浏览器是否在线, 如果不在线, 则更改为离线。我们在index.html以及我们SW的缓存, 以便可以离线使用。

要尝试全部操作, 请重新加载页面, 然后在" DevTools"应用程序选项卡上, 查看缓存中的文件。

在Google DevTools中查看PWA

如果连接正常, 你应该看到以下信息:

在PWA中测试连接

如果选择脱机选项, 则应该看到状态更改。

在PWA中更改为脱机状态

你应该在控制台上看到一些错误, 因为我们没有添加清单和其他文件index.html需要-不会影响离线模式下的任何内容, 但是如果麻烦的话, 你只需将其添加到缓存中即可。

最后, 要确保Lighthouse中的所有内容均为绿色, 请在不进行离线检查的情况下运行该应用程序。你应该得到类似于以下结果:

Lighthouse PWA得分100

恭喜!你已经在Lighthouse中检查了所有条件, 因此已经建立了第一个PWA!

兼容性

PWA与兼容性无处不在, 因为我们不仅在谈论一种技术, 还涉及诸如服务人员, Web应用程序清单, 通知, 推送和添加到主屏幕之类的元素的生态系统, 每种元素具有完全不同的兼容性全面。

也就是说, 服务人员通常有很好的支持。另一方面, 在撰写本文时, Web应用程序清单不适用于Firefox或Safari, 而Web应用程序清单则不那么兼容。

始终检查polyfill, 如果没有, 请确保你没有使用浏览器未实现的技术。

PWA的优缺点

Twitter和阿里巴巴等公司通过以下方式提高了参与度:切换到PWA, 以及其他许多已做出转换的人。

根据我的经验和个人阅读, 以下是与PWA相关的优点和缺点的简短列表。

在专业方面, PWA:

  • 第一次访问该网站后速度很快, 因为缓存了很多资产
  • 易于在现有网站上逐步实施
  • 再次使用较少的数据, 因为缓存了许多资产
  • 是独立的技术, 这意味着当你只需要一种或两种技术时, 就不必绑定到包含10种技术的库中。例如, 你可以使用服务人员, 而无需使用通知

一些缺点:

  • 第一次访问页面会很慢, 因为它必须将文件和数据下载到缓存中
  • 至少据我所知, 很少使用添加到主屏幕的功能
  • 浏览器之间的支持差异很大, 具体取决于你所使用的技术。例如, 服务人员提供了良好的支持, 但Web应用程序清单却没有, 因此要从一开始就确定可以安全使用的内容以及需要进行polyfill的操作可能很棘手。

总结

实施PWA技术是否值得?我会说是的。一个网站, 即使处于脱机状态, 也应该具有可访问的信息。例如, 如果你经营一个新闻网站, 则可以为用户提供使他们最喜欢的新闻离线显示的选项, 或者在发生感兴趣的事情时通知他们, 例如在购物车中促销商品。

你如何看待PWA?你喜欢更原生的方法吗?你有什么用吗?

你可以参考本教程中使用的代码的GitHub.

只有200 监视生产中失败和缓慢的网络请求

部署基于节点的Web应用程序或网站很容易。确保你的Node实例继续为你的应用程序提供资源是一件很困难的事情。如果你希望确保成功完成对后端或第三方服务的请求,

尝试notlogy

.

LogRocket网络请求监控

https://notlogy.com/signup/

日志火箭就像Web应用程序的DVR一样, 实际上记录了你网站上发生的一切。无需猜测问题发生的原因, 你可以汇总并报告有问题的网络请求, 以快速了解根本原因。

notlogy用你的应用程序记录基线性能计时, 例如页面加载时间, 到第一个字节的时间, 缓慢的网络请求, 并记录Redux, NgRx和Vuex的操作/状态。

免费开始监控

.

一盏木

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: