UNPKG

vite-plugin-trae-image-preloader

Version:

Vite plugin for preloading images in development mode

506 lines (460 loc) 20.6 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 index_exports = {}; __export(index_exports, { default: () => index_default, imagePreloader: () => imagePreloader }); module.exports = __toCommonJS(index_exports); function imagePreloader(options = {}) { const { extensions = ["png", "jpg", "jpeg", "gif", "svg", "webp", "avif"], verbose = true, framework = "auto", devOnly = true, delay = 1e3 } = options; let isDevMode = false; let detectedFramework = "vanilla"; const discoveredImages = /* @__PURE__ */ new Set(); const imageExtPattern = extensions.join("|"); const patterns = { // import xxx from './image.png' // import { xxx } from './image.png' importStatement: new RegExp(`import\\s+(?:[^'"]+\\s+from\\s+)?['"]([^'"]+\\.(${imageExtPattern}))['"]`, "gi"), // require('./image.png') requireStatement: new RegExp(`require\\(['"]([^'"]+\\.(${imageExtPattern}))['"]\\)`, "gi"), // url('./image.png') 或 url("./image.png") urlStatement: new RegExp(`url\\(['"]([^'"]+\\.(${imageExtPattern}))['"]\\)`, "gi"), // src="./image.png" 或 src='./image.png' srcAttribute: new RegExp(`src=['"]([^'"]+\\.(${imageExtPattern}))['"]`, "gi"), // background-image: url('./image.png') cssUrl: new RegExp(`background-image\\s*:\\s*url\\(['"]([^'"]+\\.(${imageExtPattern}))['"]\\)`, "gi"), // HTTP/HTTPS 图片URL (支持查询参数) // 匹配 https://domain.com/path/to/image 或带查询参数的URL httpImageUrl: new RegExp(`['"](https?://[^'"\\s]+(?:\\?[^'"\\s]*)?(?:&[^'"\\s]*)*)['"]`, "gi"), // 匹配包含 image 关键词的API URL apiImageUrl: new RegExp(`['"]([^'"]*(?:image|img|photo|pic)[^'"]*(?:\\?[^'"]*)?(?:&[^'"]*)*)['"]`, "gi") }; const extractImagesFromCode = (code, id) => { const images = []; let match; while ((match = patterns.importStatement.exec(code)) !== null) { images.push({ source: match[0], url: match[1], type: "import" }); } patterns.requireStatement.lastIndex = 0; while ((match = patterns.requireStatement.exec(code)) !== null) { images.push({ source: match[0], url: match[1], type: "require" }); } patterns.urlStatement.lastIndex = 0; while ((match = patterns.urlStatement.exec(code)) !== null) { images.push({ source: match[0], url: match[1], type: "url" }); } patterns.srcAttribute.lastIndex = 0; while ((match = patterns.srcAttribute.exec(code)) !== null) { images.push({ source: match[0], url: match[1], type: "url" }); } patterns.cssUrl.lastIndex = 0; while ((match = patterns.cssUrl.exec(code)) !== null) { images.push({ source: match[0], url: match[1], type: "url" }); } patterns.httpImageUrl.lastIndex = 0; while ((match = patterns.httpImageUrl.exec(code)) !== null) { const url = match[1]; if (url.includes("image") || url.includes("img") || url.includes("photo") || url.includes("pic") || url.includes("avatar") || url.includes("thumb") || url.includes("banner") || // 检查是否包含图片文件扩展名 new RegExp(`\\.(${imageExtPattern})(?:\\?|$)`, "i").test(url)) { images.push({ source: match[0], url, type: "url" }); } } patterns.apiImageUrl.lastIndex = 0; while ((match = patterns.apiImageUrl.exec(code)) !== null) { const url = match[1]; if (url.startsWith("http://") || url.startsWith("https://")) { images.push({ source: match[0], url, type: "url" }); } } return images; }; const generatePreloaderScript = () => { const imageUrls = JSON.stringify(Array.from(discoveredImages)); return ` <script> (function() { const imageUrls = ${imageUrls}; const preloadedImages = new Set(); const batchSize = 5; // \u6BCF\u6279\u5904\u74065\u5F20\u56FE\u7247 const batchInterval = 3000; // \u6BCF\u6279\u95F4\u96943\u79D2 // \u9884\u52A0\u8F7D\u56FE\u7247\u5E76\u5904\u7406 301 \u91CD\u5B9A\u5411 function preloadImage(url, retryCount = 0) { return new Promise((resolve) => { const img = new Image(); img.onload = () => { // \u68C0\u67E5\u662F\u5426\u88AB\u91CD\u5B9A\u5411\u5230\u9ED8\u8BA4\u56FE\u7247 // 301\u91CD\u5B9A\u5411\u540E\uFF0Cimg.src\u4F1A\u662F\u91CD\u5B9A\u5411\u540E\u7684URL const isDefaultImage = img.src.includes('page_image/default.jpeg'); if (${verbose}) { if (isDefaultImage) { console.log(\`\u{1F504} [Image Preloader] \u68C0\u6D4B\u5230\u9ED8\u8BA4\u56FE\u7247\uFF0C\u5F00\u59CB\u540E\u53F0\u8F6E\u8BE2: \${url}\`); console.log(\` \u91CD\u5B9A\u5411\u5230: \${img.src}\`); } else { console.log(\`\u2705 [Image Preloader] \u6210\u529F\u52A0\u8F7D\u771F\u5B9E\u56FE\u7247: \${url}\`); } } // \u65E0\u8BBA\u662F\u5426\u4E3A\u9ED8\u8BA4\u56FE\u7247\uFF0C\u90FD\u6807\u8BB0\u4E3A\u5DF2\u52A0\u8F7D preloadedImages.add(url); // \u5982\u679C\u662F\u9ED8\u8BA4\u56FE\u7247\uFF0C\u542F\u52A8\u540E\u53F0\u8F6E\u8BE2\u4EFB\u52A1 if (isDefaultImage && retryCount < 30) { // \u5148\u8BA9\u9ED8\u8BA4\u56FE\u7247\u663E\u793A\uFF0C\u7136\u540E\u5F00\u59CB\u8F6E\u8BE2 resolve(); startBackgroundPolling(url, img.src, retryCount); } else { resolve(); } }; img.onerror = () => { if (${verbose}) { console.warn(\`\u274C [Image Preloader] \u56FE\u7247\u52A0\u8F7D\u5931\u8D25: \${url}\`); } resolve(); }; // \u6DFB\u52A0\u65F6\u95F4\u6233\u907F\u514D\u7F13\u5B58 const timestamp = Date.now(); const separator = url.includes('?') ? '&' : '?'; img.src = \`\${url}\${separator}_t=\${timestamp}\`; }); } // \u540E\u53F0\u8F6E\u8BE2\u51FD\u6570 function startBackgroundPolling(originalUrl, defaultImageUrl, retryCount = 0) { if (retryCount >= 30) { if (${verbose}) { console.log(\`\u23F0 [Image Preloader] \u8FBE\u5230\u6700\u5927\u91CD\u8BD5\u6B21\u6570\uFF0C\u505C\u6B62\u8F6E\u8BE2: \${originalUrl}\`); } return; } setTimeout(() => { if (${verbose}) { console.log(\`\u{1F504} [Image Preloader] \u540E\u53F0\u8F6E\u8BE2\u4E2D... (\${retryCount + 1}/30): \${originalUrl}\`); } const img = new Image(); img.onload = () => { const isStillDefaultImage = img.src.includes('page_image/default.jpeg'); if (!isStillDefaultImage) { // \u83B7\u53D6\u5230\u771F\u5B9E\u56FE\u7247\u4E86\uFF0C\u66F4\u65B0\u9875\u9762\u4E2D\u6240\u6709\u4F7F\u7528\u8BE5URL\u7684\u56FE\u7247 updatePageImages(originalUrl, img.src); if (${verbose}) { console.log(\`\u{1F389} [Image Preloader] \u540E\u53F0\u8F6E\u8BE2\u6210\u529F\uFF0C\u5DF2\u66F4\u65B0\u9875\u9762\u56FE\u7247: \${originalUrl}\`); console.log(\` \u65B0\u56FE\u7247URL: \${img.src}\`); } } else { // \u8FD8\u662F\u9ED8\u8BA4\u56FE\u7247\uFF0C\u7EE7\u7EED\u8F6E\u8BE2 startBackgroundPolling(originalUrl, defaultImageUrl, retryCount + 1); } }; img.onerror = () => { // \u8BF7\u6C42\u5931\u8D25\uFF0C\u7EE7\u7EED\u8F6E\u8BE2 startBackgroundPolling(originalUrl, defaultImageUrl, retryCount + 1); }; // \u6DFB\u52A0\u65F6\u95F4\u6233\u907F\u514D\u7F13\u5B58 const timestamp = Date.now(); const separator = originalUrl.includes('?') ? '&' : '?'; img.src = \`\${originalUrl}\${separator}_t=\${timestamp}\`; }, 10000); // 10\u79D2\u540E\u91CD\u8BD5 } // \u66F4\u65B0\u9875\u9762\u4E2D\u6240\u6709\u4F7F\u7528\u6307\u5B9AURL\u7684\u56FE\u7247\u5143\u7D20 function updatePageImages(originalUrl, newImageUrl) { if (${verbose}) { console.log(\`\u{1F504} [Image Preloader] \u5F00\u59CB\u66F4\u65B0\u9875\u9762\u56FE\u7247: \${originalUrl} -> \${newImageUrl}\`); } let updateCount = 0; // \u66F4\u65B0\u6240\u6709img\u6807\u7B7E const images = document.querySelectorAll('img'); images.forEach(img => { // \u68C0\u67E5\u591A\u79CD\u53EF\u80FD\u7684\u5339\u914D\u65B9\u5F0F const imgSrc = img.src; const imgSrcWithoutTimestamp = imgSrc.split('_t=')[0].split('?_t=')[0]; const originalUrlWithoutTimestamp = originalUrl.split('_t=')[0].split('?_t=')[0]; // \u68C0\u67E5\u662F\u5426\u5339\u914D\uFF08\u5305\u62EC301\u91CD\u5B9A\u5411\u540E\u7684URL\uFF09 const isMatch = imgSrcWithoutTimestamp === originalUrlWithoutTimestamp || img.getAttribute('data-original-src') === originalUrl || img.getAttribute('src') === originalUrl || // \u68C0\u67E5\u662F\u5426\u662F\u9ED8\u8BA4\u56FE\u7247\u9700\u8981\u66F4\u65B0 (imgSrc.includes('page_image/default.jpeg') && img.hasAttribute('data-api-url') && img.getAttribute('data-api-url') === originalUrl); if (isMatch) { if (${verbose}) { console.log(\`\u{1F504} [Image Preloader] \u66F4\u65B0\u9875\u9762\u56FE\u7247\u5143\u7D20: \${imgSrc} -> \${newImageUrl}\`); } // \u4FDD\u5B58\u539F\u59CBURL\u5230data\u5C5E\u6027\uFF0C\u7528\u4E8E\u4E0B\u6B21\u5339\u914D img.setAttribute('data-original-src', originalUrl); img.setAttribute('data-api-url', originalUrl); img.src = newImageUrl; updateCount++; } }); // \u66F4\u65B0\u6240\u6709\u80CC\u666F\u56FE\u7247 const allElements = document.querySelectorAll('*'); allElements.forEach(el => { const computedStyle = window.getComputedStyle(el); const backgroundImage = computedStyle.backgroundImage; if (backgroundImage && backgroundImage !== 'none') { // \u63D0\u53D6URL const urlMatch = backgroundImage.match(/url\\(['"]?([^'"\\)]+)['"]?\\)/); if (urlMatch) { const bgUrl = urlMatch[1]; const bgUrlWithoutTimestamp = bgUrl.split('_t=')[0].split('?_t=')[0]; const originalUrlWithoutTimestamp = originalUrl.split('_t=')[0].split('?_t=')[0]; const isBgMatch = bgUrlWithoutTimestamp === originalUrlWithoutTimestamp || (bgUrl.includes('page_image/default.jpeg') && el.hasAttribute('data-api-bg-url') && el.getAttribute('data-api-bg-url') === originalUrl); if (isBgMatch) { if (${verbose}) { console.log(\`\u{1F504} [Image Preloader] \u66F4\u65B0\u80CC\u666F\u56FE\u7247: \${bgUrl} -> \${newImageUrl}\`); } el.setAttribute('data-api-bg-url', originalUrl); el.style.backgroundImage = \`url('\${newImageUrl}')\`; updateCount++; } } } }); if (${verbose}) { console.log(\`\u2705 [Image Preloader] \u56FE\u7247\u66F4\u65B0\u5B8C\u6210\uFF0C\u5171\u66F4\u65B0\u4E86 \${updateCount} \u4E2A\u5143\u7D20\`); } } // \u5206\u6279\u9884\u52A0\u8F7D\u56FE\u7247 function preloadImagesBatch() { // \u5C06\u56FE\u7247URL\u5206\u6210\u6279\u6B21 const batches = []; for (let i = 0; i < imageUrls.length; i += batchSize) { batches.push(imageUrls.slice(i, i + batchSize)); } if (${verbose}) { console.log('%c\u{1F5BC}\uFE0F Image Preloader:', 'color: #10b981; font-weight: bold;', \`Starting batch preload: \${imageUrls.length} images in \${batches.length} batches\`); } // \u9010\u6279\u5904\u7406 batches.forEach((batch, batchIndex) => { setTimeout(() => { if (${verbose}) { console.log('%c\u{1F5BC}\uFE0F Image Preloader:', 'color: #10b981; font-weight: bold;', \`Processing batch \${batchIndex + 1}/\${batches.length} (\${batch.length} images)\`); } const promises = batch.map(url => preloadImage(url).catch(e => null)); Promise.allSettled(promises).then(results => { const loaded = results.filter(r => r.status === 'fulfilled').length; const failed = results.filter(r => r.status === 'rejected').length; if (${verbose}) { console.log('%c\u{1F5BC}\uFE0F Image Preloader:', 'color: #10b981; font-weight: bold;', \`Batch \${batchIndex + 1} completed: \${loaded} loaded\` + (failed > 0 ? \`, \${failed} failed\` : '')); // \u5982\u679C\u662F\u6700\u540E\u4E00\u6279\uFF0C\u663E\u793A\u603B\u7ED3 if (batchIndex === batches.length - 1) { const totalLoaded = preloadedImages.size; const totalFailed = imageUrls.length - totalLoaded; console.log('%c\u{1F5BC}\uFE0F Image Preloader:', 'color: #10b981; font-weight: bold;', \`All batches completed: \${totalLoaded} total loaded\` + (totalFailed > 0 ? \`, \${totalFailed} failed\` : '')); } } }); }, batchIndex * batchInterval); }); } // \u517C\u5BB9\u65E7\u7684API function preloadAllImages() { preloadImagesBatch(); } // \u76D1\u63A7\u9875\u9762\u4E0A\u7684\u56FE\u7247\uFF0C\u81EA\u52A8\u5904\u7406\u9ED8\u8BA4\u56FE\u7247 function monitorPageImages() { const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { mutation.addedNodes.forEach((node) => { if (node.nodeType === Node.ELEMENT_NODE) { const element = node; // \u68C0\u67E5\u65B0\u6DFB\u52A0\u7684img\u5143\u7D20 if (element.tagName === 'IMG') { checkAndMarkApiImage(element); } // \u68C0\u67E5\u5B50\u5143\u7D20\u4E2D\u7684img const imgs = element.querySelectorAll ? element.querySelectorAll('img') : []; imgs.forEach(checkAndMarkApiImage); } }); }); }); // \u5F00\u59CB\u89C2\u5BDF observer.observe(document.body, { childList: true, subtree: true }); // \u68C0\u67E5\u5DF2\u5B58\u5728\u7684\u56FE\u7247 document.querySelectorAll('img').forEach(checkAndMarkApiImage); } function checkAndMarkApiImage(img) { const src = img.src; if (!src) return; // \u68C0\u67E5\u662F\u5426\u662F\u6211\u4EEC\u5173\u5FC3\u7684API URL const isApiUrl = imageUrls.some(url => { const cleanUrl = url.split('_t=')[0].split('?_t=')[0]; const cleanSrc = src.split('_t=')[0].split('?_t=')[0]; return cleanUrl === cleanSrc; }); if (isApiUrl) { // \u627E\u5230\u5BF9\u5E94\u7684\u539F\u59CBURL const originalUrl = imageUrls.find(url => { const cleanUrl = url.split('_t=')[0].split('?_t=')[0]; const cleanSrc = src.split('_t=')[0].split('?_t=')[0]; return cleanUrl === cleanSrc; }); if (originalUrl) { img.setAttribute('data-api-url', originalUrl); if (${verbose}) { console.log(\`\u{1F3F7}\uFE0F [Image Preloader] \u6807\u8BB0API\u56FE\u7247: \${src} (\u539F\u59CB: \${originalUrl})\`); } // \u5982\u679C\u68C0\u6D4B\u5230\u9ED8\u8BA4\u56FE\u7247\uFF0C\u542F\u52A8\u8F6E\u8BE2 if (src.includes('page_image/default.jpeg')) { if (${verbose}) { console.log(\`\u{1F504} [Image Preloader] \u9875\u9762\u68C0\u6D4B\u5230\u9ED8\u8BA4\u56FE\u7247\uFF0C\u542F\u52A8\u540E\u53F0\u8F6E\u8BE2: \${originalUrl}\`); } startBackgroundPolling(originalUrl, src, 0); } } } } // \u5EF6\u8FDF\u9884\u52A0\u8F7D\uFF0C\u907F\u514D\u963B\u585E\u521D\u59CB\u9875\u9762\u6E32\u67D3 setTimeout(preloadImagesBatch, ${delay}); // \u542F\u52A8\u9875\u9762\u56FE\u7247\u76D1\u63A7 setTimeout(monitorPageImages, 100); // \u5BFC\u51FA\u5230\u5168\u5C40\uFF0C\u65B9\u4FBF\u8C03\u8BD5 window.__imagePreloader = { preloadAllImages, preloadImagesBatch, startImagePolling: startBackgroundPolling, // \u66F4\u65B0\u4E3A\u540E\u53F0\u8F6E\u8BE2 getPreloadedImages: () => Array.from(preloadedImages), imageUrls, batchSize, batchInterval, pollInterval: 10000, maxAttempts: 30, updatePageImages // \u65B0\u589E\uFF1A\u624B\u52A8\u66F4\u65B0\u56FE\u7247\u7684\u65B9\u6CD5 }; })(); </script>`; }; return { name: "vite-plugin-image-preloader", configResolved(config) { isDevMode = config.command === "serve"; if (framework === "auto") { try { const packageJsonPath = config.root + "/package.json"; const fs = require("fs"); if (fs.existsSync(packageJsonPath)) { const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8")); const allDeps = { ...packageJson.dependencies, ...packageJson.devDependencies }; if (allDeps.react) { detectedFramework = "react"; } else if (allDeps.vue) { detectedFramework = "vue"; } } } catch { } } else { detectedFramework = framework; } if (verbose && isDevMode) { console.log("\u{1F5BC}\uFE0F Image Preloader Plugin enabled for", detectedFramework); } }, transform(code, id) { if (devOnly && !isDevMode) return null; const shouldProcess = id.endsWith(".tsx") || id.endsWith(".ts") || id.endsWith(".jsx") || id.endsWith(".js") || id.endsWith(".vue") || id.endsWith(".css") || id.endsWith(".scss") || id.endsWith(".sass") || id.endsWith(".less"); if (!shouldProcess) return null; const images = extractImagesFromCode(code, id); if (images.length > 0) { images.forEach((img) => { let resolvedUrl = img.url; if (img.url.startsWith("./") || img.url.startsWith("../")) { resolvedUrl = img.url; } else if (!img.url.startsWith("/") && !img.url.startsWith("http")) { resolvedUrl = "/" + img.url; } discoveredImages.add(resolvedUrl); }); if (verbose) { console.log(`\u{1F5BC}\uFE0F Found ${images.length} image(s) in ${id.split("/").pop()}`); images.forEach((img) => { console.log(` ${img.type}: ${img.url}`); }); } } return null; }, transformIndexHtml: { order: "post", handler(html) { if (devOnly && !isDevMode) return html; if (discoveredImages.size === 0) return html; const preloaderScript = generatePreloaderScript(); const headIndex = html.indexOf("</head>"); if (headIndex !== -1) { return html.slice(0, headIndex) + preloaderScript + html.slice(headIndex); } return html; } } }; } var index_default = imagePreloader; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { imagePreloader }); //# sourceMappingURL=index.js.map