vite-plugin-update
Version:
版本更新提示插件,基于 vite-plugin-pwa 二次封装的友好更新提示组件
380 lines (347 loc) • 10.7 kB
JavaScript
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
default: () => vitePluginUpdate
});
module.exports = __toCommonJS(src_exports);
var import_vite_plugin_pwa = require("vite-plugin-pwa");
// src/checkUpdate.ts
var updatePromptStyles = `
.app-update-prompt {
position: fixed;
top: 20px;
right: 20px;
z-index: 999999;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px 24px;
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
max-width: 420px;
animation: slideInDown 0.3s ease-out;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
}
@keyframes slideInDown {
from {
transform: translateY(-100%);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
.app-update-prompt__icon {
width: 24px;
height: 24px;
margin-bottom: 12px;
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
.app-update-prompt__title {
font-size: 18px;
font-weight: 600;
margin: 0 0 8px 0;
display: flex;
align-items: center;
gap: 8px;
}
.app-update-prompt__content {
font-size: 14px;
line-height: 1.5;
margin: 0 0 16px 0;
opacity: 0.95;
}
.app-update-prompt__actions {
display: flex;
gap: 8px;
}
.app-update-prompt__button {
flex: 1;
padding: 10px 20px;
border: none;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.app-update-prompt__button--primary {
background: white;
color: #667eea;
}
.app-update-prompt__button--primary:hover {
background: #f8f9fa;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(255, 255, 255, 0.2);
}
.app-update-prompt__button--secondary {
background: rgba(255, 255, 255, 0.2);
color: white;
backdrop-filter: blur(10px);
}
.app-update-prompt__button--secondary:hover {
background: rgba(255, 255, 255, 0.3);
}
.app-update-prompt__button:active {
transform: translateY(0);
}
`;
var createUpdatePrompt = (onUpdate, onDismiss, options = {}) => {
const {
title = "\u{1F389} \u53D1\u73B0\u65B0\u7248\u672C",
description = "\u68C0\u6D4B\u5230\u5E94\u7528\u6709\u65B0\u7248\u672C\u53EF\u7528\uFF0C\u662F\u5426\u7ACB\u5373\u66F4\u65B0\uFF1F",
confirmText = "\u7ACB\u5373\u66F4\u65B0",
cancelText = "\u7A0D\u540E\u63D0\u9192"
} = options;
const prompt = document.createElement("div");
prompt.className = "app-update-prompt";
prompt.innerHTML = `
<div class="app-update-prompt__title">
${title}
</div>
<div class="app-update-prompt__content">
${description}
</div>
<div class="app-update-prompt__actions">
<button class="app-update-prompt__button app-update-prompt__button--secondary" data-action="cancel">
${cancelText}
</button>
<button class="app-update-prompt__button app-update-prompt__button--primary" data-action="update">
${confirmText}
</button>
</div>
`;
const updateBtn = prompt.querySelector('[data-action="update"]');
const cancelBtn = prompt.querySelector('[data-action="cancel"]');
updateBtn?.addEventListener("click", () => {
prompt.style.animation = "slideInDown 0.3s ease-out reverse";
setTimeout(() => {
document.body.removeChild(prompt);
onUpdate();
}, 300);
});
cancelBtn?.addEventListener("click", () => {
prompt.style.animation = "slideInDown 0.3s ease-out reverse";
setTimeout(() => {
document.body.removeChild(prompt);
onDismiss();
}, 300);
});
return prompt;
};
// src/index.ts
var generateUpdateScript = (options) => {
const usePWA = options.usePWA;
if (usePWA) {
return `
<script>
(function() {
if (typeof window === 'undefined') return;
// \u6CE8\u5165\u6837\u5F0F
const style = document.createElement('style');
style.textContent = ${JSON.stringify(updatePromptStyles)};
document.head.appendChild(style);
// \u6CE8\u518C Service Worker\uFF08\u4EC5\u5728\u6D4F\u89C8\u5668\u73AF\u5883\uFF09
if ('serviceWorker' in navigator) {
let updateAvailable = false;
let swRegistration = null;
async function registerSW() {
try {
const registration = await navigator.serviceWorker.register('/sw.js', { scope: '/' });
swRegistration = registration;
// \u68C0\u67E5\u662F\u5426\u6709\u66F4\u65B0\u7684 Service Worker \u7B49\u5F85
if (registration.waiting) {
showUpdatePrompt();
}
// \u76D1\u542C\u65B0\u7684 Service Worker \u5B89\u88C5
registration.addEventListener('updatefound', () => {
const newWorker = registration.installing;
if (newWorker) {
newWorker.addEventListener('statechange', () => {
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
showUpdatePrompt();
}
});
}
});
// \u76D1\u542C Service Worker \u63A7\u5236\u6743\u53D8\u5316
navigator.serviceWorker.addEventListener('controllerchange', () => {
window.location.reload();
});
} catch (error) {
console.error('Service Worker \u6CE8\u518C\u5931\u8D25:', error);
}
}
async function showUpdatePrompt() {
if (updateAvailable) return;
updateAvailable = true;
// \u79FB\u9664\u73B0\u6709\u7684\u63D0\u793A
const existing = document.querySelector('.app-update-prompt');
if (existing) return;
const prompt = ${createUpdatePrompt.toString()};
const element = prompt(
() => {
// \u89E6\u53D1\u66F4\u65B0
if (swRegistration && swRegistration.waiting) {
swRegistration.waiting.postMessage({ type: 'SKIP_WAITING' });
setTimeout(() => window.location.reload(), 100);
}
},
() => {
updateAvailable = false;
},
{
title: ${JSON.stringify(options.title)},
description: ${JSON.stringify(options.description)},
confirmText: ${JSON.stringify(options.confirmText)},
cancelText: ${JSON.stringify(options.cancelText)}
}
);
document.body.appendChild(element);
}
// \u5EF6\u8FDF\u6CE8\u518C\uFF0C\u786E\u4FDD\u9875\u9762\u52A0\u8F7D\u5B8C\u6210
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', registerSW);
} else {
registerSW();
}
}
})();
</script>
`;
} else {
return `
<script>
(function() {
if (typeof window === 'undefined') return;
// \u6CE8\u5165\u6837\u5F0F
const style = document.createElement('style');
style.textContent = ${JSON.stringify(updatePromptStyles)};
document.head.appendChild(style);
let updateAvailable = false;
async function checkForUpdate() {
try {
const response = await fetch('/index.html?_=' + Date.now(), {
method: 'HEAD',
cache: 'no-store'
});
const newEtag = response.headers.get('ETag');
const storedEtag = localStorage.getItem('app-update-etag') || '';
if (newEtag && newEtag !== storedEtag && storedEtag !== '') {
// \u53D1\u73B0\u65B0\u7248\u672C
localStorage.setItem('app-update-etag', newEtag);
showUpdatePrompt();
} else if (!storedEtag && newEtag) {
// \u9996\u6B21\u52A0\u8F7D\uFF0C\u4FDD\u5B58 ETag
localStorage.setItem('app-update-etag', newEtag);
}
} catch (error) {
console.error('\u66F4\u65B0\u68C0\u67E5\u5931\u8D25:', error);
}
}
function showUpdatePrompt() {
if (updateAvailable) return;
updateAvailable = true;
const prompt = ${createUpdatePrompt.toString()};
const element = prompt(
() => {
window.location.reload();
},
() => {
updateAvailable = false;
},
{
title: ${JSON.stringify(options.title)},
description: ${JSON.stringify(options.description)},
confirmText: ${JSON.stringify(options.confirmText)},
cancelText: ${JSON.stringify(options.cancelText)}
}
);
document.body.appendChild(element);
}
// \u5EF6\u8FDF\u542F\u52A8\u68C0\u67E5
setTimeout(() => {
checkForUpdate();
setInterval(checkForUpdate, ${options.checkInterval});
}, 3000);
})();
</script>
`;
}
};
function vitePluginUpdate(options = {}) {
const {
enable = true,
checkInterval = 6e4,
title = "\u{1F389} \u53D1\u73B0\u65B0\u7248\u672C",
description = "\u68C0\u6D4B\u5230\u5E94\u7528\u6709\u65B0\u7248\u672C\u53EF\u7528\uFF0C\u662F\u5426\u7ACB\u5373\u66F4\u65B0\uFF1F",
confirmText = "\u7ACB\u5373\u66F4\u65B0",
cancelText = "\u7A0D\u540E\u63D0\u9192",
onlyProduction = true,
usePWA = true,
pwaOptions = {}
} = options;
const opts = {
enable,
checkInterval,
title,
description,
confirmText,
cancelText,
onlyProduction,
usePWA,
pwaOptions
};
const plugins = [];
if (usePWA && enable) {
plugins.push(
(0, import_vite_plugin_pwa.VitePWA)({
registerType: pwaOptions.registerType || "prompt",
strategies: pwaOptions.strategy || "generateSW",
workbox: {
cleanupOutdatedCaches: true,
...pwaOptions.workbox
}
})
);
}
if (enable) {
plugins.push({
name: "vite-plugin-update-prompt",
enforce: "post",
apply(config, { command }) {
if (onlyProduction && command === "serve") {
return false;
}
return true;
},
transformIndexHtml(html) {
const scripts = generateUpdateScript(opts);
return html.replace("</body>", `${scripts}</body>`);
}
});
}
return plugins;
}