React微前端实战教程(PWA篇)
渐进式Web应用(PWA)的好处:添加到主屏幕、缓存加速启动、离线运行、渐进式更新、通知等功能,不支持PWA的浏览器可以像以前一样使用网页。PWA上线时需要开启HTTPS协议,本地调试可以是HTTP协议。
在构建React微前端之后,相关资源都是动态选择,造成PWA安装时不好确定缓存哪些内容。本文例程使用postMessage()
的方式在主线程与Service Worker线程之间传递数据,让Service Worker线程取得微前端元信息。
给React微前端Demo网站添加PWA功能的操作步骤如下:
1. 添加 PWA manifest
在网站的根目录中添加文件rmf-pwa.webmanifest
,内容如下:
{
"short_name": "Micro Frontends",
"name": "React Micro Frontends Demo",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": "/",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
在网站配置文件site_config.yaml
的htmlBegin
添加:
<link rel="manifest" href="/rmf-pwa.webmanifest"/>
有了这份清单,Chrome浏览器就可以识别我们的网站为PWA,就能安装并添加到主屏幕或桌面。
2. 添加Service Worker程序
PWA的一个大特点是充分使用Service Worker来缓存资源文件。在网站的根目录,添加service-worker.js
,其内容如下:
// service-worker.js
var cacheApps = {
'app-home': true,
'app-example': true
};
var cacheName = 'rmf-cache';
var rmfMetadataJSONP = {apps: [], extra: {}};
function isImageAddress(addr) {
return (addr.pathname.endsWith('.png') || addr.pathname.endsWith('.svg')
|| addr.pathname.endsWith('.jpg') || addr.pathname.endsWith('.ico'));
}
function cacheAppEntries(metadata) {
metadata = metadata || rmfMetadataJSONP;
return caches.open(cacheName).then(function (cache) {
var apps = metadata.apps.filter(function(app) {
return cacheApps[app.id];
});
var entries = apps.reduce(function (acc, cur) {
return acc.concat(cur.entries);
}, []).filter(function(entry) {
var url = new URL(entry, self.location.origin);
return url.origin === self.location.origin;
});
console.log('[Service Worker] Caching app entries');
return cache.addAll(entries).catch(function(e) {
console.log("[Service Worker] Error in caching ", e)
});
});
}
self.addEventListener('install', function (e) {
console.log('[Service Worker] Install');
e.waitUntil(
self.clients.matchAll({
includeUncontrolled: true,
type: 'all',
}).then(function(clients) {
clients.forEach(function(client) {
client.postMessage({type: 'rmf-cache-require', payload: null});
});
})
);
});
self.addEventListener('fetch', function (e) {
// console.log(e.request.url);
var addr = new URL(e.request.url);
var isCacheType = addr.pathname.startsWith('/rmf-') || isImageAddress(addr);
if (e.request.method != 'GET' || !isCacheType) {
return;
}
e.respondWith(
caches.match(e.request).then(function (r) {
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;
});
});
})
);
});
self.addEventListener('message', function (e) {
if (e.data && e.data.type == 'rmf-cache-prefetch') {
console.log('[Service Worker] Message "rmf-cache-prefetch" received');
var metadata = Object.assign({apps: [], extra: {}}, e.data.payload);
if (e.waitUntil) {
e.waitUntil(cacheAppEntries(metadata));
} else {
cacheAppEntries(metadata);
}
}
});
以上代码设计要求:
install
事件处理时,Service Worker线程向主界面线程(Client)发送'rmf-cache-require'
消息。主界面线程响应消息再向Service Worker线程发送'rmf-cache-prefetch'
消息,并携带微前端元信息,最后Service Worker线程开始缓存资源文件。只处理同域名下的资源,Service Worker线程不允许跨域访问。fetch
事件处理时,只缓存GET与/rmf-
开头的资源。从缓存中查询不到时,再向后端请求。
首次安装时,触发install
事件,fetch
事件的监听是无效的。后续再打开网站或PWA时,向后端发送请求时才能触发fetch
事件。
当前这个service-worker.js
在install
时并没有缓存所有的资源文件,原因有二:1、小水管Demo网站做了并发限制,并发连接过多就503错误,造成当前查看的页面图片请求失败;2、后端返回的微前端metadata中已去除了所有文件清单,只保留入口文件。真实提供PWA网站服务时,并发限制要设置比较大,微前端metadata中要返回所有文件清单。
3. 注册Service Worker
在HTML文档中,添加如下的代码:
// Register service worker to control making site work offline
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js').then(function () {
console.log('Service Worker Registered');
});
navigator.serviceWorker.addEventListener('message', function (e) {
if (e.data && e.data.type == 'rmf-cache-require') {
console.log('Message "rmf-cache-require" received');
e.source.postMessage({ type: 'rmf-cache-prefetch', payload: rmfMetadataJSONP });
}
});
navigator.serviceWorker.startMessages();
}
其中的最后一行代码可以解决Service Worker发送'rmf-cache-require'
未能及时响应的问题。
在Demo网站中,我们使用site_config.yaml
文件定制HTML模板,把上面的代码搬到配置文件中:
htmlMiddle: >-
</head><body><noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div><script>var rmfMetadataJSONP = {apps:[], extra: {}};
function rmfMetadataCallback(data) { rmfMetadataJSONP = data }
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js').then(function() {
console.log('Service Worker Registered'); });
navigator.serviceWorker.addEventListener('message', function(e) {
if (e.data && e.data.type == 'rmf-cache-require') {
console.log('Message "rmf-cache-require" received');
e.source.postMessage({type:'rmf-cache-prefetch', payload: rmfMetadataJSONP});
}});
navigator.serviceWorker.startMessages();
}</script>
4. Nginx配置
在Nginx配置文件中添加如下两个Location:
location = /rmf-pwa.webmanifest {
expires 7d;
try_files $uri $uri/ =404;
}
location = /service-worker.js {
expires 1d;
try_files $uri $uri/ =404;
}
5. 重启服务
sudo service nginx restart
sudo service react-micro-frontend-daemon restart
6. 结果验证
使用Chrome浏览Demo网站,可以在地址栏的后面看到一个加号(+),点击它就可以安装PWA到本地。然后,在Chrome的应用中与Windows桌面中都可以看得React Micro Frontends Demo
快捷方式。
双击React Micro Frontends Demo
,打开全屏的PWA程序。