mirror of
https://gitee.com/element-plus/element-plus.git
synced 2024-11-30 02:08:12 +08:00
docs: only cache the user preferred language pages in the PWA (#7568)
* docs: cache the preferred lang pages in the PWA * chore: remove index.html in manifest * fix: no cache the '/' page * perf: optimize code * docs: add always refresh from PWA
This commit is contained in:
parent
325f84d44f
commit
0c92e8a8d2
@ -1,5 +1,6 @@
|
||||
{
|
||||
"message": "New content available, click on refresh button to update.",
|
||||
"refresh": "Refresh",
|
||||
"always-refresh": "Always Refresh",
|
||||
"close": "Close"
|
||||
}
|
||||
|
@ -25,4 +25,10 @@
|
||||
? toPath
|
||||
: toPath.concat('/')
|
||||
}
|
||||
if (navigator && navigator.serviceWorker.controller) {
|
||||
navigator.serviceWorker.controller.postMessage({
|
||||
type: 'LANG',
|
||||
lang: userPreferredLang,
|
||||
})
|
||||
}
|
||||
})()
|
||||
|
189
docs/.vitepress/sw.ts
Normal file
189
docs/.vitepress/sw.ts
Normal file
@ -0,0 +1,189 @@
|
||||
import { cacheNames, clientsClaim } from 'workbox-core'
|
||||
import type { ManifestEntry } from 'workbox-build'
|
||||
|
||||
declare let self: ServiceWorkerGlobalScope & {
|
||||
__WB_MANIFEST: ManifestEntry[]
|
||||
}
|
||||
const manifest = self.__WB_MANIFEST
|
||||
const cacheName = cacheNames.runtime
|
||||
const defaultLang = manifest.some((item) => {
|
||||
return item.url.includes(navigator.language)
|
||||
})
|
||||
? navigator.language
|
||||
: 'en-US'
|
||||
|
||||
let userPreferredLang = ''
|
||||
let cacheEntries: RequestInfo[] = []
|
||||
let cacheManifestURLs: string[] = []
|
||||
let manifestURLs: string[] = []
|
||||
|
||||
class LangDB {
|
||||
private db: IDBDatabase | undefined
|
||||
private databaseName = 'PWA_DB'
|
||||
private version = 1
|
||||
private storeNames = 'lang'
|
||||
|
||||
constructor() {
|
||||
this.initDB()
|
||||
}
|
||||
|
||||
private initDB() {
|
||||
return new Promise<boolean>((resolve) => {
|
||||
const request = indexedDB.open(this.databaseName, this.version)
|
||||
|
||||
request.onsuccess = (event) => {
|
||||
this.db = (event.target as IDBOpenDBRequest).result
|
||||
resolve(true)
|
||||
}
|
||||
|
||||
request.onupgradeneeded = (event) => {
|
||||
this.db = (event.target as IDBOpenDBRequest).result
|
||||
|
||||
if (!this.db.objectStoreNames.contains(this.storeNames)) {
|
||||
this.db.createObjectStore(this.storeNames, { keyPath: 'id' })
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private async initLang() {
|
||||
this.db!.transaction(this.storeNames, 'readwrite')
|
||||
.objectStore(this.storeNames)
|
||||
.add({ id: 1, lang: defaultLang })
|
||||
}
|
||||
|
||||
async getLang() {
|
||||
if (!this.db) await this.initDB()
|
||||
|
||||
return new Promise<string>((resolve) => {
|
||||
const request = this.db!.transaction(this.storeNames)
|
||||
.objectStore(this.storeNames)
|
||||
.get(1)
|
||||
|
||||
request.onsuccess = () => {
|
||||
if (request.result) {
|
||||
resolve(request.result.lang)
|
||||
} else {
|
||||
this.initLang()
|
||||
resolve(defaultLang)
|
||||
}
|
||||
}
|
||||
|
||||
request.onerror = () => {
|
||||
resolve(defaultLang)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async setLang(lang: string) {
|
||||
if (userPreferredLang !== lang) {
|
||||
userPreferredLang = lang
|
||||
cacheEntries = []
|
||||
cacheManifestURLs = []
|
||||
manifestURLs = []
|
||||
|
||||
if (!this.db) await this.initDB()
|
||||
|
||||
this.db!.transaction(this.storeNames, 'readwrite')
|
||||
.objectStore(this.storeNames)
|
||||
.put({ id: 1, lang })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function initManifest() {
|
||||
userPreferredLang = userPreferredLang || (await langDB.getLang())
|
||||
// match the data that needs to be cached
|
||||
// NOTE: When the structure of the document dist files changes, it needs to be changed here
|
||||
const cacheList = [
|
||||
userPreferredLang,
|
||||
`assets/(${userPreferredLang}|app|index|style|chunks)`,
|
||||
'images',
|
||||
'android-chrome',
|
||||
'apple-touch-icon',
|
||||
'manifest.webmanifest',
|
||||
]
|
||||
const regExp = new RegExp(`^(${cacheList.join('|')})`)
|
||||
|
||||
for (const item of manifest) {
|
||||
const url = new URL(item.url, self.location.origin)
|
||||
manifestURLs.push(url.href)
|
||||
|
||||
if (regExp.test(item.url) || /^\/$/.test(item.url)) {
|
||||
const request = new Request(url.href, { credentials: 'same-origin' })
|
||||
cacheEntries.push(request)
|
||||
cacheManifestURLs.push(url.href)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const langDB = new LangDB()
|
||||
|
||||
self.addEventListener('install', (event) => {
|
||||
event.waitUntil(
|
||||
caches.open(cacheName).then(async (cache) => {
|
||||
if (!cacheEntries.length) await initManifest()
|
||||
|
||||
return cache.addAll(cacheEntries)
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
self.addEventListener('activate', (event: ExtendableEvent) => {
|
||||
// clean up outdated runtime cache
|
||||
event.waitUntil(
|
||||
caches.open(cacheName).then(async (cache) => {
|
||||
if (!cacheManifestURLs.length) await initManifest()
|
||||
|
||||
cache.keys().then((keys) => {
|
||||
keys.forEach((request) => {
|
||||
// clean up those who are not listed in cacheManifestURLs
|
||||
!cacheManifestURLs.includes(request.url) && cache.delete(request)
|
||||
})
|
||||
})
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
self.addEventListener('fetch', (event) => {
|
||||
event.respondWith(
|
||||
caches.match(event.request).then(async (response) => {
|
||||
// when the cache is hit, it returns directly to the cache
|
||||
if (response) return response
|
||||
if (!manifestURLs.length) await initManifest()
|
||||
const requestClone = event.request.clone()
|
||||
|
||||
// otherwise create a new fetch request
|
||||
return fetch(requestClone)
|
||||
.then((response) => {
|
||||
const responseClone = response.clone()
|
||||
|
||||
if (response.type !== 'basic' && response.status !== 200) {
|
||||
return response
|
||||
}
|
||||
|
||||
// cache the data contained in the manifestURLs list
|
||||
manifestURLs.includes(requestClone.url) &&
|
||||
caches.open(cacheName).then((cache) => {
|
||||
cache.put(requestClone, responseClone)
|
||||
})
|
||||
return response
|
||||
})
|
||||
.catch((err) => {
|
||||
throw new Error(`Failed to load resource ${requestClone.url}, ${err}`)
|
||||
})
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
self.addEventListener('message', (event) => {
|
||||
if (event.data) {
|
||||
if (event.data.type === 'SKIP_WAITING') {
|
||||
self.skipWaiting()
|
||||
} else if (event.data.type === 'LANG') {
|
||||
langDB.setLang(event.data.lang)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
clientsClaim()
|
@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { computed, watch } from 'vue'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { useRegisterSW } from 'virtual:pwa-register/vue'
|
||||
import { useLang } from '../composables/lang'
|
||||
import pwaLocale from '../../i18n/component/pwa.json'
|
||||
@ -7,15 +8,23 @@ import pwaLocale from '../../i18n/component/pwa.json'
|
||||
const lang = useLang()
|
||||
const locale = computed(() => pwaLocale[lang.value])
|
||||
const { needRefresh, updateServiceWorker } = useRegisterSW()
|
||||
const alwaysRefresh = useStorage('PWA_Always_Refresh', false)
|
||||
|
||||
watch(needRefresh, (value) => {
|
||||
value && alwaysRefresh.value && updateServiceWorker()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<transition name="pwa-popup">
|
||||
<el-card v-if="needRefresh" class="pwa-card" role="alert">
|
||||
<el-card v-if="!alwaysRefresh && needRefresh" class="pwa-card" role="alert">
|
||||
<p class="pwa-card-text">{{ locale.message }}</p>
|
||||
<el-button type="primary" plain @click="updateServiceWorker()">
|
||||
{{ locale.refresh }}
|
||||
</el-button>
|
||||
<el-button plain @click="alwaysRefresh = true">
|
||||
{{ locale['always-refresh'] }}
|
||||
</el-button>
|
||||
<el-button plain @click="needRefresh = false">
|
||||
{{ locale.close }}
|
||||
</el-button>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { computed } from 'vue'
|
||||
import { useRoute, useRouter } from 'vitepress'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { isClient, useStorage } from '@vueuse/core'
|
||||
import { PREFERRED_LANG_KEY } from '../constant'
|
||||
|
||||
import langs from '../../i18n/lang.json'
|
||||
@ -50,6 +50,13 @@ export const useTranslation = () => {
|
||||
const goTo = `/${targetLang}/${route.path.slice(firstSlash + 1)}`
|
||||
|
||||
router.go(goTo)
|
||||
|
||||
if (isClient) {
|
||||
navigator?.serviceWorker.controller?.postMessage({
|
||||
type: 'LANG',
|
||||
lang: targetLang,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -10,6 +10,7 @@
|
||||
"strict": true,
|
||||
"noImplicitAny": false,
|
||||
"skipLibCheck": true,
|
||||
"lib": ["WebWorker"],
|
||||
"paths": {
|
||||
"element-plus": ["../packages/element-plus"],
|
||||
"~/*": ["./.vitepress/vitepress/*"]
|
||||
|
@ -99,8 +99,25 @@ export default defineConfig(async ({ mode }) => {
|
||||
Inspect(),
|
||||
mkcert(),
|
||||
VitePWA({
|
||||
strategies: 'injectManifest',
|
||||
srcDir: '.vitepress',
|
||||
filename: 'sw.ts',
|
||||
outDir: '.vitepress/dist',
|
||||
includeAssets: ['images/**'],
|
||||
injectManifest: {
|
||||
manifestTransforms: [
|
||||
(manifest) => {
|
||||
for (const item of manifest) {
|
||||
if (item.url.endsWith('index.html')) {
|
||||
const url = item.url.replace('index.html', '')
|
||||
item.url = url ? url : '/'
|
||||
}
|
||||
}
|
||||
|
||||
return { manifest }
|
||||
},
|
||||
],
|
||||
},
|
||||
manifest: {
|
||||
id: '/',
|
||||
name: 'Element Plus',
|
||||
|
Loading…
Reference in New Issue
Block a user