欢迎来到我们进入渐进式 Web 应用世界的第一大步。本章将致力于创建我们的第一个服务工作器,这反过来将解锁使 PWAs 如此特殊的许多功能。
我们之前已经讨论过 PWA 桥接 web 应用和本机应用。他们这样做的方式是通过服务工作器。服务工作器使推送通知和脱机访问成为可能。这是一项令人兴奋的新技术,有许多应用(每年都有越来越多的应用);如果有一项技术能在未来五年内改变 web 开发,那就是服务工作器。
然而,足够的炒作;让我们深入了解服务工作器到底是什么。
在本章中,我们将介绍以下主题:
- 什么是服务工作器?
- 服务工作器生命周期
- 如何在我们的页面上注册服务工作器
服务工作器是介于我们的应用和网络之间的一种 JavaScript。
您可以考虑在应用上下文之外运行的脚本,但我们可以在代码范围内使用它进行通信。它是我们应用的一部分,但与其他部分是分开的。
最简单的例子是在缓存文件的上下文中(我们将在接下来的章节中探讨)。假设我们的应用,当用户导航到时 https://chatastrophe.com 去拿我们的icon.png
文件。
如果我们配置了服务工作器,它将位于我们的应用和网络之间。当我们的应用请求图标文件时,服务工作器会拦截该请求并检查本地缓存中的文件。如果找到,它将返回它;没有网络请求。只有在缓存中找不到文件时,才会让网络请求通过;下载完成后,它会将文件放入缓存。
你可以看到“工人”这个词的由来——我们的服务工作器是一只忙碌的小蜜蜂。
让我们看另一个例子;推送通知(偷偷预览第 9 章,使我们的应用可以通过清单安装)。大多数推送通知都是这样工作的——当某个事件发生时(用户发送新的聊天消息),消息服务会发出警报(在我们的例子中,消息服务由 Firebase 管理)。消息服务向相关注册用户(通过其设备注册的用户)发送警报,然后其设备创建通知(叮当!)。
在 web 应用上下文中,此流的问题在于,当用户不在页面上时,我们的应用停止运行,因此我们无法通知他们,除非他们的应用已经打开,这完全破坏了推送通知的作用。
服务工作器通过始终“打开”并倾听消息来解决此问题。现在,消息服务可以提醒我们的服务工作器,向用户显示消息。我们的应用代码实际上从未涉及,所以不管它是否在运行。
这是令人兴奋的事情,但任何新技术,都有一些粗糙的边缘,还有一些东西需要注意。
当用户第一次访问您的页面时,服务工作器的生命就开始了。服务工作器已下载并开始运行。它可能会在不需要时闲置一段时间,但在需要时可以重新启动。
这种始终开启的功能使服务工作器对推送通知非常有用。这也使得服务工作器对工作有点不感兴趣(后面会有更多)。然而,让我们深入地看看一个典型页面上的服务工作器的生死。
首先,如果可能,安装了 service worker。所有 service worker 安装都将首先检查用户的浏览器是否支持该技术。到目前为止,Firefox、Chrome 和 Opera 都有完全的支持,而其他浏览器则没有。例如,苹果公司将服务工作器视为实验性技术,表明他们对整个事件仍持怀疑态度。
如果用户的浏览器足够现代化,则开始安装。脚本(例如,sw.js
)安装(或者更确切地说,注册)在某个范围内。范围在本例中是指其关注的网站路径。全局范围将采用'/'
,例如站点上的所有路径,但您也可以将您的服务工作器限制为'/users'
;例如,仅缓存应用的某些部分。我们将在缓存章节中详细讨论范围。
注册后,服务工作器即被激活。激活事件也会在需要服务工作器时发生,例如,当传入推送通知时。服务工作器的激活和停用意味着您无法在服务工作器内保持状态;它只是对事件做出反应而运行的一段代码,而不是它自己的完整应用。这是一个需要记住的重要区别,以免我们对员工要求太多。
在事件发生之前,服务工作器将处于空闲状态。到目前为止,服务工作器对两个事件做出反应:fetch
事件(也称为来自应用的网络请求)和message
(也称为来自应用代码或消息服务的交互)。我们可以在服务工作器中注册这些事件的侦听器,然后根据需要做出反应。
服务工作器代码将在两种情况下更新:24 小时已过(在这种情况下,它将停止并重新下载一种方法,以防止损坏的代码造成太多麻烦),或者用户访问页面并且sw.js
文件已更改。每次用户访问应用时,服务工作器都会将其当前代码与网站提供的sw.js
进行比较,如果有一个字节的差异,就会下载并注册新的sw.js
。
这是对服务工作器及其工作方式的基本技术概述。这可能看起来很复杂,但好消息是使用服务工作器相对简单;您可以在几分钟内完成一个简单的安装并运行,这正是我们下一步要做的!
记住服务工作器的区别——他们是我们网站的一部分,但在我们的应用代码之外运行。考虑到这一点,我们的服务工作器将住在public/ folder
内,而不是src/ folder
内。
然后,在public/ folder
中创建一个名为sw.js
的文件。现在我们将保持简单;只需在内部添加一个console.log
:
console.log("Service worker running!");
实际工作(登记服务工作器)将在我们的index.html
内完成。对于此过程,我们希望执行以下操作:
- 检查浏览器是否支持服务工作器。
- 等待页面加载。
- 注册服务工作器。
- 注销结果。
让我们一个接一个地完成这些步骤。首先,让我们在 Firebase 初始化下方的public/index.html
内创建一个空的script
标记:
<body>
<div id="root"></div>
<script src="/secrets.js"></script>
<script src="https://www.gstatic.com/firebasejs/4.1.2/firebase.js"></script>
<script>
// Initialize Firebase
var config = {
apiKey: window.apiKey,
authDomain: "chatastrophe-77bac.firebaseapp.com",
databaseURL: "https://chatastrophe-77bac.firebaseio.com",
projectId: "chatastrophe-77bac",
storageBucket: "chatastrophe-77bac.appspot.com",
messagingSenderId: "85734589405"
};
window.firebase = firebase;
firebase.initializeApp(config);
</script>
<script>
// Service worker code here.
</script>
检查用户的浏览器是否可以处理服务工作器是非常容易的。在脚本标签中,我们将添加一个简单的if
语句:
<script>
if ('serviceWorker' in navigator) {
// register
} else {
console.log('service worker is not supported');
}
</script>
在这里,我们检查window.navigator
对象是否有任何服务工作器支持。导航器也可以用来(通过它的userAgent
属性)检查用户有什么浏览器,尽管这里不需要。
在页面加载完成之前,我们不想注册我们的服务工作器;这没有意义,而且可能会导致复杂性,因此我们将在'load'
事件的窗口中添加一个事件侦听器:
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
});
} else {
console.log('service worker is not supported');
}
</script>
正如我们前面提到的,window.navigator
有一个serviceWorker
属性,它的存在确认了浏览器对服务工作器的支持。我们还可以使用同一对象通过其register
功能注册我们的服务工作器。我知道,这是令人震惊的事情。
我们调用navigator.serviceWorker.register
,并将路径传递到我们的服务工作器文件:
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('sw.js')
});
} else {
console.log('service worker is not supported');
}
</script>
最后,让我们添加一些console.logs
,以便我们知道注册的结果。幸运的是,navigator.serviceWorker.register
返回了一个承诺:
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('sw.js').then(function(registration) {
// Registration was successful
console.log('Registered!');
}, function(err) {
// registration failed :(
console.log('ServiceWorker registration failed: ', err);
}).catch(function(err) {
console.log(err);
});
});
} else {
console.log('service worker is not supported');
}
</script>
好的,让我们测试一下!重新加载页面,如果一切正常,您应该在控制台中看到以下内容:
您还可以通过导航到 DevTools 中的“应用”选项卡,然后导航到“服务工作器”选项卡进行检查:
我建议您趁此机会检查一下“重新加载时更新”按钮。这可以确保每次刷新页面时都会刷新服务工作器(请记住我们前面讨论的正常服务工作器生命周期)。为什么要采取这种预防措施?我们正在步入缓存代码的世界,浏览器可能会认为您的服务工作器没有改变,而实际上却改变了。此复选框仅确保您始终处理最新版本的sw.js
。
好的,我们已经登记了一名工人!好极了让我们花一点时间从服务工作器的生命周期中开始。
服务工作器经历的第一个事件是'install'
事件。这是用户第一次启动 PWA 时。标准用户只能体验一次。
要利用此事件,我们所要做的就是向服务工作器本身添加一个事件侦听器。为了在sw.js
中实现这一点,我们使用self
关键字:
self.addEventListener('install', function() {
console.log('Install!');
});
重新加载页面时,控制台中会出现'Install!'
。事实上,您应该在每次重新加载页面时看到它,除非您取消选中 Application | Service Workers 下的 Update on reload 选项。那么,你只会第一次看到它。
接下来是activate
事件。此事件在服务工作器首次注册时触发,在注册完成之前。换句话说,它应在与安装相同的情况下发生,仅在以后发生:
self.addEventListener('activate', function() {
console.log('Activate!');
});
我们要报道的最后一个事件是'fetch'
事件。每当应用发出网络请求时,都会调用此事件。它由一个具有请求 URL 的事件对象调用,我们可以注销该 URL:
self.addEventListener('fetch', function(event) {
console.log('Fetch!', event.request);
});
在添加此项后,我们将看到一个非常混乱的控制台:
您现在可以删除 service worker 中的所有console.logs
,但我们将在将来使用这些事件侦听器中的每一个。
接下来,我们将研究如何连接 Firebase 消息服务,为推送通知奠定基础。
本章其余部分的目标是将 Firebase 集成到我们的服务工作器中,以便它能够接收推送通知并显示它们。
这是一个大项目。在下一章结束之前,我们无法实际显示推送通知。然而,在这里我们将看到如何将第三方服务集成到服务工作器中,并进一步深入研究服务工作器背后的理论。
我们将用于向用户设备发送推送通知的服务称为Firebase 云消息或FCM。FCM 在 web 上的工作方式是查找服务工作器,然后向其发送消息(包含通知详细信息)。然后,服务工作器显示通知。
默认情况下,FCM 查找名为firebase-messaging-sw.js
的服务工作器。您可以使用firebase.messaging().useServiceWorker
并传递服务工作器注册对象来更改该设置。然而,出于我们的目的,更直接的方法是简单地重命名我们的服务工作器。让我们这样做;更改public/
中的文件名和index.html
中的注册:
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('firebase-messaging-sw.js').then(function(registration) {
// Registration was successful
console.log('Registered!');
}, function(err) {
// registration failed :(
console.log('ServiceWorker registration failed: ', err);
}).catch(function(err) {
console.log(err);
});
});
} else {
console.log('service worker is not supported');
}
</script>
完成后,我们可以在服务工作器内部开始初始化 Firebase。
再说一遍;服务工作器未链接到您的应用代码。这意味着它无法访问我们当前的 Firebase 初始化。不过,我们可以在服务工作器内部重新初始化 Firebase,并且只保留相关的内容--messagingSenderId
。您可以从 Firebase 控制台或您的secrets.js
文件获取您的messagingSenderId
。
If you're concerned about security, ensure that you add public/firebase-messaging-sw.js
to your .gitignore
, though keeping your messagingSenderId
private is not as important as keeping your API key secret.
// firebase-messaging-sw.js
firebase.initializeApp({
'messagingSenderId': '85734589405'
});
我们还需要导入文件顶部需要的 Firebase 部分,包括app
库和messaging
库:
importScripts('https://www.gstatic.com/firebasejs/3.9.0/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/3.9.0/firebase-messaging.js');
完成后,我们应该能够console.log
输出firebase.messaging();
:
importScripts('https://www.gstatic.com/firebasejs/3.9.0/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/3.9.0/firebase-messaging.js');
firebase.initializeApp({
'messagingSenderId': '85734589405'
});console.log(firebase.messaging());
您应该看到以下内容:
这意味着我们的 Firebase 在我们的服务工作器内部启动并运行!
If you're still seeing the logs from our old sw.js
, go to the Application | Service Workers tab of DevTools and Unregister it. This is a good example of how service workers will persist even if they're not being reregistered.
如前所述,服务工作器是一段始终在运行的代码(尽管不是完全准确的——想想这些工作器的生命周期——这是一种很好的思考他们的方式)。这意味着它将始终等待 FCM 通知它有消息传入。
但是,目前我们没有收到任何消息。下一步是开始配置何时发送推送通知以及如何显示它们!
在本章中,我们学习了服务工作器的基本知识,并启动了一个服务工作器。我们的下一步是开始使用它。具体来说,我们希望使用它来侦听通知,然后将它们显示给用户。让我们通过设置推送通知,朝着让我们的 PWA 感觉像本机应用的方向迈出另一大步。