| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355 |
- export function progressTrackingPlugin() {
- let server
- const moduleStats = {
- total: 0,
- processed: 0,
- pending: new Set()
- }
-
- return {
- name: 'progress-tracking',
- apply: 'serve',
- enforce: 'pre',
- configureServer(_server) {
- server = _server
- },
- async transform(code, id) {
- if (server && id) {
- moduleStats.total++
- moduleStats.pending.add(id)
-
- server.ws.send('progress:update', {
- total: moduleStats.total,
- processed: moduleStats.processed,
- current: id
- })
-
- await server.waitForRequestsIdle(id)
-
- moduleStats.processed++
- moduleStats.pending.delete(id)
-
- server.ws.send('progress:update', {
- total: moduleStats.total,
- processed: moduleStats.processed,
- current: null
- })
- }
- },
- transformIndexHtml: {
- order: 'pre',
- handler(html, ctx) {
- return {
- html,
- tags: [
- {
- tag: 'style',
- children: `
- #vite-progress-bar {
- position: fixed;
- top: 0;
- left: 0;
- width: 0%;
- height: 3px;
- background: #646cff;
- transition: width 0.3s ease;
- z-index: 9999;
- opacity: 1;
- }
- #vite-progress-bar.complete {
- opacity: 0;
- transition: opacity 0.5s ease;
- }
- #vite-progress-status {
- position: fixed;
- top: 10px;
- right: 10px;
- background: rgba(0, 0, 0, 0.8);
- color: white;
- padding: 8px 12px;
- border-radius: 4px;
- font-size: 12px;
- font-family: monospace;
- z-index: 10000;
- display: none;
- }
- `,
- injectTo: 'head'
- },
- {
- tag: 'div',
- attrs: { id: 'vite-progress-bar' },
- injectTo: 'body-prepend'
- },
- {
- tag: 'div',
- attrs: { id: 'vite-progress-status' },
- children: 'Loading...',
- injectTo: 'body-prepend'
- },
- {
- tag: 'script',
- attrs: { type: 'module' },
- children: `
- if (import.meta.hot) {
- // 进度追踪状态
- let serverProgress = 0;
- let resourcesCompleted = 0;
- let estimatedTotal = 0;
- let isLoading = true;
- let completionTimer = null;
- let lastResourceTime = Date.now();
- let hasServerActivity = false;
- let serverActivityTimer = null;
-
- // DOM 元素引用
- const progressBar = document.getElementById('vite-progress-bar');
- const statusDisplay = document.getElementById('vite-progress-status');
-
- // 改进的预估算法
- function estimateResourceCount() {
- const scripts = document.querySelectorAll('script[src]').length;
- const links = document.querySelectorAll('link[href]').length;
- const styleSheets = document.querySelectorAll('link[rel="stylesheet"]').length;
- const preloadLinks = document.querySelectorAll('link[rel="preload"], link[rel="modulepreload"]').length;
-
- const baseEstimate = scripts + links + styleSheets + preloadLinks;
- const conservativeEstimate = Math.max(baseEstimate + 10, 15);
-
- return conservativeEstimate;
- }
-
- // 检测服务端活动
- function detectServerActivity() {
- hasServerActivity = true;
- if (serverActivityTimer) clearTimeout(serverActivityTimer);
-
- // 如果3秒内没有新的服务端活动,认为服务端编译已完成或无需编译
- serverActivityTimer = setTimeout(() => {
- if (serverProgress === 0) {
- // console.log('No server compilation detected - using client-only mode');
- hasServerActivity = false;
- }
- }, 3000);
- }
-
- // 检测是否完成加载
- function checkCompletion() {
- const now = Date.now();
- const timeSinceLastResource = now - lastResourceTime;
-
- // 添加详细的调试日志
- // console.log('=== checkCompletion Debug ===');
- // console.log('hasServerActivity:', hasServerActivity);
- // console.log('serverProgress:', serverProgress);
- // console.log('resourcesCompleted:', resourcesCompleted);
- // console.log('estimatedTotal:', estimatedTotal);
- // console.log('timeSinceLastResource:', timeSinceLastResource);
- // console.log('isLoading:', isLoading);
-
- // 如果已经不在加载状态,直接返回
- if (!isLoading) {
- // console.log('⚠️ Not loading anymore, exiting checkCompletion');
- return;
- }
-
- // 根据服务端活动情况调整完成条件
- if (!hasServerActivity) {
- // 无服务端编译时,主要依赖客户端资源
- if (resourcesCompleted >= estimatedTotal * 0.8 && timeSinceLastResource > 2000) {
- // console.log('Client-only loading complete');
- forceComplete();
- return;
- }
- } else {
- // 有服务端编译时,需要服务端完成
- if (serverProgress >= 1.0 && timeSinceLastResource > 2000) {
- // console.log('Server + client loading complete');
- forceComplete();
- return;
- }
- }
-
- // 超时强制完成
- if (timeSinceLastResource > 1000 * 10) {
- // console.log('Force completing due to timeout');
- forceComplete();
- }
- // 如果仍在加载中,且未触发超时强制完成,则500ms后再次检查
- if (isLoading && timeSinceLastResource <= 1000 * 10) {
- setTimeout(checkCompletion, 1000 * 5); // 循环检查
- }
- }
-
- // 强制完成
- function forceComplete() {
- if (!isLoading) return;
-
- isLoading = false;
- progressBar.style.width = '100%';
- statusDisplay.style.display = 'none';
- progressBar.classList.add('complete');
-
- // console.log('Loading completed!');
-
- setTimeout(() => {
- progressBar.style.display = 'none';
- }, 1000);
- }
-
- // 更新进度条和状态显示
- function updateProgress() {
- if (!progressBar || !statusDisplay || !isLoading) return;
-
- let totalProgress;
-
- if (!hasServerActivity && serverProgress === 0) {
- // 无服务端编译活动,100%依赖客户端资源
- const clientProgress = estimatedTotal > 0 ?
- Math.min(resourcesCompleted / estimatedTotal, 1.0) : 0;
- totalProgress = clientProgress * 100;
-
- // console.log(\`Client-only mode: \${resourcesCompleted}/\${estimatedTotal} resources\`);
- } else {
- // 有服务端编译活动,使用混合权重
- const serverWeight = serverProgress < 1.0 ? 0.6 : 0.3;
- const clientWeight = serverProgress < 1.0 ? 0.4 : 0.7;
-
- const serverPart = serverProgress * serverWeight;
- const clientPart = estimatedTotal > 0 ?
- Math.min(resourcesCompleted / estimatedTotal, 1.0) * clientWeight : 0;
-
- totalProgress = (serverPart + clientPart) * 100;
-
- // console.log(\`Mixed mode: Server \${Math.round(serverProgress * 100)}%, Client \${resourcesCompleted}/\${estimatedTotal}\`);
- }
-
- // 确保进度不会倒退
- const currentWidth = parseFloat(progressBar.style.width) || 0;
- totalProgress = Math.max(totalProgress, currentWidth);
-
- // 更新进度条
- progressBar.style.width = Math.min(totalProgress, 100) + '%';
-
- // 更新状态显示
- statusDisplay.style.display = 'block';
- const mode = hasServerActivity ? 'Mixed' : 'Client-only';
- statusDisplay.textContent =
- \`Loading... \${Math.round(totalProgress)}% (\${mode}: \${resourcesCompleted}/\${estimatedTotal})\`;
-
- // console.log(\`Progress: Total \${Math.round(totalProgress)}%\`);
-
- // 如果接近完成,开始检测完成状态
- if (totalProgress >= 90) {
- if (completionTimer) clearTimeout(completionTimer);
- completionTimer = setTimeout(checkCompletion, 1000);
- }
- }
-
- // 监听服务端编译进度
- import.meta.hot.on('progress:update', (data) => {
- detectServerActivity();
- serverProgress = data.total > 0 ? data.processed / data.total : 0;
- updateProgress();
- });
-
- // 使用 PerformanceObserver 监控资源加载完成
- if (window.PerformanceObserver) {
- const completedResources = new Set();
-
- const observer = new PerformanceObserver((list) => {
- for (const entry of list.getEntries()) {
- if (entry.entryType === 'resource') {
- const isRelevantResource =
- entry.initiatorType === 'script' ||
- entry.initiatorType === 'link' ||
- entry.initiatorType === 'css' ||
- entry.name.includes('.js') ||
- entry.name.includes('.css') ||
- entry.name.includes('.ts');
-
- if (isRelevantResource && !completedResources.has(entry.name)) {
- completedResources.add(entry.name);
- resourcesCompleted++;
- lastResourceTime = Date.now();
-
- // 动态调整预估总数
- if (resourcesCompleted > estimatedTotal * 0.9) {
- estimatedTotal = Math.max(estimatedTotal, resourcesCompleted + 3);
- }
-
- // console.log(\`Resource completed: \${entry.name} (type: \${entry.initiatorType})\`);
- updateProgress();
- }
- }
- }
- });
-
- observer.observe({ entryTypes: ['resource'] });
- }
-
- // 初始化
- function initialize() {
- estimatedTotal = estimateResourceCount();
- lastResourceTime = Date.now();
- // console.log(\`Estimated total resources: \${estimatedTotal}\`);
-
- // 显示初始状态
- if (statusDisplay) {
- statusDisplay.style.display = 'block';
- statusDisplay.textContent = \`Initializing... (0/\${estimatedTotal} resources)\`;
- }
-
- updateProgress();
-
- // 启动服务端活动检测
- setTimeout(() => {
- if (!hasServerActivity) {
- // console.log('No server activity detected, switching to client-only mode');
- updateProgress();
- }
- }, 1000);
- }
-
- // 页面加载完成后的处理
- function handlePageLoad() {
- setTimeout(() => {
- if (resourcesCompleted > 0) {
- estimatedTotal = Math.max(resourcesCompleted + 2, estimatedTotal);
- // console.log(\`Final estimated total: \${estimatedTotal}\`);
- }
- updateProgress();
-
- // 开始完成检测
- setTimeout(checkCompletion, 2000);
- }, 1000);
- }
-
- // 根据文档状态初始化
- if (document.readyState === 'loading') {
- document.addEventListener('DOMContentLoaded', initialize);
- document.addEventListener('load', handlePageLoad);
- } else {
- setTimeout(initialize, 0);
- if (document.readyState === 'complete') {
- setTimeout(handlePageLoad, 100);
- } else {
- document.addEventListener('load', handlePageLoad);
- }
- }
-
- // HMR 更新时重置状态
- import.meta.hot.on('vite:beforeUpdate', () => {
- // console.log('HMR update detected, maintaining progress tracking...');
- });
- }
- `,
- injectTo: 'body'
- }
- ]
- }
- }
- }
- }
- }
|