|
|
@@ -1,209 +1,270 @@
|
|
|
-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;
|
|
|
- z-index: 9999;
|
|
|
- }
|
|
|
- `,
|
|
|
- injectTo: 'head'
|
|
|
- },
|
|
|
- {
|
|
|
- tag: 'div',
|
|
|
- attrs: { id: 'vite-progress-bar' },
|
|
|
- injectTo: 'body-prepend'
|
|
|
- },
|
|
|
- {
|
|
|
- tag: 'script',
|
|
|
- attrs: { type: 'module' },
|
|
|
- children: `
|
|
|
- if (import.meta.hot) {
|
|
|
- // 进度跟踪状态
|
|
|
- let serverProgress = 0;
|
|
|
- let clientProgress = { loaded: 0, total: 0 };
|
|
|
- let dynamicImports = { loaded: 0, total: 0 };
|
|
|
- let preloadProgress = { loaded: 0, total: 0 };
|
|
|
+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;
|
|
|
+
|
|
|
+ // 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;
|
|
|
|
|
|
- // 更新进度条的统一函数
|
|
|
- function updateProgressBar() {
|
|
|
- const bar = document.getElementById('vite-progress-bar');
|
|
|
- if (!bar) return;
|
|
|
-
|
|
|
- // 计算各部分进度 (服务端40%, 客户端资源30%, 动态导入15%, 预加载15%)
|
|
|
- const server = serverProgress * 0.4;
|
|
|
- const client = clientProgress.total > 0 ?
|
|
|
- (clientProgress.loaded / clientProgress.total) * 0.3 : 0;
|
|
|
- const dynamic = dynamicImports.total > 0 ?
|
|
|
- (dynamicImports.loaded / dynamicImports.total) * 0.15 : 0;
|
|
|
- const preload = preloadProgress.total > 0 ?
|
|
|
- (preloadProgress.loaded / preloadProgress.total) * 0.15 : 0;
|
|
|
-
|
|
|
- console.log(clientProgress.loaded , clientProgress.total)
|
|
|
- console.log(server,client,dynamic,preload)
|
|
|
-
|
|
|
- const totalProgress = (server + client + dynamic + preload) * 100;
|
|
|
- bar.style.width = Math.min(totalProgress, 100) + '%';
|
|
|
-
|
|
|
- if (totalProgress >= 100) {
|
|
|
- setTimeout(() => bar.style.display = 'none', 1000);
|
|
|
- }
|
|
|
- }
|
|
|
+ // 基于现有资源数量和经验值估算
|
|
|
+ const baseEstimate = scripts + links + styleSheets + preloadLinks;
|
|
|
+ const dynamicEstimate = Math.max(baseEstimate * 1.5, 10); // 至少10个资源
|
|
|
|
|
|
- // 1. 监控服务端编译进度
|
|
|
- import.meta.hot.on('progress:update', (data) => {
|
|
|
- serverProgress = data.total > 0 ? data.processed / data.total : 0;
|
|
|
- updateProgressBar();
|
|
|
- });
|
|
|
+ return Math.floor(dynamicEstimate);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新进度条和状态显示
|
|
|
+ function updateProgress() {
|
|
|
+ if (!progressBar || !statusDisplay) return;
|
|
|
+
|
|
|
+ // 服务端进度权重70%,客户端资源进度权重30%
|
|
|
+ const serverWeight = 0.7;
|
|
|
+ const clientWeight = 0.3;
|
|
|
|
|
|
- // 2. 监控动态导入
|
|
|
- const originalImport = window.import;
|
|
|
- if (originalImport) {
|
|
|
- window.import = async function(specifier) {
|
|
|
- dynamicImports.total++;
|
|
|
- updateProgressBar();
|
|
|
-
|
|
|
- try {
|
|
|
- const result = await originalImport.call(this, specifier);
|
|
|
- dynamicImports.loaded++;
|
|
|
- updateProgressBar();
|
|
|
- return result;
|
|
|
- } catch (error) {
|
|
|
- dynamicImports.loaded++;
|
|
|
- updateProgressBar();
|
|
|
- throw error;
|
|
|
- }
|
|
|
- };
|
|
|
- }
|
|
|
+ const serverPart = serverProgress * serverWeight;
|
|
|
+ const clientPart = estimatedTotal > 0 ?
|
|
|
+ (resourcesCompleted / estimatedTotal) * clientWeight : 0;
|
|
|
+
|
|
|
+ const totalProgress = Math.min((serverPart + clientPart) * 100, 100);
|
|
|
|
|
|
- // 3. 监控预加载资源
|
|
|
- function setupPreloadMonitoring() {
|
|
|
- const preloadLinks = document.querySelectorAll('link[rel="preload"], link[rel="modulepreload"]');
|
|
|
- preloadProgress.total = preloadLinks.length;
|
|
|
+ // 更新进度条
|
|
|
+ progressBar.style.width = totalProgress + '%';
|
|
|
+
|
|
|
+ // 更新状态显示
|
|
|
+ if (isLoading && totalProgress < 100) {
|
|
|
+ statusDisplay.style.display = 'block';
|
|
|
+ statusDisplay.textContent =
|
|
|
+ \`Loading... \${Math.round(totalProgress)}% (\${resourcesCompleted}/\${estimatedTotal} resources)\`;
|
|
|
+ } else if (totalProgress >= 100) {
|
|
|
+ isLoading = false;
|
|
|
+ statusDisplay.style.display = 'none';
|
|
|
+ progressBar.classList.add('complete');
|
|
|
|
|
|
- preloadLinks.forEach(link => {
|
|
|
- // 检查是否已经加载完成
|
|
|
- if (link.sheet || link.complete) {
|
|
|
- preloadProgress.loaded++;
|
|
|
- } else {
|
|
|
- link.addEventListener('load', () => {
|
|
|
- preloadProgress.loaded++;
|
|
|
- updateProgressBar();
|
|
|
- });
|
|
|
- link.addEventListener('error', () => {
|
|
|
- preloadProgress.loaded++;
|
|
|
- updateProgressBar();
|
|
|
- });
|
|
|
- }
|
|
|
- });
|
|
|
- updateProgressBar();
|
|
|
+ // 1秒后隐藏进度条
|
|
|
+ setTimeout(() => {
|
|
|
+ progressBar.style.display = 'none';
|
|
|
+ }, 1000);
|
|
|
}
|
|
|
|
|
|
- // 4. 监控客户端资源加载 (通过Performance API)
|
|
|
- if (window.PerformanceObserver) {
|
|
|
- const loadedResources = new Set(); // 避免重复计数
|
|
|
+ console.log(\`Progress: Server \${Math.round(serverProgress * 100)}%, Resources \${resourcesCompleted}/\${estimatedTotal}, Total \${Math.round(totalProgress)}%\`);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 监听服务端编译进度
|
|
|
+ import.meta.hot.on('progress:update', (data) => {
|
|
|
+ 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' && entry.initiatorType === 'script' ) {
|
|
|
- if (!loadedResources.has(entry.name)) {
|
|
|
- console.log('entry.name', entry.name)
|
|
|
- loadedResources.add(entry.name);
|
|
|
- clientProgress.loaded++;
|
|
|
+ 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++;
|
|
|
|
|
|
- // 动态调整总数 - 当发现新资源时增加总数
|
|
|
- if (clientProgress.loaded > clientProgress.total) {
|
|
|
- clientProgress.total = clientProgress.loaded;
|
|
|
- }
|
|
|
+ // 动态调整预估总数
|
|
|
+ if (resourcesCompleted > estimatedTotal * 0.8) {
|
|
|
+ estimatedTotal = Math.max(estimatedTotal, resourcesCompleted + 5);
|
|
|
+ }
|
|
|
|
|
|
- updateProgressBar();
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- });
|
|
|
- observer.observe({ entryTypes: ['resource'] });
|
|
|
- }
|
|
|
+ console.log(\`Resource completed: \${entry.name} (type: \${entry.initiatorType})\`);
|
|
|
+ updateProgress();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
|
|
|
- // 初始化时统计需要加载的资源
|
|
|
- document.addEventListener('DOMContentLoaded', () => {
|
|
|
- const scripts = document.querySelectorAll('script[src]');
|
|
|
- const links = document.querySelectorAll('link[href][rel="stylesheet"]');
|
|
|
- clientProgress.total = scripts.length + links.length;
|
|
|
+ observer.observe({ entryTypes: ['resource'] });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 监控动态导入
|
|
|
+ const originalImport = window.import;
|
|
|
+ if (originalImport) {
|
|
|
+ window.import = async function(specifier) {
|
|
|
+ console.log(\`Dynamic import started: \${specifier}\`);
|
|
|
|
|
|
- setupPreloadMonitoring();
|
|
|
- updateProgressBar();
|
|
|
- });
|
|
|
+ try {
|
|
|
+ const result = await originalImport.call(this, specifier);
|
|
|
+ console.log(\`Dynamic import completed: \${specifier}\`);
|
|
|
+ return result;
|
|
|
+ } catch (error) {
|
|
|
+ console.log(\`Dynamic import failed: \${specifier}\`);
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ // 初始化
|
|
|
+ function initialize() {
|
|
|
+ estimatedTotal = estimateResourceCount();
|
|
|
+ console.log(\`Estimated total resources: \${estimatedTotal}\`);
|
|
|
+
|
|
|
+ // 显示初始状态
|
|
|
+ if (statusDisplay) {
|
|
|
+ statusDisplay.style.display = 'block';
|
|
|
+ statusDisplay.textContent = \`Initializing... (0/\${estimatedTotal} resources)\`;
|
|
|
+ }
|
|
|
|
|
|
- // 如果DOM已经加载完成
|
|
|
- if (document.readyState === 'loading') {
|
|
|
- // DOM还在加载中,等待DOMContentLoaded
|
|
|
+ updateProgress();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 页面加载完成后的处理
|
|
|
+ function handlePageLoad() {
|
|
|
+ // 给一些时间让所有资源完成加载
|
|
|
+ setTimeout(() => {
|
|
|
+ if (resourcesCompleted < estimatedTotal * 0.5) {
|
|
|
+ // 如果完成的资源太少,调整预估值
|
|
|
+ estimatedTotal = Math.max(resourcesCompleted + 2, 5);
|
|
|
+ console.log(\`Adjusted estimated total to: \${estimatedTotal}\`);
|
|
|
+ }
|
|
|
+ updateProgress();
|
|
|
+ }, 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 {
|
|
|
- // DOM已经加载完成,立即执行
|
|
|
- setTimeout(() => {
|
|
|
- const scripts = document.querySelectorAll('script[src]');
|
|
|
- const links = document.querySelectorAll('link[href][rel="stylesheet"]');
|
|
|
- clientProgress.total = scripts.length + links.length;
|
|
|
-
|
|
|
- setupPreloadMonitoring();
|
|
|
- updateProgressBar();
|
|
|
- }, 0);
|
|
|
+ document.addEventListener('load', handlePageLoad);
|
|
|
}
|
|
|
- }
|
|
|
- `,
|
|
|
- injectTo: 'body'
|
|
|
- }
|
|
|
- ]
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 错误处理
|
|
|
+ window.addEventListener('error', (event) => {
|
|
|
+ console.log(\`Resource load error: \${event.filename || 'unknown'}\`);
|
|
|
+ });
|
|
|
+
|
|
|
+ // HMR 更新时重置状态
|
|
|
+ import.meta.hot.on('vite:beforeUpdate', () => {
|
|
|
+ console.log('HMR update detected, maintaining progress tracking...');
|
|
|
+ });
|
|
|
+ }
|
|
|
+ `,
|
|
|
+ injectTo: 'body'
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|