Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add pwa.cache.* option to precisely control caching #1501

Merged
merged 1 commit into from
Jan 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,6 @@ package-lock.json
.idea

# Misc
*.map
sw.min.js
assets/js/dist
17 changes: 10 additions & 7 deletions _config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ google_analytics:
# light - Use the light color scheme
# dark - Use the dark color scheme
#
theme_mode: # [light|dark]
theme_mode: # [light | dark]

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

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

paginate: 10

Expand Down Expand Up @@ -157,10 +164,6 @@ defaults:
values:
layout: page
permalink: /:title/
- scope:
path: assets/img/favicons
values:
swcache: true
- scope:
path: assets/js/dist
values:
Expand Down
2 changes: 2 additions & 0 deletions _data/origin/cors.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ cdns:
- url: https://fonts.googleapis.com
# jsDelivr CDN
- url: https://cdn.jsdelivr.net
# polyfill.io for math
- url: https://polyfill.io

# fonts

Expand Down
4 changes: 3 additions & 1 deletion _includes/favicons.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
<link rel="apple-touch-icon" sizes="180x180" href="{{ favicon_path }}/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="{{ favicon_path }}/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="{{ favicon_path }}/favicon-16x16.png">
<link rel="manifest" href="{{ favicon_path }}/site.webmanifest">
{% if site.pwa.enabled %}
<link rel="manifest" href="{{ favicon_path }}/site.webmanifest">
{% endif %}
<link rel="shortcut icon" href="{{ favicon_path }}/favicon.ico">
<meta name="apple-mobile-web-app-title" content="{{ site.title }}">
<meta name="application-name" content="{{ site.title }}">
Expand Down
9 changes: 9 additions & 0 deletions _includes/head.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@

{{ seo_tags }}

<!-- PWA cache settings -->
<meta
name="pwa-cache"
content="{{ site.pwa.cache.enabled | default: 'false' }}"
{%- if site.baseurl and site.baseurl != empty -%}
data-baseurl="{{ site.baseurl }}"
{%- endif -%}
>

<title>
{%- unless page.layout == 'home' -%}
{{ page.title | append: ' | ' }}
Expand Down
8 changes: 4 additions & 4 deletions _includes/js-selector.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

<!-- layout specified -->

{% assign js_dist = '/assets/js/dist/' %}

{% if page.layout == 'post' or page.layout == 'page' or page.layout == 'home' %}
{% assign urls = urls | append: ',' | append: site.data.origin[type]['lazy-polyfill'].js %}

Expand Down Expand Up @@ -65,7 +67,7 @@
{% assign js = 'commons' %}
{% endcase %}

{% capture script %}/assets/js/dist/{{ js }}.min.js{% endcapture %}
{% capture script %}{{ js_dist }}{{ js }}.min.js{% endcapture %}
<script defer src="{{ script | relative_url }}"></script>

{% if page.math %}
Expand Down Expand Up @@ -94,9 +96,7 @@
{% if jekyll.environment == 'production' %}
<!-- PWA -->
{% if site.pwa.enabled %}
<script defer src="{{ '/app.js' | relative_url }}"></script>
{% else %}
<script defer src="{{ '/unregister.js' | relative_url }}"></script>
<script defer src="{{ 'app.min.js' | prepend: js_dist | relative_url }}"></script>
{% endif %}

<!-- GA -->
Expand Down
58 changes: 58 additions & 0 deletions _javascript/pwa/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/* PWA loader */

if ('serviceWorker' in navigator) {
const meta = document.querySelector('meta[name="pwa-cache"]');
const isEnabled = meta.content === 'true';

if (isEnabled) {
let swUrl = '/sw.min.js';
const baseUrl = meta.getAttribute('data-baseurl');

if (baseUrl !== null) {
swUrl = `${baseUrl}${swUrl}?baseurl=${encodeURIComponent(baseUrl)}`;
}

const $notification = $('#notification');
const $btnRefresh = $('#notification .toast-body>button');

navigator.serviceWorker.register(swUrl).then((registration) => {
// In case the user ignores the notification
if (registration.waiting) {
$notification.toast('show');
}

registration.addEventListener('updatefound', () => {
registration.installing.addEventListener('statechange', () => {
if (registration.waiting) {
if (navigator.serviceWorker.controller) {
$notification.toast('show');
}
}
});
});

$btnRefresh.on('click', () => {
if (registration.waiting) {
registration.waiting.postMessage('SKIP_WAITING');
}
$notification.toast('hide');
});
});

let refreshing = false;

// Detect controller change and refresh all the opened tabs
navigator.serviceWorker.addEventListener('controllerchange', () => {
if (!refreshing) {
window.location.reload();
refreshing = true;
}
});
} else {
navigator.serviceWorker.getRegistrations().then(function (registrations) {
for (let registration of registrations) {
registration.unregister();
}
});
}
}
101 changes: 101 additions & 0 deletions _javascript/pwa/sw.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/* PWA service worker */

const swconfPath = '/assets/js/data/swconf.js';
const params = new URL(location).searchParams;
const swconfUrl = params.has('baseurl')
? `${params.get('baseurl')}${swconfPath}`
: swconfPath;

importScripts(swconfUrl);
const purge = swconf.purge;

function verifyHost(url) {
for (const host of swconf.allowHosts) {
const regex = RegExp(`^http(s)?://${host}/`);
if (regex.test(url)) {
return true;
}
}
return false;
}

function verifyUrl(url) {
if (!verifyHost(url)) {
return false;
}

const requestPath = new URL(url).pathname;

for (const path of swconf.denyPaths) {
if (requestPath.startsWith(path)) {
return false;
}
}
return true;
}

if (!purge) {
swconf.allowHosts.push(location.host);
}

self.addEventListener('install', (event) => {
if (purge) {
return;
}

event.waitUntil(
caches.open(swconf.cacheName).then((cache) => {
return cache.addAll(swconf.resources);
})
);
});

self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((keyList) => {
return Promise.all(
keyList.map((key) => {
if (purge) {
return caches.delete(key);
} else {
if (key !== swconf.cacheName) {
return caches.delete(key);
}
}
})
);
})
);
});

self.addEventListener('message', (event) => {
if (event.data === 'SKIP_WAITING') {
self.skipWaiting();
}
});

self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
if (response) {
return response;
}

return fetch(event.request).then((response) => {
const url = event.request.url;

if (purge || event.request.method !== 'GET' || !verifyUrl(url)) {
return response;
}

// See : <https://developers.google.com/web/fundamentals/primers/service-workers#cache_and_return_requests>
let responseToCache = response.clone();

caches.open(swconf.cacheName).then((cache) => {
cache.put(event.request, responseToCache);
});
return response;
});
})
);
});
49 changes: 0 additions & 49 deletions assets/js/data/swcache.js

This file was deleted.

51 changes: 51 additions & 0 deletions assets/js/data/swconf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
layout: compress
permalink: '/:path/swconf.js'
# Note that this file will be fetched by the ServiceWorker, so it will not be cached.
---

const swconf = {
{% if site.pwa.cache.enabled %}
cacheName: 'chirpy-{{ "now" | date: "%s" }}',

{%- comment -%} Resources added to the cache during PWA installation. {%- endcomment -%}
resources: [
'{{ "/assets/css/:THEME.css" | replace: ':THEME', site.theme | relative_url }}',
'{{ "/" | relative_url }}',
{% for tab in site.tabs %}
'{{- tab.url | relative_url -}}',
{% endfor %}

{% assign cache_list = site.static_files | where: 'swcache', true %}
{% for file in cache_list %}
'{{ file.path | relative_url }}'{%- unless forloop.last -%},{%- endunless -%}
{% endfor %}
],

{%- comment -%} The request url with below domain will be cached. {%- endcomment -%}
allowHosts: [
{% if site.img_cdn and site.img_cdn contains '//' %}
'{{ site.img_cdn | split: '//' | last | split: '/' | first }}',
{% endif %}

{%- unless site.assets.self_host.enabled -%}
{% for cdn in site.data.origin["cors"].cdns %}
'{{ cdn.url | split: "//" | last }}'
{%- unless forloop.last -%},{%- endunless -%}
{% endfor %}
{% endunless %}
],

{%- comment -%} The request url with below path will not be cached. {%- endcomment -%}
denyPaths: [
{% for path in site.pwa.cache.deny_paths %}
{% unless path == empty %}
'{{ path | relative_url }}'{%- unless forloop.last -%},{%- endunless -%}
{% endunless %}
{% endfor %}
],
purge: false
{% else %}
purge: true
{% endif %}
};
Loading