UNPKG

vite-plugin-trae-image-preloader

Version:

Vite plugin for preloading images in development mode

1 lines 26 kB
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Plugin } from 'vite'\n\nexport interface ImagePreloaderOptions {\n /** 图片文件扩展名 */\n extensions?: string[]\n \n /** 是否在控制台显示预加载信息 */\n verbose?: boolean\n \n /** 框架类型 */\n framework?: 'react' | 'vue' | 'auto'\n \n /** 是否只在开发模式下启用 */\n devOnly?: boolean\n \n /** 预加载延迟时间 (ms) */\n delay?: number\n}\n\ninterface ImageInfo {\n source: string\n url: string\n type: 'import' | 'require' | 'url'\n}\n\nfunction imagePreloader(options: ImagePreloaderOptions = {}): Plugin {\n const {\n extensions = ['png', 'jpg', 'jpeg', 'gif', 'svg', 'webp', 'avif'],\n verbose = true,\n framework = 'auto',\n devOnly = true,\n delay = 1000\n } = options\n\n let isDevMode = false\n let detectedFramework: 'react' | 'vue' | 'vanilla' = 'vanilla'\n const discoveredImages = new Set<string>()\n\n // 创建匹配图片的正则表达式\n const imageExtPattern = extensions.join('|')\n const patterns = {\n // import xxx from './image.png'\n // import { xxx } from './image.png'\n importStatement: new RegExp(`import\\\\s+(?:[^'\"]+\\\\s+from\\\\s+)?['\"]([^'\"]+\\\\.(${imageExtPattern}))['\"]`, 'gi'),\n \n // require('./image.png')\n requireStatement: new RegExp(`require\\\\(['\"]([^'\"]+\\\\.(${imageExtPattern}))['\"]\\\\)`, 'gi'),\n \n // url('./image.png') 或 url(\"./image.png\")\n urlStatement: new RegExp(`url\\\\(['\"]([^'\"]+\\\\.(${imageExtPattern}))['\"]\\\\)`, 'gi'),\n \n // src=\"./image.png\" 或 src='./image.png'\n srcAttribute: new RegExp(`src=['\"]([^'\"]+\\\\.(${imageExtPattern}))['\"]`, 'gi'),\n \n // background-image: url('./image.png')\n cssUrl: new RegExp(`background-image\\\\s*:\\\\s*url\\\\(['\"]([^'\"]+\\\\.(${imageExtPattern}))['\"]\\\\)`, 'gi'),\n \n // HTTP/HTTPS 图片URL (支持查询参数)\n // 匹配 https://domain.com/path/to/image 或带查询参数的URL\n httpImageUrl: new RegExp(`['\"](https?://[^'\"\\\\s]+(?:\\\\?[^'\"\\\\s]*)?(?:&[^'\"\\\\s]*)*)['\"]`, 'gi'),\n \n // 匹配包含 image 关键词的API URL\n apiImageUrl: new RegExp(`['\"]([^'\"]*(?:image|img|photo|pic)[^'\"]*(?:\\\\?[^'\"]*)?(?:&[^'\"]*)*)['\"]`, 'gi')\n }\n\n const extractImagesFromCode = (code: string, id: string): ImageInfo[] => {\n const images: ImageInfo[] = []\n \n // 匹配 import 语句\n let match: RegExpExecArray | null\n while ((match = patterns.importStatement.exec(code)) !== null) {\n images.push({\n source: match[0],\n url: match[1],\n type: 'import'\n })\n }\n \n // 匹配 require 语句\n patterns.requireStatement.lastIndex = 0\n while ((match = patterns.requireStatement.exec(code)) !== null) {\n images.push({\n source: match[0],\n url: match[1],\n type: 'require'\n })\n }\n \n // 匹配 url() 语句\n patterns.urlStatement.lastIndex = 0\n while ((match = patterns.urlStatement.exec(code)) !== null) {\n images.push({\n source: match[0],\n url: match[1],\n type: 'url'\n })\n }\n \n // 匹配 src 属性\n patterns.srcAttribute.lastIndex = 0\n while ((match = patterns.srcAttribute.exec(code)) !== null) {\n images.push({\n source: match[0],\n url: match[1],\n type: 'url'\n })\n }\n \n // 匹配 CSS background-image\n patterns.cssUrl.lastIndex = 0\n while ((match = patterns.cssUrl.exec(code)) !== null) {\n images.push({\n source: match[0],\n url: match[1],\n type: 'url'\n })\n }\n \n // 匹配 HTTP/HTTPS 图片URL\n patterns.httpImageUrl.lastIndex = 0\n while ((match = patterns.httpImageUrl.exec(code)) !== null) {\n const url = match[1]\n // 检查是否是图片相关的URL\n if (url.includes('image') || url.includes('img') || url.includes('photo') || url.includes('pic') || \n url.includes('avatar') || url.includes('thumb') || url.includes('banner') ||\n // 检查是否包含图片文件扩展名\n new RegExp(`\\\\.(${imageExtPattern})(?:\\\\?|$)`, 'i').test(url)) {\n images.push({\n source: match[0],\n url: url,\n type: 'url'\n })\n }\n }\n \n // 匹配包含 image 关键词的API URL\n patterns.apiImageUrl.lastIndex = 0\n while ((match = patterns.apiImageUrl.exec(code)) !== null) {\n const url = match[1]\n // 确保是完整的URL\n if (url.startsWith('http://') || url.startsWith('https://')) {\n images.push({\n source: match[0],\n url: url,\n type: 'url'\n })\n }\n }\n \n return images\n }\n\n const generatePreloaderScript = (): string => {\n const imageUrls = JSON.stringify(Array.from(discoveredImages))\n \n return `\n<script>\n (function() {\n const imageUrls = ${imageUrls};\n const preloadedImages = new Set();\n const batchSize = 5; // 每批处理5张图片\n const batchInterval = 3000; // 每批间隔3秒\n \n // 预加载图片并处理 301 重定向\n function preloadImage(url, retryCount = 0) {\n return new Promise((resolve) => {\n const img = new Image();\n \n img.onload = () => {\n // 检查是否被重定向到默认图片\n // 301重定向后,img.src会是重定向后的URL\n const isDefaultImage = img.src.includes('page_image/default.jpeg');\n \n if (${verbose}) {\n if (isDefaultImage) {\n console.log(\\`🔄 [Image Preloader] 检测到默认图片,开始后台轮询: \\${url}\\`);\n console.log(\\` 重定向到: \\${img.src}\\`);\n } else {\n console.log(\\`✅ [Image Preloader] 成功加载真实图片: \\${url}\\`);\n }\n }\n \n // 无论是否为默认图片,都标记为已加载\n preloadedImages.add(url);\n \n // 如果是默认图片,启动后台轮询任务\n if (isDefaultImage && retryCount < 30) {\n // 先让默认图片显示,然后开始轮询\n resolve();\n startBackgroundPolling(url, img.src, retryCount);\n } else {\n resolve();\n }\n };\n \n img.onerror = () => {\n if (${verbose}) {\n console.warn(\\`❌ [Image Preloader] 图片加载失败: \\${url}\\`);\n }\n resolve();\n };\n \n // 添加时间戳避免缓存\n const timestamp = Date.now();\n const separator = url.includes('?') ? '&' : '?';\n img.src = \\`\\${url}\\${separator}_t=\\${timestamp}\\`;\n });\n }\n \n // 后台轮询函数\n function startBackgroundPolling(originalUrl, defaultImageUrl, retryCount = 0) {\n if (retryCount >= 30) {\n if (${verbose}) {\n console.log(\\`⏰ [Image Preloader] 达到最大重试次数,停止轮询: \\${originalUrl}\\`);\n }\n return;\n }\n \n setTimeout(() => {\n if (${verbose}) {\n console.log(\\`🔄 [Image Preloader] 后台轮询中... (\\${retryCount + 1}/30): \\${originalUrl}\\`);\n }\n \n const img = new Image();\n img.onload = () => {\n const isStillDefaultImage = img.src.includes('page_image/default.jpeg');\n \n if (!isStillDefaultImage) {\n // 获取到真实图片了,更新页面中所有使用该URL的图片\n updatePageImages(originalUrl, img.src);\n if (${verbose}) {\n console.log(\\`🎉 [Image Preloader] 后台轮询成功,已更新页面图片: \\${originalUrl}\\`);\n console.log(\\` 新图片URL: \\${img.src}\\`);\n }\n } else {\n // 还是默认图片,继续轮询\n startBackgroundPolling(originalUrl, defaultImageUrl, retryCount + 1);\n }\n };\n \n img.onerror = () => {\n // 请求失败,继续轮询\n startBackgroundPolling(originalUrl, defaultImageUrl, retryCount + 1);\n };\n \n // 添加时间戳避免缓存\n const timestamp = Date.now();\n const separator = originalUrl.includes('?') ? '&' : '?';\n img.src = \\`\\${originalUrl}\\${separator}_t=\\${timestamp}\\`;\n }, 10000); // 10秒后重试\n }\n \n // 更新页面中所有使用指定URL的图片元素\n function updatePageImages(originalUrl, newImageUrl) {\n if (${verbose}) {\n console.log(\\`🔄 [Image Preloader] 开始更新页面图片: \\${originalUrl} -> \\${newImageUrl}\\`);\n }\n \n let updateCount = 0;\n \n // 更新所有img标签\n const images = document.querySelectorAll('img');\n images.forEach(img => {\n // 检查多种可能的匹配方式\n const imgSrc = img.src;\n const imgSrcWithoutTimestamp = imgSrc.split('_t=')[0].split('?_t=')[0];\n const originalUrlWithoutTimestamp = originalUrl.split('_t=')[0].split('?_t=')[0];\n \n // 检查是否匹配(包括301重定向后的URL)\n const isMatch = \n imgSrcWithoutTimestamp === originalUrlWithoutTimestamp ||\n img.getAttribute('data-original-src') === originalUrl ||\n img.getAttribute('src') === originalUrl ||\n // 检查是否是默认图片需要更新\n (imgSrc.includes('page_image/default.jpeg') && img.hasAttribute('data-api-url') && img.getAttribute('data-api-url') === originalUrl);\n \n if (isMatch) {\n if (${verbose}) {\n console.log(\\`🔄 [Image Preloader] 更新页面图片元素: \\${imgSrc} -> \\${newImageUrl}\\`);\n }\n // 保存原始URL到data属性,用于下次匹配\n img.setAttribute('data-original-src', originalUrl);\n img.setAttribute('data-api-url', originalUrl);\n img.src = newImageUrl;\n updateCount++;\n }\n });\n \n // 更新所有背景图片\n const allElements = document.querySelectorAll('*');\n allElements.forEach(el => {\n const computedStyle = window.getComputedStyle(el);\n const backgroundImage = computedStyle.backgroundImage;\n \n if (backgroundImage && backgroundImage !== 'none') {\n // 提取URL\n const urlMatch = backgroundImage.match(/url\\\\(['\"]?([^'\"\\\\)]+)['\"]?\\\\)/);\n if (urlMatch) {\n const bgUrl = urlMatch[1];\n const bgUrlWithoutTimestamp = bgUrl.split('_t=')[0].split('?_t=')[0];\n const originalUrlWithoutTimestamp = originalUrl.split('_t=')[0].split('?_t=')[0];\n \n const isBgMatch = \n bgUrlWithoutTimestamp === originalUrlWithoutTimestamp ||\n (bgUrl.includes('page_image/default.jpeg') && el.hasAttribute('data-api-bg-url') && el.getAttribute('data-api-bg-url') === originalUrl);\n \n if (isBgMatch) {\n if (${verbose}) {\n console.log(\\`🔄 [Image Preloader] 更新背景图片: \\${bgUrl} -> \\${newImageUrl}\\`);\n }\n el.setAttribute('data-api-bg-url', originalUrl);\n el.style.backgroundImage = \\`url('\\${newImageUrl}')\\`;\n updateCount++;\n }\n }\n }\n });\n \n if (${verbose}) {\n console.log(\\`✅ [Image Preloader] 图片更新完成,共更新了 \\${updateCount} 个元素\\`);\n }\n }\n \n // 分批预加载图片\n function preloadImagesBatch() {\n // 将图片URL分成批次\n const batches = [];\n for (let i = 0; i < imageUrls.length; i += batchSize) {\n batches.push(imageUrls.slice(i, i + batchSize));\n }\n \n if (${verbose}) {\n console.log('%c🖼️ Image Preloader:', 'color: #10b981; font-weight: bold;', \n \\`Starting batch preload: \\${imageUrls.length} images in \\${batches.length} batches\\`);\n }\n \n // 逐批处理\n batches.forEach((batch, batchIndex) => {\n setTimeout(() => {\n if (${verbose}) {\n console.log('%c🖼️ Image Preloader:', 'color: #10b981; font-weight: bold;', \n \\`Processing batch \\${batchIndex + 1}/\\${batches.length} (\\${batch.length} images)\\`);\n }\n \n const promises = batch.map(url => preloadImage(url).catch(e => null));\n \n Promise.allSettled(promises).then(results => {\n const loaded = results.filter(r => r.status === 'fulfilled').length;\n const failed = results.filter(r => r.status === 'rejected').length;\n \n if (${verbose}) {\n console.log('%c🖼️ Image Preloader:', 'color: #10b981; font-weight: bold;', \n \\`Batch \\${batchIndex + 1} completed: \\${loaded} loaded\\` + (failed > 0 ? \\`, \\${failed} failed\\` : ''));\n \n // 如果是最后一批,显示总结\n if (batchIndex === batches.length - 1) {\n const totalLoaded = preloadedImages.size;\n const totalFailed = imageUrls.length - totalLoaded;\n console.log('%c🖼️ Image Preloader:', 'color: #10b981; font-weight: bold;', \n \\`All batches completed: \\${totalLoaded} total loaded\\` + (totalFailed > 0 ? \\`, \\${totalFailed} failed\\` : ''));\n }\n }\n });\n }, batchIndex * batchInterval);\n });\n }\n \n // 兼容旧的API\n function preloadAllImages() {\n preloadImagesBatch();\n }\n \n // 监控页面上的图片,自动处理默认图片\n function monitorPageImages() {\n const observer = new MutationObserver((mutations) => {\n mutations.forEach((mutation) => {\n mutation.addedNodes.forEach((node) => {\n if (node.nodeType === Node.ELEMENT_NODE) {\n const element = node;\n \n // 检查新添加的img元素\n if (element.tagName === 'IMG') {\n checkAndMarkApiImage(element);\n }\n \n // 检查子元素中的img\n const imgs = element.querySelectorAll ? element.querySelectorAll('img') : [];\n imgs.forEach(checkAndMarkApiImage);\n }\n });\n });\n });\n \n // 开始观察\n observer.observe(document.body, {\n childList: true,\n subtree: true\n });\n \n // 检查已存在的图片\n document.querySelectorAll('img').forEach(checkAndMarkApiImage);\n }\n \n function checkAndMarkApiImage(img) {\n const src = img.src;\n if (!src) return;\n \n // 检查是否是我们关心的API URL\n const isApiUrl = imageUrls.some(url => {\n const cleanUrl = url.split('_t=')[0].split('?_t=')[0];\n const cleanSrc = src.split('_t=')[0].split('?_t=')[0];\n return cleanUrl === cleanSrc;\n });\n \n if (isApiUrl) {\n // 找到对应的原始URL\n const originalUrl = imageUrls.find(url => {\n const cleanUrl = url.split('_t=')[0].split('?_t=')[0];\n const cleanSrc = src.split('_t=')[0].split('?_t=')[0];\n return cleanUrl === cleanSrc;\n });\n \n if (originalUrl) {\n img.setAttribute('data-api-url', originalUrl);\n \n if (${verbose}) {\n console.log(\\`🏷️ [Image Preloader] 标记API图片: \\${src} (原始: \\${originalUrl})\\`);\n }\n \n // 如果检测到默认图片,启动轮询\n if (src.includes('page_image/default.jpeg')) {\n if (${verbose}) {\n console.log(\\`🔄 [Image Preloader] 页面检测到默认图片,启动后台轮询: \\${originalUrl}\\`);\n }\n startBackgroundPolling(originalUrl, src, 0);\n }\n }\n }\n }\n \n // 延迟预加载,避免阻塞初始页面渲染\n setTimeout(preloadImagesBatch, ${delay});\n \n // 启动页面图片监控\n setTimeout(monitorPageImages, 100);\n \n // 导出到全局,方便调试\n window.__imagePreloader = {\n preloadAllImages,\n preloadImagesBatch,\n startImagePolling: startBackgroundPolling, // 更新为后台轮询\n getPreloadedImages: () => Array.from(preloadedImages),\n imageUrls,\n batchSize,\n batchInterval,\n pollInterval: 10000,\n maxAttempts: 30,\n updatePageImages // 新增:手动更新图片的方法\n };\n })();\n</script>`\n }\n\n return {\n name: 'vite-plugin-image-preloader',\n \n configResolved(config) {\n isDevMode = config.command === 'serve'\n \n // 自动检测框架\n if (framework === 'auto') {\n try {\n const packageJsonPath = config.root + '/package.json'\n const fs = require('fs')\n if (fs.existsSync(packageJsonPath)) {\n const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'))\n const allDeps = {\n ...packageJson.dependencies,\n ...packageJson.devDependencies\n }\n \n if (allDeps.react) {\n detectedFramework = 'react'\n } else if (allDeps.vue) {\n detectedFramework = 'vue'\n }\n }\n } catch {\n // 忽略错误,使用默认值\n }\n } else {\n detectedFramework = framework\n }\n \n if (verbose && isDevMode) {\n console.log('🖼️ Image Preloader Plugin enabled for', detectedFramework)\n }\n },\n\n transform(code, id) {\n if (devOnly && !isDevMode) return null\n \n // 只处理相关文件类型\n const shouldProcess = \n id.endsWith('.tsx') || \n id.endsWith('.ts') || \n id.endsWith('.jsx') || \n id.endsWith('.js') || \n id.endsWith('.vue') || \n id.endsWith('.css') || \n id.endsWith('.scss') || \n id.endsWith('.sass') || \n id.endsWith('.less')\n \n if (!shouldProcess) return null\n \n const images = extractImagesFromCode(code, id)\n \n if (images.length > 0) {\n images.forEach(img => {\n // 处理相对路径,转换为绝对路径\n let resolvedUrl = img.url\n if (img.url.startsWith('./') || img.url.startsWith('../')) {\n // 对于相对路径,保持原样,让 Vite 处理\n resolvedUrl = img.url\n } else if (!img.url.startsWith('/') && !img.url.startsWith('http')) {\n // 对于不以 / 或 http 开头的路径,添加 /\n resolvedUrl = '/' + img.url\n }\n \n discoveredImages.add(resolvedUrl)\n })\n \n if (verbose) {\n console.log(`🖼️ Found ${images.length} image(s) in ${id.split('/').pop()}`)\n images.forEach(img => {\n console.log(` ${img.type}: ${img.url}`)\n })\n }\n }\n \n return null // 不修改代码\n },\n\n transformIndexHtml: {\n order: 'post',\n handler(html) {\n if (devOnly && !isDevMode) return html\n if (discoveredImages.size === 0) return html\n\n const preloaderScript = generatePreloaderScript()\n \n // 在 head 标签结束前注入预加载脚本\n const headIndex = html.indexOf('</head>')\n if (headIndex !== -1) {\n return html.slice(0, headIndex) + preloaderScript + html.slice(headIndex)\n }\n \n return html\n }\n }\n }\n}\n\n// 同时支持命名导出和默认导出\nexport { imagePreloader }\nexport default imagePreloader "],"mappings":"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","names":[]}