vite-plugin-compile-progress.js 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. export function progressTrackingPlugin() {
  2. let server
  3. const moduleStats = {
  4. total: 0,
  5. processed: 0,
  6. pending: new Set()
  7. }
  8. return {
  9. name: 'progress-tracking',
  10. apply: 'serve',
  11. enforce: 'pre',
  12. configureServer(_server) {
  13. server = _server
  14. },
  15. async transform(code, id) {
  16. if (server && id) {
  17. moduleStats.total++
  18. moduleStats.pending.add(id)
  19. server.ws.send('progress:update', {
  20. total: moduleStats.total,
  21. processed: moduleStats.processed,
  22. current: id
  23. })
  24. await server.waitForRequestsIdle(id)
  25. moduleStats.processed++
  26. moduleStats.pending.delete(id)
  27. server.ws.send('progress:update', {
  28. total: moduleStats.total,
  29. processed: moduleStats.processed,
  30. current: null
  31. })
  32. }
  33. },
  34. transformIndexHtml: {
  35. order: 'pre',
  36. handler(html, ctx) {
  37. return {
  38. html,
  39. tags: [
  40. {
  41. tag: 'style',
  42. children: `
  43. #vite-progress-bar {
  44. position: fixed;
  45. top: 0;
  46. left: 0;
  47. width: 0%;
  48. height: 3px;
  49. background: #646cff;
  50. transition: width 0.3s;
  51. z-index: 9999;
  52. }
  53. `,
  54. injectTo: 'head'
  55. },
  56. {
  57. tag: 'div',
  58. attrs: { id: 'vite-progress-bar' },
  59. injectTo: 'body-prepend'
  60. },
  61. {
  62. tag: 'script',
  63. attrs: { type: 'module' },
  64. children: `
  65. if (import.meta.hot) {
  66. // 进度跟踪状态
  67. let serverProgress = 0;
  68. let clientProgress = { loaded: 0, total: 0 };
  69. let dynamicImports = { loaded: 0, total: 0 };
  70. let preloadProgress = { loaded: 0, total: 0 };
  71. // 更新进度条的统一函数
  72. function updateProgressBar() {
  73. const bar = document.getElementById('vite-progress-bar');
  74. if (!bar) return;
  75. // 计算各部分进度 (服务端40%, 客户端资源30%, 动态导入15%, 预加载15%)
  76. const server = serverProgress * 0.4;
  77. const client = clientProgress.total > 0 ?
  78. (clientProgress.loaded / clientProgress.total) * 0.3 : 0;
  79. const dynamic = dynamicImports.total > 0 ?
  80. (dynamicImports.loaded / dynamicImports.total) * 0.15 : 0;
  81. const preload = preloadProgress.total > 0 ?
  82. (preloadProgress.loaded / preloadProgress.total) * 0.15 : 0;
  83. const totalProgress = (server + client + dynamic + preload) * 100;
  84. bar.style.width = Math.min(totalProgress, 100) + '%';
  85. if (totalProgress >= 100) {
  86. setTimeout(() => bar.style.display = 'none', 1000);
  87. }
  88. }
  89. // 1. 监控服务端编译进度
  90. import.meta.hot.on('progress:update', (data) => {
  91. serverProgress = data.total > 0 ? data.processed / data.total : 0;
  92. updateProgressBar();
  93. });
  94. // 2. 监控动态导入
  95. const originalImport = window.import;
  96. if (originalImport) {
  97. window.import = async function(specifier) {
  98. dynamicImports.total++;
  99. updateProgressBar();
  100. try {
  101. const result = await originalImport.call(this, specifier);
  102. dynamicImports.loaded++;
  103. updateProgressBar();
  104. return result;
  105. } catch (error) {
  106. dynamicImports.loaded++;
  107. updateProgressBar();
  108. throw error;
  109. }
  110. };
  111. }
  112. // 3. 监控预加载资源
  113. function setupPreloadMonitoring() {
  114. const preloadLinks = document.querySelectorAll('link[rel="preload"], link[rel="modulepreload"]');
  115. preloadProgress.total = preloadLinks.length;
  116. preloadLinks.forEach(link => {
  117. // 检查是否已经加载完成
  118. if (link.sheet || link.complete) {
  119. preloadProgress.loaded++;
  120. } else {
  121. link.addEventListener('load', () => {
  122. preloadProgress.loaded++;
  123. updateProgressBar();
  124. });
  125. link.addEventListener('error', () => {
  126. preloadProgress.loaded++;
  127. updateProgressBar();
  128. });
  129. }
  130. });
  131. updateProgressBar();
  132. }
  133. // 4. 监控客户端资源加载 (通过Performance API)
  134. if (window.PerformanceObserver) {
  135. const observer = new PerformanceObserver((list) => {
  136. for (const entry of list.getEntries()) {
  137. if (entry.entryType === 'resource' &&
  138. (entry.name.includes('.js') || entry.name.includes('.css') ||
  139. entry.name.includes('.ts') || entry.name.includes('.vue'))) {
  140. clientProgress.loaded++;
  141. updateProgressBar();
  142. }
  143. }
  144. });
  145. observer.observe({ entryTypes: ['resource'] });
  146. }
  147. // 初始化时统计需要加载的资源
  148. document.addEventListener('DOMContentLoaded', () => {
  149. const scripts = document.querySelectorAll('script[src]');
  150. const links = document.querySelectorAll('link[href][rel="stylesheet"]');
  151. clientProgress.total = scripts.length + links.length;
  152. setupPreloadMonitoring();
  153. updateProgressBar();
  154. });
  155. // 如果DOM已经加载完成
  156. if (document.readyState === 'loading') {
  157. // DOM还在加载中,等待DOMContentLoaded
  158. } else {
  159. // DOM已经加载完成,立即执行
  160. setTimeout(() => {
  161. const scripts = document.querySelectorAll('script[src]');
  162. const links = document.querySelectorAll('link[href][rel="stylesheet"]');
  163. clientProgress.total = scripts.length + links.length;
  164. setupPreloadMonitoring();
  165. updateProgressBar();
  166. }, 0);
  167. }
  168. }
  169. `,
  170. injectTo: 'body'
  171. }
  172. ]
  173. }
  174. }
  175. }
  176. }
  177. }