From e8adda8b219ccc6819d62f6fc316c5166aa5e51b Mon Sep 17 00:00:00 2001 From: Toru Nagashima Date: Thu, 17 May 2018 21:28:51 +0900 Subject: [PATCH 1/5] feat: e.skipWaiting() in sw-update event --- lib/app/SWUpdateEvent.js | 35 ++++++++++++++++++++++++++++++ lib/app/clientEntry.js | 6 ++++- lib/build.js | 7 +++++- lib/service-worker/skip-waiting.js | 12 ++++++++++ 4 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 lib/app/SWUpdateEvent.js create mode 100644 lib/service-worker/skip-waiting.js diff --git a/lib/app/SWUpdateEvent.js b/lib/app/SWUpdateEvent.js new file mode 100644 index 0000000000..7a3f45e1c3 --- /dev/null +++ b/lib/app/SWUpdateEvent.js @@ -0,0 +1,35 @@ +export default class SWUpdateEvent { + constructor (registration) { + Object.defineProperty(this, 'registration', { + value: registration, + configurable: true, + writable: true + }) + } + + /** + * Activate new service worker to work 'location.reload()' with new data. + */ + skipWaiting () { + const worker = this.registration.waiting + if (!worker) { + return Promise.resolve() + } + + console.log('[vuepress:sw] Doing worker.skipWaiting().') + return new Promise((resolve, reject) => { + const channel = new MessageChannel() + + channel.port1.onmessage = (event) => { + console.log('[vuepress:sw] Done worker.skipWaiting().') + if (event.data.error) { + reject(event.data.error) + } else { + resolve(event.data) + } + } + + worker.postMessage({ type: 'skip-waiting' }, [channel.port2]) + }) + } +} diff --git a/lib/app/clientEntry.js b/lib/app/clientEntry.js index 4b11dbb48b..292371ecb1 100644 --- a/lib/app/clientEntry.js +++ b/lib/app/clientEntry.js @@ -1,6 +1,7 @@ /* global BASE_URL, GA_ID, ga, SW_ENABLED */ import { createApp } from './app' +import SWUpdateEvent from './SWUpdateEvent' import { register } from 'register-service-worker' const { app, router } = createApp() @@ -47,7 +48,10 @@ router.onReady(() => { }, updated () { console.log('[vuepress:sw] Content updated.') - app.$refs.layout.$emit('sw-updated') + + navigator.serviceWorker.getRegistration().then(registration => { + app.$refs.layout.$emit('sw-updated', new SWUpdateEvent(registration)) + }) }, offline () { console.log('[vuepress:sw] No internet connection found. App is running in offline mode.') diff --git a/lib/build.js b/lib/build.js index 9587da5542..cb37084bd3 100644 --- a/lib/build.js +++ b/lib/build.js @@ -81,11 +81,16 @@ module.exports = async function build (sourceDir, cliOptions = {}) { if (options.siteConfig.serviceWorker) { logger.wait('\nGenerating service worker...') const wbb = require('workbox-build') - wbb.generateSW({ + await wbb.generateSW({ swDest: path.resolve(outDir, 'service-worker.js'), globDirectory: outDir, globPatterns: ['**\/*.{js,css,html,png,jpg,jpeg,gif,svg,woff,woff2,eot,ttf,otf}'] }) + await fs.writeFile( + path.resolve(outDir, 'service-worker.js'), + await fs.readFile(path.resolve(__dirname, 'service-worker/skip-waiting.js'), 'utf8'), + { flag: 'a' } + ) } // DONE. diff --git a/lib/service-worker/skip-waiting.js b/lib/service-worker/skip-waiting.js new file mode 100644 index 0000000000..54fd8d37ce --- /dev/null +++ b/lib/service-worker/skip-waiting.js @@ -0,0 +1,12 @@ +addEventListener('message', event => { + const replyPort = event.ports[0] + const message = event.data + if (replyPort && message && message.type === 'skip-waiting') { + event.waitUntil( + self.skipWaiting().then( + () => replyPort.postMessage({ error: null }), + error => replyPort.postMessage({ error }) + ) + ) + } +}) From a304cc483cc28d57fee7361ea895c432673b5a19 Mon Sep 17 00:00:00 2001 From: Toru Nagashima Date: Tue, 29 May 2018 13:50:07 +0900 Subject: [PATCH 2/5] update for register-service-worker@1.3.0 --- lib/app/SWUpdateEvent.js | 7 +++++++ lib/app/clientEntry.js | 11 ++++------- package.json | 2 +- yarn.lock | 2 +- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/lib/app/SWUpdateEvent.js b/lib/app/SWUpdateEvent.js index 7a3f45e1c3..7063ce96e2 100644 --- a/lib/app/SWUpdateEvent.js +++ b/lib/app/SWUpdateEvent.js @@ -7,6 +7,13 @@ export default class SWUpdateEvent { }) } + /** + * Check if the new service worker exists or not. + */ + update () { + return this.registration.update() + } + /** * Activate new service worker to work 'location.reload()' with new data. */ diff --git a/lib/app/clientEntry.js b/lib/app/clientEntry.js index 292371ecb1..12eb56d8cd 100644 --- a/lib/app/clientEntry.js +++ b/lib/app/clientEntry.js @@ -42,16 +42,13 @@ router.onReady(() => { console.log('[vuepress:sw] Service worker is active.') app.$refs.layout.$emit('sw-ready') }, - cached () { + cached (registration) { console.log('[vuepress:sw] Content has been cached for offline use.') - app.$refs.layout.$emit('sw-cached') + app.$refs.layout.$emit('sw-cached', new SWUpdateEvent(registration)) }, - updated () { + updated (registration) { console.log('[vuepress:sw] Content updated.') - - navigator.serviceWorker.getRegistration().then(registration => { - app.$refs.layout.$emit('sw-updated', new SWUpdateEvent(registration)) - }) + app.$refs.layout.$emit('sw-updated', new SWUpdateEvent(registration)) }, offline () { console.log('[vuepress:sw] No internet connection found. App is running in offline mode.') diff --git a/package.json b/package.json index 728ee92581..da5649e4a3 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "portfinder": "^1.0.13", "postcss-loader": "^2.1.5", "prismjs": "^1.13.0", - "register-service-worker": "^1.2.0", + "register-service-worker": "^1.3.0", "semver": "^5.5.0", "stylus": "^0.54.5", "stylus-loader": "^3.0.2", diff --git a/yarn.lock b/yarn.lock index 2dfaa93da1..b7e9af8a0e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7022,7 +7022,7 @@ regexpu-core@^4.1.3, regexpu-core@^4.1.4: unicode-match-property-ecmascript "^1.0.3" unicode-match-property-value-ecmascript "^1.0.1" -register-service-worker@^1.2.0: +register-service-worker@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/register-service-worker/-/register-service-worker-1.3.0.tgz#02a0b7c40413b3c5ed1d801d764deb3aab1c3397" From 6ab6c5f7cef184f0e79defab000ddfae00a05031 Mon Sep 17 00:00:00 2001 From: Toru Nagashima Date: Thu, 31 May 2018 05:19:05 +0900 Subject: [PATCH 3/5] add popup --- lib/default-theme/Layout.vue | 12 ++++- lib/default-theme/SWUpdatePopup.vue | 82 +++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 lib/default-theme/SWUpdatePopup.vue diff --git a/lib/default-theme/Layout.vue b/lib/default-theme/Layout.vue index 7b422a41fa..ad65a47ce4 100644 --- a/lib/default-theme/Layout.vue +++ b/lib/default-theme/Layout.vue @@ -17,6 +17,7 @@ + @@ -27,13 +28,15 @@ import Home from './Home.vue' import Navbar from './Navbar.vue' import Page from './Page.vue' import Sidebar from './Sidebar.vue' +import SWUpdatePopup from './SWUpdatePopup.vue' import { resolveSidebarItems } from './util' export default { - components: { Home, Page, Sidebar, Navbar }, + components: { Home, Page, Sidebar, Navbar, SWUpdatePopup }, data () { return { - isSidebarOpen: false + isSidebarOpen: false, + swUpdateEvent: null } }, @@ -101,6 +104,8 @@ export default { nprogress.done() this.isSidebarOpen = false }) + + this.$on('sw-updated', this.onSWUpdated) }, methods: { @@ -124,6 +129,9 @@ export default { this.toggleSidebar(false) } } + }, + onSWUpdated (e) { + this.swUpdateEvent = e } } } diff --git a/lib/default-theme/SWUpdatePopup.vue b/lib/default-theme/SWUpdatePopup.vue new file mode 100644 index 0000000000..cbe5d76a85 --- /dev/null +++ b/lib/default-theme/SWUpdatePopup.vue @@ -0,0 +1,82 @@ + + + + + From 15097fd0683a5a831fb2fafedc1570fcddabbd15 Mon Sep 17 00:00:00 2001 From: Toru Nagashima Date: Thu, 31 May 2018 14:55:35 +0900 Subject: [PATCH 4/5] update docs --- docs/default-theme-config/README.md | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/docs/default-theme-config/README.md b/docs/default-theme-config/README.md index 304220dc0e..4d00481f8b 100644 --- a/docs/default-theme-config/README.md +++ b/docs/default-theme-config/README.md @@ -154,7 +154,7 @@ module.exports = { } ``` -::: tip +::: tip It is worth mentioning that when you disable this option, the corresponding script of this functionality will not be loaded. This is a small point in our performance optimization. ::: @@ -314,6 +314,31 @@ Note that it's `off` by default. If given `string`, it will be displayed as a pr Since `lastUpdated` is based on `git`, so you can only use it in a `git` repository. ::: +## Service Workers + +The `themeConfig.serviceWorker` option allows you to configure about service workers. + +### Popup UI to refresh contents + +The `themeConfig.serviceWorker.updatePopup` option enables the popup to refresh contents. The popup will be shown when the site is updated (the service worker is updated). It provides `refresh` button to allow users to refresh contents immediately. + +::: tip NOTE +If without the `refresh` button, the new service worker will be active after all clients are closed. +This means that visitors cannot see new contents until they close all tabs of your site. + +But the `refresh` button activates the new service worker immediately. +::: + +``` js +module.exports = { + themeConfig: { + serviceWorker { + updatePopup: true | {message: "New content is available.", buttonText: "Refresh"} + } + } +} +``` + ## Prev / Next Links Prev and next links are automatically inferred based on the sidebar order of the active page. You can also explicitly overwrite or disable them using `YAML front matter`: From 272706b9319e35090c18b3904b9b1d7b5e41bf52 Mon Sep 17 00:00:00 2001 From: ULIVZ <472590061@qq.com> Date: Fri, 13 Jul 2018 14:19:46 +0800 Subject: [PATCH 5/5] chore: trigger sw update --- lib/app/SWUpdateEvent.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/app/SWUpdateEvent.js b/lib/app/SWUpdateEvent.js index 7063ce96e2..fe6ab31c33 100644 --- a/lib/app/SWUpdateEvent.js +++ b/lib/app/SWUpdateEvent.js @@ -40,3 +40,4 @@ export default class SWUpdateEvent { }) } } +