Skip to content

Commit 1127c43

Browse files
authored
feat: add pwa.cache.* option to precisely control caching (cotes2020#1501)
1 parent ea3a22e commit 1127c43

18 files changed

+272
-228
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,6 @@ package-lock.json
1818
.idea
1919

2020
# Misc
21+
*.map
22+
sw.min.js
2123
assets/js/dist

_config.yml

+10-7
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ google_analytics:
6363
# light - Use the light color scheme
6464
# dark - Use the dark color scheme
6565
#
66-
theme_mode: # [light|dark]
66+
theme_mode: # [light | dark]
6767

6868
# The CDN endpoint for images.
6969
# Notice that once it is assigned, the CDN url
@@ -108,10 +108,17 @@ assets:
108108
enabled: # boolean, keep empty means false
109109
# specify the Jekyll environment, empty means both
110110
# only works if `assets.self_host.enabled` is 'true'
111-
env: # [development|production]
111+
env: # [development | production]
112112

113113
pwa:
114-
enabled: true # the option for PWA feature
114+
enabled: true # the option for PWA feature (installable)
115+
cache:
116+
enabled: true # the option for PWA offline cache
117+
# Paths defined here will be excluded from the PWA cache.
118+
# Usually its value is the `baseurl` of another website that
119+
# shares the same domain name as the current website.
120+
deny_paths:
121+
# - "/example" # URLs match `<SITE_URL>/example/*` will not be cached by the PWA
115122

116123
paginate: 10
117124

@@ -157,10 +164,6 @@ defaults:
157164
values:
158165
layout: page
159166
permalink: /:title/
160-
- scope:
161-
path: assets/img/favicons
162-
values:
163-
swcache: true
164167
- scope:
165168
path: assets/js/dist
166169
values:

_data/origin/cors.yml

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ cdns:
88
- url: https://fonts.googleapis.com
99
# jsDelivr CDN
1010
- url: https://cdn.jsdelivr.net
11+
# polyfill.io for math
12+
- url: https://polyfill.io
1113

1214
# fonts
1315

_includes/favicons.html

+3-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
<link rel="apple-touch-icon" sizes="180x180" href="{{ favicon_path }}/apple-touch-icon.png">
99
<link rel="icon" type="image/png" sizes="32x32" href="{{ favicon_path }}/favicon-32x32.png">
1010
<link rel="icon" type="image/png" sizes="16x16" href="{{ favicon_path }}/favicon-16x16.png">
11-
<link rel="manifest" href="{{ favicon_path }}/site.webmanifest">
11+
{% if site.pwa.enabled %}
12+
<link rel="manifest" href="{{ favicon_path }}/site.webmanifest">
13+
{% endif %}
1214
<link rel="shortcut icon" href="{{ favicon_path }}/favicon.ico">
1315
<meta name="apple-mobile-web-app-title" content="{{ site.title }}">
1416
<meta name="application-name" content="{{ site.title }}">

_includes/head.html

+9
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,15 @@
4545

4646
{{ seo_tags }}
4747

48+
<!-- PWA cache settings -->
49+
<meta
50+
name="pwa-cache"
51+
content="{{ site.pwa.cache.enabled | default: 'false' }}"
52+
{%- if site.baseurl and site.baseurl != empty -%}
53+
data-baseurl="{{ site.baseurl }}"
54+
{%- endif -%}
55+
>
56+
4857
<title>
4958
{%- unless page.layout == 'home' -%}
5059
{{ page.title | append: ' | ' }}

_includes/js-selector.html

+4-4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
<!-- layout specified -->
1313

14+
{% assign js_dist = '/assets/js/dist/' %}
15+
1416
{% if page.layout == 'post' or page.layout == 'page' or page.layout == 'home' %}
1517
{% assign urls = urls | append: ',' | append: site.data.origin[type]['lazy-polyfill'].js %}
1618

@@ -65,7 +67,7 @@
6567
{% assign js = 'commons' %}
6668
{% endcase %}
6769

68-
{% capture script %}/assets/js/dist/{{ js }}.min.js{% endcapture %}
70+
{% capture script %}{{ js_dist }}{{ js }}.min.js{% endcapture %}
6971
<script defer src="{{ script | relative_url }}"></script>
7072

7173
{% if page.math %}
@@ -94,9 +96,7 @@
9496
{% if jekyll.environment == 'production' %}
9597
<!-- PWA -->
9698
{% if site.pwa.enabled %}
97-
<script defer src="{{ '/app.js' | relative_url }}"></script>
98-
{% else %}
99-
<script defer src="{{ '/unregister.js' | relative_url }}"></script>
99+
<script defer src="{{ 'app.min.js' | prepend: js_dist | relative_url }}"></script>
100100
{% endif %}
101101

102102
<!-- GA -->

_javascript/pwa/app.js

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/* PWA loader */
2+
3+
if ('serviceWorker' in navigator) {
4+
const meta = document.querySelector('meta[name="pwa-cache"]');
5+
const isEnabled = meta.content === 'true';
6+
7+
if (isEnabled) {
8+
let swUrl = '/sw.min.js';
9+
const baseUrl = meta.getAttribute('data-baseurl');
10+
11+
if (baseUrl !== null) {
12+
swUrl = `${baseUrl}${swUrl}?baseurl=${encodeURIComponent(baseUrl)}`;
13+
}
14+
15+
const $notification = $('#notification');
16+
const $btnRefresh = $('#notification .toast-body>button');
17+
18+
navigator.serviceWorker.register(swUrl).then((registration) => {
19+
// In case the user ignores the notification
20+
if (registration.waiting) {
21+
$notification.toast('show');
22+
}
23+
24+
registration.addEventListener('updatefound', () => {
25+
registration.installing.addEventListener('statechange', () => {
26+
if (registration.waiting) {
27+
if (navigator.serviceWorker.controller) {
28+
$notification.toast('show');
29+
}
30+
}
31+
});
32+
});
33+
34+
$btnRefresh.on('click', () => {
35+
if (registration.waiting) {
36+
registration.waiting.postMessage('SKIP_WAITING');
37+
}
38+
$notification.toast('hide');
39+
});
40+
});
41+
42+
let refreshing = false;
43+
44+
// Detect controller change and refresh all the opened tabs
45+
navigator.serviceWorker.addEventListener('controllerchange', () => {
46+
if (!refreshing) {
47+
window.location.reload();
48+
refreshing = true;
49+
}
50+
});
51+
} else {
52+
navigator.serviceWorker.getRegistrations().then(function (registrations) {
53+
for (let registration of registrations) {
54+
registration.unregister();
55+
}
56+
});
57+
}
58+
}

_javascript/pwa/sw.js

+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/* PWA service worker */
2+
3+
const swconfPath = '/assets/js/data/swconf.js';
4+
const params = new URL(location).searchParams;
5+
const swconfUrl = params.has('baseurl')
6+
? `${params.get('baseurl')}${swconfPath}`
7+
: swconfPath;
8+
9+
importScripts(swconfUrl);
10+
const purge = swconf.purge;
11+
12+
function verifyHost(url) {
13+
for (const host of swconf.allowHosts) {
14+
const regex = RegExp(`^http(s)?://${host}/`);
15+
if (regex.test(url)) {
16+
return true;
17+
}
18+
}
19+
return false;
20+
}
21+
22+
function verifyUrl(url) {
23+
if (!verifyHost(url)) {
24+
return false;
25+
}
26+
27+
const requestPath = new URL(url).pathname;
28+
29+
for (const path of swconf.denyPaths) {
30+
if (requestPath.startsWith(path)) {
31+
return false;
32+
}
33+
}
34+
return true;
35+
}
36+
37+
if (!purge) {
38+
swconf.allowHosts.push(location.host);
39+
}
40+
41+
self.addEventListener('install', (event) => {
42+
if (purge) {
43+
return;
44+
}
45+
46+
event.waitUntil(
47+
caches.open(swconf.cacheName).then((cache) => {
48+
return cache.addAll(swconf.resources);
49+
})
50+
);
51+
});
52+
53+
self.addEventListener('activate', (event) => {
54+
event.waitUntil(
55+
caches.keys().then((keyList) => {
56+
return Promise.all(
57+
keyList.map((key) => {
58+
if (purge) {
59+
return caches.delete(key);
60+
} else {
61+
if (key !== swconf.cacheName) {
62+
return caches.delete(key);
63+
}
64+
}
65+
})
66+
);
67+
})
68+
);
69+
});
70+
71+
self.addEventListener('message', (event) => {
72+
if (event.data === 'SKIP_WAITING') {
73+
self.skipWaiting();
74+
}
75+
});
76+
77+
self.addEventListener('fetch', (event) => {
78+
event.respondWith(
79+
caches.match(event.request).then((response) => {
80+
if (response) {
81+
return response;
82+
}
83+
84+
return fetch(event.request).then((response) => {
85+
const url = event.request.url;
86+
87+
if (purge || event.request.method !== 'GET' || !verifyUrl(url)) {
88+
return response;
89+
}
90+
91+
// See : <https://developers.google.com/web/fundamentals/primers/service-workers#cache_and_return_requests>
92+
let responseToCache = response.clone();
93+
94+
caches.open(swconf.cacheName).then((cache) => {
95+
cache.put(event.request, responseToCache);
96+
});
97+
return response;
98+
});
99+
})
100+
);
101+
});

assets/js/data/swcache.js

-49
This file was deleted.

assets/js/data/swconf.js

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
---
2+
layout: compress
3+
permalink: '/:path/swconf.js'
4+
# Note that this file will be fetched by the ServiceWorker, so it will not be cached.
5+
---
6+
7+
const swconf = {
8+
{% if site.pwa.cache.enabled %}
9+
cacheName: 'chirpy-{{ "now" | date: "%s" }}',
10+
11+
{%- comment -%} Resources added to the cache during PWA installation. {%- endcomment -%}
12+
resources: [
13+
'{{ "/assets/css/:THEME.css" | replace: ':THEME', site.theme | relative_url }}',
14+
'{{ "/" | relative_url }}',
15+
{% for tab in site.tabs %}
16+
'{{- tab.url | relative_url -}}',
17+
{% endfor %}
18+
19+
{% assign cache_list = site.static_files | where: 'swcache', true %}
20+
{% for file in cache_list %}
21+
'{{ file.path | relative_url }}'{%- unless forloop.last -%},{%- endunless -%}
22+
{% endfor %}
23+
],
24+
25+
{%- comment -%} The request url with below domain will be cached. {%- endcomment -%}
26+
allowHosts: [
27+
{% if site.img_cdn and site.img_cdn contains '//' %}
28+
'{{ site.img_cdn | split: '//' | last | split: '/' | first }}',
29+
{% endif %}
30+
31+
{%- unless site.assets.self_host.enabled -%}
32+
{% for cdn in site.data.origin["cors"].cdns %}
33+
'{{ cdn.url | split: "//" | last }}'
34+
{%- unless forloop.last -%},{%- endunless -%}
35+
{% endfor %}
36+
{% endunless %}
37+
],
38+
39+
{%- comment -%} The request url with below path will not be cached. {%- endcomment -%}
40+
denyPaths: [
41+
{% for path in site.pwa.cache.deny_paths %}
42+
{% unless path == empty %}
43+
'{{ path | relative_url }}'{%- unless forloop.last -%},{%- endunless -%}
44+
{% endunless %}
45+
{% endfor %}
46+
],
47+
purge: false
48+
{% else %}
49+
purge: true
50+
{% endif %}
51+
};

0 commit comments

Comments
 (0)