UNPKG

vite-plugin-update

Version:

版本更新提示插件,基于 vite-plugin-pwa 二次封装的友好更新提示组件

380 lines (347 loc) 10.7 kB
"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; }