Skip to content

Commit 8d6a387

Browse files
committed
Feature(sw): add service worker update popup support
1 parent 1d9e800 commit 8d6a387

8 files changed

+166
-59
lines changed

package.json

+5-6
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"test:unit": "jest --clearCache && vue-cli-service test:unit"
1515
},
1616
"dependencies": {
17-
"@tinymce/tinymce-vue": "^2.0.0",
17+
"@tinymce/tinymce-vue": "^2.1.0",
1818
"axios": "^0.19.0",
1919
"clipboard": "^2.0.4",
2020
"codemirror": "^5.47.0",
@@ -37,7 +37,7 @@
3737
"script-loader": "^0.7.2",
3838
"sortablejs": "^1.9.0",
3939
"tinymce": "^5.0.7",
40-
"tui-editor": "^1.4.1",
40+
"tui-editor": "^1.4.2",
4141
"vue": "^2.6.10",
4242
"vue-class-component": "^7.1.0",
4343
"vue-count-to": "^1.0.13",
@@ -70,7 +70,6 @@
7070
"@types/sortablejs": "^1.7.2",
7171
"@types/tinymce": "^4.5.22",
7272
"@types/webpack-env": "^1.13.9",
73-
"@types/xlsx": "^0.0.36",
7473
"@types/yamljs": "^0.2.30",
7574
"@vue/cli-plugin-babel": "^3.8.0",
7675
"@vue/cli-plugin-e2e-cypress": "^3.8.0",
@@ -89,14 +88,14 @@
8988
"eslint-plugin-vue": "^5.2.2",
9089
"fibers": "^4.0.1",
9190
"jest": "^24.8.0",
92-
"lint-staged": "^8.1.7",
93-
"sass": "^1.20.3",
91+
"lint-staged": "^8.2.0",
92+
"sass": "^1.21.0",
9493
"sass-loader": "^7.1.0",
9594
"style-resources-loader": "^1.2.1",
9695
"swagger-routes-express": "^3.0.1",
9796
"ts-jest": "^24.0.2",
9897
"ts-node-dev": "^1.0.0-pre.39",
99-
"typescript": "3.4.5",
98+
"typescript": "3.5.1",
10099
"vue-cli-plugin-element": "^1.0.1",
101100
"vue-cli-plugin-style-resources-loader": "^0.1.3",
102101
"vue-template-compiler": "^2.6.10",

src/App.vue

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
<template>
22
<div id="app">
33
<router-view />
4+
<service-worker-update-popup />
45
</div>
56
</template>
67

78
<script lang="ts">
89
import { Component, Vue } from 'vue-property-decorator'
10+
import ServiceWorkerUpdatePopup from '@/pwa/components/ServiceWorkerUpdatePopup.vue'
911
1012
@Component({
11-
name: 'App'
13+
name: 'App',
14+
components: {
15+
ServiceWorkerUpdatePopup
16+
}
1217
})
1318
export default class extends Vue {}
1419
</script>

src/main.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import i18n from '@/lang' // Internationalization
1313
import '@/icons/components'
1414
import '@/permission'
1515
import '@/utils/error-log'
16-
import '@/registerServiceWorker'
16+
import '@/pwa/register-service-worker'
1717
import * as filters from '@/filters'
1818

1919
Vue.use(ElementUI, {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<script lang="ts">
2+
import { Component, Vue } from 'vue-property-decorator'
3+
4+
@Component({
5+
name: 'ServiceWorkerUpdatePopup'
6+
})
7+
export default class extends Vue {
8+
private refreshing = false
9+
private notificationText = 'New content is available!'
10+
private refreshButtonText = 'Refresh'
11+
private registration: ServiceWorkerRegistration | null = null
12+
13+
created() {
14+
// Listen for swUpdated event and display refresh notification as required.
15+
document.addEventListener('swUpdated', this.showRefreshUI, { once: true })
16+
// Refresh all open app tabs when a new service worker is installed.
17+
navigator.serviceWorker.addEventListener('controllerchange', () => {
18+
if (this.refreshing) return
19+
this.refreshing = true
20+
window.location.reload()
21+
})
22+
}
23+
24+
render() {
25+
// Avoid warning for missing template
26+
}
27+
28+
private showRefreshUI(e: Event) {
29+
// Display a notification inviting the user to refresh/reload the app due
30+
// to an app update being available.
31+
// The new service worker is installed, but not yet active.
32+
// Store the ServiceWorkerRegistration instance for later use.
33+
const h = this.$createElement
34+
this.registration = (e as CustomEvent).detail
35+
this.$notify.info({
36+
title: 'Update available',
37+
message: h('div', { class: 'sw-update-popup' }, [
38+
this.notificationText,
39+
h('br'),
40+
h('button', {
41+
on: {
42+
click: (e: Event) => {
43+
e.preventDefault()
44+
this.refreshApp()
45+
}
46+
}
47+
}, this.refreshButtonText)
48+
]),
49+
position: 'bottom-right',
50+
duration: 0
51+
})
52+
}
53+
54+
private refreshApp() {
55+
// Protect against missing registration.waiting.
56+
if (!this.registration || !this.registration.waiting) return
57+
this.registration.waiting.postMessage('skipWaiting')
58+
}
59+
}
60+
</script>
61+
62+
<style lang="scss" scoped>
63+
.sw-update-popup > button {
64+
margin-top: 0.5em;
65+
padding: 0.25em 1.5em;
66+
}
67+
</style>

src/registerServiceWorker.ts renamed to src/pwa/register-service-worker.ts

+12-2
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,27 @@ if (process.env.NODE_ENV === 'production') {
1010
'For more details, visit https://goo.gl/AFskqB'
1111
)
1212
},
13-
registered() {
13+
registered(registration) {
1414
console.log('Service worker has been registered.')
15+
// Routinely check for app updates by testing for a new service worker.
16+
setInterval(() => {
17+
registration.update()
18+
}, 1000 * 60 * 60) // hourly checks
1519
},
1620
cached() {
1721
console.log('Content has been cached for offline use.')
1822
},
1923
updatefound() {
2024
console.log('New content is downloading.')
2125
},
22-
updated() {
26+
updated(registration) {
2327
console.log('New content is available; please refresh.')
28+
// Add a custom event and dispatch it.
29+
// Used to display of a 'refresh' banner following a service worker update.
30+
// Set the event payload to the service worker registration object.
31+
document.dispatchEvent(
32+
new CustomEvent('swUpdated', { detail: registration })
33+
)
2434
},
2535
offline() {
2636
console.log('No internet connection found. App is running in offline mode.')

src/pwa/service-worker.js

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// This is the code piece that GenerateSW mode can't provide for us.
2+
// This code listens for the user's confirmation to update the app.
3+
self.addEventListener('message', (e) => {
4+
if (e.data) {
5+
if (e.data === 'skipWaiting') {
6+
self.skipWaiting()
7+
}
8+
}
9+
})
10+
11+
workbox.clientsClaim()
12+
13+
// The precaching code provided by Workbox. You don't need to change this part.
14+
self.__precacheManifest = [].concat(self.__precacheManifest || [])
15+
workbox.precaching.suppressWarnings()
16+
workbox.precaching.precacheAndRoute(self.__precacheManifest, {})

vue.config.js

+8-4
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,18 @@ module.exports = {
3232
}
3333
},
3434
pwa: {
35-
name: name
35+
name: name,
36+
workboxPluginMode: 'InjectManifest',
37+
workboxOptions: {
38+
swSrc: path.resolve(__dirname, 'src/pwa/service-worker.js')
39+
}
3640
},
3741
pluginOptions: {
3842
'style-resources-loader': {
3943
preProcessor: 'scss',
4044
patterns: [
41-
path.resolve(__dirname, './src/styles/_variables.scss'),
42-
path.resolve(__dirname, './src/styles/_mixins.scss')
45+
path.resolve(__dirname, 'src/styles/_variables.scss'),
46+
path.resolve(__dirname, 'src/styles/_mixins.scss')
4347
]
4448
}
4549
},
@@ -74,7 +78,7 @@ module.exports = {
7478
},
7579
commons: {
7680
name: 'chunk-commons',
77-
test: path.join(__dirname, 'src/components'),
81+
test: path.resolve(__dirname, 'src/components'),
7882
minChunks: 3, // minimum common number
7983
priority: 5,
8084
reuseExistingChunk: true

0 commit comments

Comments
 (0)