Skip to content

Commit 1fc4d49

Browse files
committed
chore: initial commit
0 parents  commit 1fc4d49

File tree

20 files changed

+562
-0
lines changed

20 files changed

+562
-0
lines changed

.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
public/
2+
resources/
3+
.hugo_build.lock
4+
node_modules/
5+
package-lock.json

.prettierrc

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"overrides": [
3+
{
4+
"files": [
5+
"*.html"
6+
],
7+
"options": {
8+
"parser": "go-template"
9+
}
10+
}
11+
]
12+
}

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2023 Hugo Modules Authors
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Hugo PWA Module
2+
3+
[![Used By](https://img.shields.io/badge/dynamic/json?color=success&label=used+by&query=repositories_humanize&logo=hugo&style=flat-square&url=https://api.razonyang.com/v1/github/dependents/hugomods/pwa)](https://github.com/hugomods/pwa/network/dependents)
4+
![Hugo Requirements](https://img.shields.io/badge/dynamic/json?color=important&label=requirements&query=requirements&logo=hugo&style=flat-square&url=https://api.razonyang.com/v1/hugo/modules/github.com/hugomods/pwa)
5+
[![License](https://img.shields.io/github/license/hugomods/pwa?style=flat-square)](https://github.com/hugomods/pwa/blob/main/LICENSE)
6+
[![Version](https://img.shields.io/github/v/tag/hugomods/pwa?label=version&style=flat-square)](https://github.com/hugomods/pwa/tags)
7+
8+
## Features
9+
10+
- [x] Offline available.
11+
- [x] Offline page.
12+
- [x] Offline image.
13+
- [x] Install site on home screen.
14+
- [x] Precaching.
15+
- [x] Caching.
16+
- [ ] Documentations.
+130
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import { default as params } from '@params'
2+
import { CacheableResponsePlugin } from 'workbox-cacheable-response'
3+
import { ExpirationPlugin } from 'workbox-expiration'
4+
import { registerRoute, setCatchHandler } from 'workbox-routing'
5+
import { CacheFirst, NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies'
6+
7+
self.__WB_DISABLE_DEV_LOGS = !params.debug
8+
9+
const debug = (...data: any[]): void => {
10+
if (self.__WB_DISABLE_DEV_LOGS) {
11+
return
12+
}
13+
14+
console.debug('[pwa]', ...data);
15+
}
16+
17+
const cachePrefix = 'hugo-pwa-'
18+
const fallbacksCache = cachePrefix + 'fallbacks'
19+
// Filter the invalid URLs, such as temporary URLs generated by Hugo PostProgress.
20+
const precaches = params.precaches.filter((url) => url.indexOf('__h_pp_l1') !== 0)
21+
22+
// Register page route with NetworkFirst strategy.
23+
// There will be a problem with CacheFirst or StaleWhileRevalidate strategy
24+
// if the cached page loads no longer exist or expired assets, such as CSS and JS.
25+
registerRoute(
26+
({ request }) => {
27+
return request.mode === 'navigate';
28+
},
29+
new NetworkFirst({
30+
cacheName: cachePrefix + 'pages',
31+
plugins: [
32+
new CacheableResponsePlugin({
33+
statuses: [200],
34+
}),
35+
],
36+
})
37+
)
38+
39+
// Register assets routes.
40+
const assets = ['font', 'image', 'script', 'style']
41+
for (let i in assets) {
42+
const kind = assets[i]
43+
const cache = params.caches[kind]
44+
const cacheName = cachePrefix + kind + 's'
45+
let strategy = null
46+
let plugins = [
47+
new CacheableResponsePlugin({
48+
statuses: [200],
49+
}),
50+
new ExpirationPlugin({
51+
maxAgeSeconds: cache.max_age ?? 60 * 60 * 24 * 30,
52+
})
53+
]
54+
switch (cache.strategy) {
55+
case 'network-first':
56+
strategy = new NetworkFirst({
57+
cacheName: cacheName,
58+
plugins: plugins,
59+
})
60+
break
61+
case 'cache-first':
62+
strategy = new CacheFirst({
63+
cacheName: cacheName,
64+
plugins: plugins,
65+
})
66+
break
67+
case 'stale-while-revalidate':
68+
strategy = new StaleWhileRevalidate({
69+
cacheName: cacheName,
70+
plugins: plugins,
71+
})
72+
break
73+
default:
74+
throw new Error(`invalid strategy for kind "${kind}": ` + cache.strategy)
75+
}
76+
registerRoute(
77+
({ request }) => {
78+
return request.destination === kind;
79+
},
80+
strategy
81+
);
82+
}
83+
84+
self.addEventListener('install', event => {
85+
event.waitUntil(
86+
self.caches
87+
.open(fallbacksCache)
88+
.then(cache => cache.addAll(precaches))
89+
);
90+
});
91+
92+
const handler = async options => {
93+
debug('catch handler', options.request)
94+
const dest = options.request.destination
95+
const url = options.request.url
96+
const cache = await self.caches.open(fallbacksCache)
97+
98+
// Return the cached item if found.
99+
const cached = await cache.match(url)
100+
if (cached) {
101+
return cached
102+
}
103+
104+
if (dest === 'document') {
105+
const offline = '/offline.html'
106+
let lang = ''
107+
let paths: string[]
108+
if (url.indexOf(params.baseURL) === 0) {
109+
paths = url.replace(params.baseURL, '').split('/', 1)
110+
} else {
111+
paths = (new URL(url)).pathname.replace(/^\//, '').split('/', 1)
112+
}
113+
if (paths.length > 0 && params.langs.includes(paths[0])) {
114+
lang = paths[0]
115+
}
116+
117+
debug('loading offline page', `/${lang}/offline.html`, offline)
118+
return (await cache.match(`/${lang}/offline.html`))
119+
|| (await cache.match(offline))
120+
|| Response.error()
121+
} else if (dest === 'image' && params.offline_image) {
122+
return (await cache.match(params.offline_image))
123+
|| Response.error()
124+
}
125+
126+
// Return a error response.
127+
return Response.error()
128+
};
129+
130+
setCatchHandler(handler)

go.mod

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
module github.com/hugomods/pwa
2+
3+
go 1.18
4+
5+
require (
6+
github.com/hugomods/icons/vendors/bootstrap v0.4.0 // indirect
7+
github.com/hugomods/workbox v0.1.0 // indirect
8+
github.com/jakearchibald/idb v7.1.1+incompatible // indirect
9+
)

go.sum

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
github.com/hugomods/icons v0.6.0 h1:G6RU93okhPPRDh/jqcew9gwkcYpSpg0rCBv4S6yUAFw=
2+
github.com/hugomods/icons/vendors/bootstrap v0.4.0 h1:+2r0aWG/qdsdLBtQ+b5VN7z3Kh/M0/TduF9ZxW0d9Kg=
3+
github.com/hugomods/icons/vendors/bootstrap v0.4.0/go.mod h1:my/xxEyTvWAym67NVfhWRvdQKrHcscU43neIBXnlbfM=
4+
github.com/hugomods/workbox v0.1.0 h1:yxuTj3gT1BNf6OitxrjxSJXfmbFBj2UTwdWr142eYFQ=
5+
github.com/hugomods/workbox v0.1.0/go.mod h1:RpFcIcltqYsiHWJV6PHPOdMagFZxO7nrO5XUpn/TMoc=
6+
github.com/jakearchibald/idb v7.1.1+incompatible h1:/o1ng3qUjgMUeeKFim3J3tiQAitcVXU12pXIjSzwT/c=
7+
github.com/jakearchibald/idb v7.1.1+incompatible/go.mod h1:AuDoW54s2DKfbclM+ScrnPp8ahUQ9rHph9bb8sZTEXc=

hugo.toml

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
[module]
2+
[module.hugoVersion]
3+
extended = true
4+
min = "0.110.0"
5+
6+
[outputFormats]
7+
[outputFormats.Offline]
8+
baseName = "offline"
9+
isHTML = true
10+
notAlternative = true
11+
mediaType = "text/html"
12+
13+
[[module.imports]]
14+
path = "github.com/hugomods/icons/vendors/bootstrap"
15+
16+
[[module.imports]]
17+
path = "github.com/hugomods/workbox"
18+
19+
[params.hugopress.modules.pwa.hooks.head-begin]
20+
cacheable = true
21+
22+
[params.hugopress.modules.pwa.hooks.body-end]
23+
weight = 1000
24+
25+
[params.pwa]
26+
debug = false
27+
icon_path = "images/pwa/icon.png"
28+
icon_sizes = [48, 64, 128, 144, 256, 512]
29+
offline_image = "images/pwa/offline.png"
30+
31+
# [[params.pwa.precaches]]
32+
# url = ""
33+
34+
[params.pwa.caches.font]
35+
strategy = "cache-first"
36+
# 30d
37+
max_age = 2592000
38+
39+
[params.pwa.caches.image]
40+
strategy = "cache-first"
41+
# 30d
42+
max_age = 2592000
43+
44+
[params.pwa.caches.script]
45+
strategy = "cache-first"
46+
# 30d
47+
max_age = 2592000
48+
49+
[params.pwa.caches.style]
50+
strategy = "cache-first"
51+
# 30d
52+
max_age = 2592000

i18n/en.toml

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[go_back]
2+
other = "Go back"
3+
4+
[home]
5+
other = "Home"
6+
7+
[offline_title]
8+
other = "Oops! You're offline"
9+
10+
[offline_info]
11+
other = """
12+
It seems there is a problem of your connection, please check your network status.
13+
14+
Please get in touch with us if you think this is a server error. Thank you.
15+
"""
16+
17+
[try_again]
18+
other = "Try again"

i18n/zh-hans.toml

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[go_back]
2+
other = "返回"
3+
4+
[home]
5+
other = "主页"
6+
7+
[offline_title]
8+
other = "哎哟!你离线了"
9+
10+
[offline_info]
11+
other = """
12+
看来你的连接有问题,请检查你的网络状态。
13+
14+
如果您认为这是服务器错误,请与我们联系,非常感谢。
15+
"""
16+
17+
[try_again]
18+
other = "再试一次"

i18n/zh-hant.toml

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[go_back]
2+
other = "返回"
3+
4+
[home]
5+
other = "主頁"
6+
7+
[offline_title]
8+
other = "哎喲!你離線了"
9+
10+
[offline_info]
11+
other = """
12+
看來你的連接有問題,請檢查你的網絡狀態。
13+
14+
如果您認為這是服務器錯誤,請與我們聯系,非常感謝。
15+
"""
16+
17+
[try_again]
18+
other = "再試一次"

0 commit comments

Comments
 (0)