@jon49/sw 0.14.6 → 0.14.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/lib/file-mapper.ts +69 -70
- package/lib/new-app-notifier.ts +56 -43
- package/package.json +1 -1
package/bin/lib/file-mapper.ts
CHANGED
|
@@ -4,84 +4,83 @@ import path from "node:path"
|
|
|
4
4
|
import { getHash, glob, write } from "./system.ts"
|
|
5
5
|
|
|
6
6
|
export interface FileMapper {
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
url: string
|
|
8
|
+
file: string
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
function isFileMapper(x: any | undefined): x is FileMapper {
|
|
12
|
-
|
|
12
|
+
return x?.url && x?.file
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
let option: { isRunning: boolean, isWaiting: NodeJS.Timeout | null } = {
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
isRunning: false,
|
|
17
|
+
isWaiting: null
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export async function fileMapper(targetDirectory: string, force = false) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
option.isWaiting = setTimeout(() => {
|
|
26
|
-
option.isWaiting = null
|
|
27
|
-
fileMapper(targetDirectory, true)
|
|
28
|
-
}, 30)
|
|
29
|
-
return
|
|
30
|
-
}
|
|
31
|
-
try {
|
|
32
|
-
option.isRunning = true
|
|
33
|
-
console.time("File Mapper")
|
|
34
|
-
|
|
35
|
-
let oldFileMapperFiles = await glob("**/file-map.*.js", targetDirectory)
|
|
36
|
-
Promise.all(oldFileMapperFiles.map(async x => {
|
|
37
|
-
await rm(`${targetDirectory}/${x}`)
|
|
38
|
-
}))
|
|
39
|
-
|
|
40
|
-
let files: string[] = await glob("**/*.{js,css,json,ico,svg,png}", targetDirectory)
|
|
41
|
-
|
|
42
|
-
let mapper = files.map(x => {
|
|
43
|
-
let parsed = path.parse(x)
|
|
44
|
-
let parsed2 = path.parse(parsed.name)
|
|
45
|
-
let url = `/${parsed.dir}/${parsed2.name}${parsed.ext}`
|
|
46
|
-
let file = `/${x}`
|
|
47
|
-
return {
|
|
48
|
-
url,
|
|
49
|
-
file,
|
|
50
|
-
}
|
|
51
|
-
})
|
|
52
|
-
.filter(isFileMapper)
|
|
53
|
-
|
|
54
|
-
if (option.isWaiting) return
|
|
55
|
-
// Write mapper to file in src/web/file-map.js
|
|
56
|
-
let fileMapJsonContent = JSON.stringify(mapper)
|
|
57
|
-
let hash = getHash(fileMapJsonContent)
|
|
58
|
-
let fileMapUrl = `/web/file-map.${hash}.js`
|
|
59
|
-
|
|
60
|
-
fileMapJsonContent = `${fileMapJsonContent.slice(0, -1)},{"url":"/web/file-map.js","file":"${fileMapUrl}"}]`
|
|
61
|
-
|
|
62
|
-
let fileMapContent = `(() => { self.app = { links: ${fileMapJsonContent} } })()`
|
|
63
|
-
await write(`${targetDirectory}${fileMapUrl}`, fileMapContent)
|
|
64
|
-
|
|
65
|
-
let globalFiles =
|
|
66
|
-
mapper
|
|
67
|
-
.filter(x => x.url.includes(".global."))
|
|
68
|
-
.map(x => x.file)
|
|
69
|
-
.join(`","`)
|
|
70
|
-
|
|
71
|
-
let swFile = mapper.find(x => x.url === "/web/sw.js")?.file
|
|
72
|
-
|
|
73
|
-
let globals = `${globalFiles && `"`}${globalFiles}${globalFiles && `",`}`
|
|
74
|
-
// Create service worker central file
|
|
75
|
-
if (option.isWaiting) return
|
|
76
|
-
await write(
|
|
77
|
-
`${targetDirectory}/web/sw.js`,
|
|
78
|
-
`importScripts("${fileMapUrl}",${globals}"${swFile}")`)
|
|
79
|
-
|
|
80
|
-
} catch (error) {
|
|
81
|
-
console.error(error)
|
|
82
|
-
} finally {
|
|
83
|
-
option.isRunning = false
|
|
84
|
-
console.timeEnd("File Mapper")
|
|
21
|
+
if (option.isRunning || option.isWaiting || !force) {
|
|
22
|
+
if (option.isWaiting) {
|
|
23
|
+
clearTimeout(option.isWaiting)
|
|
85
24
|
}
|
|
25
|
+
option.isWaiting = setTimeout(() => {
|
|
26
|
+
option.isWaiting = null
|
|
27
|
+
fileMapper(targetDirectory, true)
|
|
28
|
+
}, 30)
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
option.isRunning = true
|
|
33
|
+
console.time("File Mapper")
|
|
34
|
+
|
|
35
|
+
let oldFileMapperFiles = await glob("**/file-map.*.js", targetDirectory)
|
|
36
|
+
Promise.all(oldFileMapperFiles.map(async x => {
|
|
37
|
+
await rm(`${targetDirectory}/${x}`)
|
|
38
|
+
}))
|
|
39
|
+
|
|
40
|
+
let files: string[] = await glob("**/*.{js,css,json,ico,svg,png}", targetDirectory)
|
|
41
|
+
|
|
42
|
+
let mapper = files.map(x => {
|
|
43
|
+
let parsed = path.parse(x)
|
|
44
|
+
let parsed2 = path.parse(parsed.name)
|
|
45
|
+
let url = `/${parsed.dir}/${parsed2.name}${parsed.ext}`
|
|
46
|
+
let file = `/${x}`
|
|
47
|
+
return {
|
|
48
|
+
url,
|
|
49
|
+
file,
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
.filter(isFileMapper)
|
|
53
|
+
|
|
54
|
+
if (option.isWaiting) return
|
|
55
|
+
// Write mapper to file in src/web/file-map.js
|
|
56
|
+
let fileMapJsonContent = JSON.stringify(mapper)
|
|
57
|
+
let hash = getHash(fileMapJsonContent)
|
|
58
|
+
let fileMapUrl = `/web/file-map.${hash}.js`
|
|
59
|
+
|
|
60
|
+
fileMapJsonContent = `${fileMapJsonContent.slice(0, -1)},{"url":"/web/file-map.js","file":"${fileMapUrl}"}]`
|
|
61
|
+
|
|
62
|
+
let fileMapContent = `(() => { self.app = { links: ${fileMapJsonContent} } })()`
|
|
63
|
+
await write(`${targetDirectory}${fileMapUrl}`, fileMapContent)
|
|
64
|
+
|
|
65
|
+
let globalFiles =
|
|
66
|
+
mapper
|
|
67
|
+
.filter(x => x.url.includes(".global."))
|
|
68
|
+
.map(x => x.file)
|
|
69
|
+
.join(`","`)
|
|
70
|
+
|
|
71
|
+
let swFile = mapper.find(x => x.url === "/web/sw.js")?.file
|
|
72
|
+
|
|
73
|
+
let globals = `${globalFiles && `"`}${globalFiles}${globalFiles && `",`}`
|
|
74
|
+
// Create service worker central file
|
|
75
|
+
if (option.isWaiting) return
|
|
76
|
+
await write(
|
|
77
|
+
`${targetDirectory}/web/sw.js`,
|
|
78
|
+
`importScripts("${fileMapUrl}",${globals}"${swFile}")`)
|
|
79
|
+
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.error(error)
|
|
82
|
+
} finally {
|
|
83
|
+
option.isRunning = false
|
|
84
|
+
console.timeEnd("File Mapper")
|
|
85
|
+
}
|
|
86
86
|
}
|
|
87
|
-
|
package/lib/new-app-notifier.ts
CHANGED
|
@@ -1,66 +1,79 @@
|
|
|
1
1
|
// https://deanhume.com/displaying-a-new-version-available-progressive-web-app/
|
|
2
|
+
const isDev = ["localhost", "127.0.0.1"].includes(document.location.hostname)
|
|
2
3
|
let refreshing = false
|
|
3
4
|
// The event listener that is fired when the service worker updates
|
|
4
5
|
// Here we reload the page
|
|
5
6
|
navigator.serviceWorker.addEventListener('controllerchange', function () {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
if (refreshing) return
|
|
8
|
+
refreshing = true
|
|
9
|
+
window.location.reload()
|
|
9
10
|
})
|
|
10
11
|
|
|
11
12
|
/// Checks if there is a new version of the app available
|
|
12
13
|
/// @param fn - Callback function to be called when a new version is available
|
|
13
14
|
/// @returns void
|
|
14
15
|
/// @example
|
|
15
|
-
/// notifier((
|
|
16
|
-
///
|
|
17
|
-
///
|
|
18
|
-
///
|
|
19
|
-
///
|
|
20
|
-
/// worker.postMessage("skipWaiting")
|
|
21
|
-
/// }
|
|
16
|
+
/// notifier((worker) => {
|
|
17
|
+
/// // Show notification to user about new version
|
|
18
|
+
/// if (/* user confirms update */) {
|
|
19
|
+
/// worker.postMessage("skipWaiting")
|
|
20
|
+
/// }
|
|
22
21
|
/// })
|
|
23
|
-
function notifier(fn: (
|
|
24
|
-
|
|
22
|
+
function notifier(fn: (worker: ServiceWorker) => void) {
|
|
23
|
+
let newWorker: ServiceWorker | undefined | null
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
25
|
+
if ('serviceWorker' in navigator) {
|
|
26
|
+
// Register the service worker
|
|
27
|
+
navigator.serviceWorker.register('/web/sw.js').then(reg => {
|
|
28
|
+
if ((newWorker = reg.waiting)?.state === 'installed') {
|
|
29
|
+
// @ts-ignore
|
|
30
|
+
fn(newWorker)
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
reg.addEventListener('updatefound', () => {
|
|
34
|
+
// An updated service worker has appeared in reg.installing!
|
|
35
|
+
newWorker = reg.installing
|
|
37
36
|
|
|
38
|
-
|
|
37
|
+
newWorker?.addEventListener('statechange', () => {
|
|
39
38
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
39
|
+
// There is a new service worker available, show the notification
|
|
40
|
+
if (newWorker?.state === "installed" && navigator.serviceWorker.controller) {
|
|
41
|
+
navigator.serviceWorker.controller.postMessage("installed")
|
|
42
|
+
// If we're in dev/server auto-activate the new worker
|
|
43
|
+
if (isDev) {
|
|
44
|
+
try {
|
|
45
|
+
newWorker.postMessage({ action: 'skipWaiting' })
|
|
46
|
+
} catch (err) {
|
|
47
|
+
// Ignore if worker doesn't accept messages
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
fn(newWorker)
|
|
51
|
+
}
|
|
45
52
|
|
|
46
|
-
})
|
|
47
|
-
})
|
|
48
53
|
})
|
|
49
|
-
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
}
|
|
50
57
|
}
|
|
51
58
|
|
|
52
59
|
notifier(notifyUserAboutNewVersion)
|
|
53
60
|
|
|
54
|
-
function
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
window.app = window.app || {}
|
|
61
|
+
function skipWaiting(id: string) {
|
|
62
|
+
let el = document.getElementById(id)
|
|
63
|
+
el?.addEventListener("click", (e: MouseEvent) => {
|
|
64
|
+
e.preventDefault()
|
|
59
65
|
// @ts-ignore
|
|
60
|
-
window.app.sw
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
+
window.app.sw.postMessage({ action: 'skipWaiting' })
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function notifyUserAboutNewVersion(worker: ServiceWorker) {
|
|
71
|
+
let nav = document.getElementById("sw-message")
|
|
72
|
+
nav?.insertAdjacentHTML("afterbegin", `<div class=inline><a id=skipWaiting href="#">Click here to update your app.</a></div>`)
|
|
73
|
+
// @ts-ignore
|
|
74
|
+
skipWaiting("skipWaiting")
|
|
75
|
+
// @ts-ignore
|
|
76
|
+
window.app = window.app || {}
|
|
77
|
+
// @ts-ignore
|
|
78
|
+
window.app.sw = worker
|
|
66
79
|
}
|