Skip to content

Commit

Permalink
feat($pwa): add themeConfig.serviceWorker.updatePopup option (close: #…
Browse files Browse the repository at this point in the history
  • Loading branch information
mysticatea authored and ulivz committed Jul 14, 2018
1 parent 887a0a6 commit 14dbd1e
Show file tree
Hide file tree
Showing 9 changed files with 187 additions and 11 deletions.
25 changes: 25 additions & 0 deletions docs/default-theme-config/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,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`:
Expand Down
43 changes: 43 additions & 0 deletions lib/app/SWUpdateEvent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
export default class SWUpdateEvent {
constructor (registration) {
Object.defineProperty(this, 'registration', {
value: registration,
configurable: true,
writable: true
})
}

/**
* 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.
*/
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])
})
}
}

9 changes: 5 additions & 4 deletions lib/app/clientEntry.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* global BASE_URL, GA_ID, ga, SW_ENABLED, VUEPRESS_VERSION, LAST_COMMIT_HASH*/

import { createApp } from './app'
import SWUpdateEvent from './SWUpdateEvent'
import { register } from 'register-service-worker'

const { app, router } = createApp()
Expand Down Expand Up @@ -46,13 +47,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.')
app.$refs.layout.$emit('sw-updated')
app.$refs.layout.$emit('sw-updated', new SWUpdateEvent(registration))
},
offline () {
console.log('[vuepress:sw] No internet connection found. App is running in offline mode.')
Expand Down
7 changes: 6 additions & 1 deletion lib/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,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.
Expand Down
12 changes: 10 additions & 2 deletions lib/default-theme/Layout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<slot name="page-top" slot="top"/>
<slot name="page-bottom" slot="bottom"/>
</Page>
<SWUpdatePopup :updateEvent="swUpdateEvent" />
</div>
</template>

Expand All @@ -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
}
},
Expand Down Expand Up @@ -101,6 +104,8 @@ export default {
nprogress.done()
this.isSidebarOpen = false
})
this.$on('sw-updated', this.onSWUpdated)
},
methods: {
Expand All @@ -124,6 +129,9 @@ export default {
this.toggleSidebar(false)
}
}
},
onSWUpdated (e) {
this.swUpdateEvent = e
}
}
}
Expand Down
82 changes: 82 additions & 0 deletions lib/default-theme/SWUpdatePopup.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<template>
<transition name="sw-update-popup">
<div v-if="enabled" class="sw-update-popup">
{{message}}<br>
<button @click="reload">{{buttonText}}</button>
</div>
</transition>
</template>

<script>
export default {
props: {
updateEvent: {
type: Object,
default: null
}
},
computed: {
popupConfig () {
for (const config of [this.$themeLocaleConfig, this.$site.themeConfig]) {
const sw = config.serviceWorker
if (sw && sw.updatePopup) {
return typeof sw.updatePopup === 'object' ? sw.updatePopup : {}
}
}
return null
},
enabled () {
return Boolean(this.popupConfig && this.updateEvent)
},
message () {
const c = this.popupConfig
return (c && c.message) || 'New content is available.'
},
buttonText () {
const c = this.popupConfig
return (c && c.buttonText) || 'Refresh'
}
},
methods: {
reload () {
if (this.updateEvent) {
this.updateEvent.skipWaiting().then(() => {
location.reload(true)
})
this.updateEvent = null
}
}
}
}
</script>

<style lang="stylus">
@import './styles/config.styl'
.sw-update-popup
position fixed
right 1em
bottom 1em
padding 1em
border 1px solid $accentColor
border-radius 3px
background #fff
box-shadow 0 4px 16px rgba(0, 0, 0, 0.5)
text-align center
button
margin-top 0.5em
padding 0.25em 2em
.sw-update-popup-enter-active, .sw-update-popup-leave-active
transition opacity 0.3s, transform 0.3s
.sw-update-popup-enter, .sw-update-popup-leave-to
opacity 0
transform translate(0, 50%) scale(0.5)
</style>
12 changes: 12 additions & 0 deletions lib/service-worker/skip-waiting.js
Original file line number Diff line number Diff line change
@@ -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 })
)
)
}
})
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,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",
Expand Down
6 changes: 3 additions & 3 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6941,9 +6941,9 @@ regexpu-core@^4.1.3, regexpu-core@^4.1.4:
unicode-match-property-ecmascript "^1.0.4"
unicode-match-property-value-ecmascript "^1.0.2"

register-service-worker@^1.2.0:
version "1.4.1"
resolved "https://registry.yarnpkg.com/register-service-worker/-/register-service-worker-1.4.1.tgz#4b4c9b4200fc697942c6ae7d611349587b992b2f"
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"

registry-auth-token@^3.0.1:
version "3.3.2"
Expand Down

0 comments on commit 14dbd1e

Please sign in to comment.