Bläddra i källkod

perf: 通过worker 优化 更新监测

lanceJiang 9 månader sedan
förälder
incheckning
659f321c57

+ 0 - 135
src/layout/components/CheckUpdates.vue

@@ -1,135 +0,0 @@
-<template>
-	<slot></slot>
-</template>
-<script setup lang="tsx">
-import { onMounted, onUnmounted, ref } from 'vue'
-import {ElButton, ElNotification} from 'element-plus'
-import { t } from '@/utils'
-const props = defineProps({
-	checkUpdatesInterval: {
-		type: Number,
-		default: 5
-	}
-})
-const lastVersionTag = ref('')
-let isCheckingUpdates = false
-let timer: ReturnType<typeof setInterval>
-const entrance = import.meta.env.VITE_PUBLIC_PATH
-let last_notify = null
-async function getVersionTag() {
-	try {
-		if (location.hostname === 'localhost' || location.hostname === '127.0.0.1') {
-			return null
-		}
-
-		const response = await fetch(entrance, {
-			cache: 'no-cache',
-			method: 'HEAD'
-		})
-
-		return response.headers.get('etag') || response.headers.get('last-modified')
-	} catch {
-		console.error('Failed to fetch version tag')
-		return null
-	}
-}
-
-async function checkForUpdates() {
-	const versionTag = await getVersionTag()
-	if (!versionTag) {
-		return
-	}
-
-	// 首次运行时不提示更新
-	if (!lastVersionTag.value) {
-		lastVersionTag.value = versionTag
-		return
-	}
-	if (lastVersionTag.value !== versionTag && versionTag) {
-		clearInterval(timer)
-		handleNotice(versionTag)
-	}
-}
-
-const handleNotice = versionTag => {
-	const onOk = () => {
-		lastVersionTag.value = versionTag
-		window.location.reload()
-	}
-	if (last_notify) return
-
-	const notify = ElNotification({
-		title: t('le.layout.checkUpdatesTitle'),
-		customClass: 'le-notification--check-app',
-		message: (
-			<div>
-				{t('le.layout.checkUpdatesDescription')}
-				<div class="text-right mt-[12px]">
-					<ElButton onClick={onClose}>{t('le.btn.cancel')}</ElButton>
-					<ElButton type="primary" onClick={onOk}>
-						{t('le.refresh')}
-					</ElButton>
-				</div>
-			</div>
-		),
-		position: 'bottom-right',
-		duration: 0,
-		onClose
-	})
-	last_notify = notify
-	function onClose() {
-		notify.close()
-		last_notify = null
-	}
-}
-
-function start() {
-	if (props.checkUpdatesInterval <= 0) {
-		return
-	}
-
-	// 每 checkUpdatesInterval(默认值为5) 分钟检查一次
-	timer = setInterval(checkForUpdates, props.checkUpdatesInterval * 60 * 1000)
-}
-function stop() {
-	clearInterval(timer)
-}
-function handleVisibilitychange() {
-	// console.error(isCheckingUpdates, 'isCheckingUpdates')
-	if (document.hidden) {
-		stop()
-	} else {
-		if (!isCheckingUpdates) {
-			isCheckingUpdates = true
-			checkForUpdates().finally(() => {
-				isCheckingUpdates = false
-				start()
-			})
-		}
-	}
-}
-onMounted(() => {
-	start()
-	document.addEventListener('visibilitychange', handleVisibilitychange)
-})
-
-onUnmounted(() => {
-	stop()
-	document.removeEventListener('visibilitychange', handleVisibilitychange)
-})
-</script>
-
-<style lang="scss">
-//.le-notification--check-app {
-.#{$prefix}notification--check-app {
-	.el-notification {
-		&__group {
-			margin: 0;
-			width: 100%;
-		}
-		&__content {
-			margin-right: -12px;
-		}
-	}
-}
-</style>

+ 91 - 0
src/layout/components/CheckUpdates/index.vue

@@ -0,0 +1,91 @@
+<template>
+	<slot></slot>
+</template>
+<script setup lang="tsx">
+import { onMounted, onUnmounted, ref } from 'vue'
+import { ElNotification, ElButton } from 'element-plus'
+import { t } from '@/utils'
+import { createWorker, createWorkFn } from './utils'
+const entrance = location.origin + import.meta.env.VITE_PUBLIC_PATH
+let last_notify = null
+const opts = {
+	intervalTime: 5 * 60 * 1000, // 5min
+	fetchUrl: entrance
+	// immediate: false
+}
+const worker = createWorker(createWorkFn, [])
+worker.addEventListener('message', (e: any) => {
+	// e.data: {type: 'showNotice', data: 'version'}
+	if (import.meta.env.MODE === 'production') handleNotice()
+})
+const start = (immediate = false) => {
+	worker.postMessage({ type: 'start', data: { ...opts, immediate } })
+}
+const stop = () => {
+	worker.postMessage({ type: 'stop' })
+}
+
+const handleNotice = (/*versionTag*/) => {
+	const onOk = () => {
+		window.location.reload()
+	}
+	if (last_notify) return
+
+	const notify = ElNotification({
+		title: t('le.layout.checkUpdatesTitle'),
+		customClass: 'le-notification--check-app',
+		message: (
+			<div>
+				{t('le.layout.checkUpdatesDescription')}
+				<div class="text-right mt-[12px]">
+					<ElButton onClick={onClose}>{t('le.btn.cancel')}</ElButton>
+					<ElButton type="primary" onClick={onOk}>
+						{t('le.refresh')}
+					</ElButton>
+				</div>
+			</div>
+		),
+		position: 'bottom-right',
+		duration: 0,
+		onClose
+	})
+	last_notify = notify
+	function onClose() {
+		notify.close()
+		last_notify = null
+	}
+}
+
+function handleVisibilitychange() {
+	if (document.hidden) {
+		stop()
+	} else {
+		start(true)
+	}
+}
+onMounted(() => {
+	start()
+	document.addEventListener('visibilitychange', handleVisibilitychange)
+})
+
+onUnmounted(() => {
+	stop()
+	worker.terminate()
+	document.removeEventListener('visibilitychange', handleVisibilitychange)
+})
+</script>
+
+<style lang="scss">
+//.le-notification--check-app {
+.#{$prefix}notification--check-app {
+	.el-notification {
+		&__group {
+			margin: 0;
+			width: 100%;
+		}
+		&__content {
+			margin-right: -12px;
+		}
+	}
+}
+</style>

+ 73 - 0
src/layout/components/CheckUpdates/utils.ts

@@ -0,0 +1,73 @@
+// 创建worker
+export const createWorker = (func: () => void, deps?: Array<() => void>) => {
+	// work 依赖 fns
+	const depsFncStr = `${deps?.map(dep => dep.toString()).join(';\n\n') || ''}`
+	const blob = new Blob(
+		[
+			`
+				${depsFncStr};
+				(${func.toString()})();
+			`
+		],
+		{
+			type: 'application/javascript'
+		}
+	)
+	return new Worker(window.URL.createObjectURL(blob))
+}
+export const createWorkFn = () => {
+	const opts = {
+		immediate: true,
+		intervalTime: 5000, // 毫秒
+		fetchUrl: '/'
+	}
+	let timer: any = null
+	const stop = () => {
+		clearInterval(timer)
+	}
+	const temp: Worker = self as any
+	let lastVersionTag = ''
+	const getVersionTag = async () => {
+		return fetch(opts.fetchUrl, { method: 'HEAD', cache: 'no-cache' })
+			.then(res => res.headers.get('etag') || res.headers.get('last-modified'))
+			.catch(e => {
+				console.error('Failed to fetch version tag', e)
+				return null
+			})
+	}
+	const doFetch = async () => {
+		const versionTag = await getVersionTag()
+		if (!versionTag) return
+		// 首次运行时不提示更新
+		if (!lastVersionTag) {
+			lastVersionTag = versionTag
+			return
+		}
+		if (lastVersionTag !== versionTag) {
+			stop()
+			temp.postMessage({ type: 'showNotice', data: versionTag })
+		}
+	}
+	temp.addEventListener('message', async (event: any) => {
+		// { type: 'start' | 'stop' }
+		switch (event.data.type) {
+			case 'start':
+			{
+				const data = event.data.data
+				if (data.intervalTime) opts.intervalTime = data.intervalTime
+				if (data.fetchUrl) opts.fetchUrl = data.fetchUrl
+				if (data.immediate) {
+					await doFetch()
+				}
+				if (timer) stop()
+				timer = setInterval(doFetch, opts.intervalTime)
+			}
+				break
+			case 'stop': {
+				stop()
+				break
+			}
+		}
+	})
+	return temp
+}

+ 1 - 1
src/layout/index.vue

@@ -14,7 +14,7 @@ import LayoutLeft from './LayoutLeft/index.vue'
 import LayoutLeftMix from './LayoutLeftMix/index.vue'
 import LayoutTop from './LayoutTop/index.vue'
 import LayoutTopMix from './LayoutTopMix/index.vue'
-import CheckUpdates from './components/CheckUpdates.vue'
+import CheckUpdates from './components/CheckUpdates/index.vue'
 import { ThemeSettings } from '@/layout/components'
 
 const LayoutComponents: Record<LayoutType, Component> = {