|
|
@@ -1,93 +1,196 @@
|
|
|
-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', // 在 Vite 内部转换之前执行
|
|
|
- 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' }, // 关键:添加 type="module"
|
|
|
- children: `
|
|
|
- if (import.meta.hot) {
|
|
|
- import.meta.hot.on('progress:update', (data) => {
|
|
|
+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 };
|
|
|
+
|
|
|
+ // 更新进度条的统一函数
|
|
|
+ function updateProgressBar() {
|
|
|
const bar = document.getElementById('vite-progress-bar');
|
|
|
- if (bar) {
|
|
|
- const percentage = (data.processed / data.total) * 100;
|
|
|
- bar.style.width = percentage + '%';
|
|
|
- if (percentage >= 100) {
|
|
|
- setTimeout(() => bar.style.display = 'none', 1000);
|
|
|
- }
|
|
|
+ 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;
|
|
|
+
|
|
|
+ const totalProgress = (server + client + dynamic + preload) * 100;
|
|
|
+ bar.style.width = Math.min(totalProgress, 100) + '%';
|
|
|
+
|
|
|
+ if (totalProgress >= 100) {
|
|
|
+ setTimeout(() => bar.style.display = 'none', 1000);
|
|
|
}
|
|
|
+ }
|
|
|
+
|
|
|
+ // 1. 监控服务端编译进度
|
|
|
+ import.meta.hot.on('progress:update', (data) => {
|
|
|
+ serverProgress = data.total > 0 ? data.processed / data.total : 0;
|
|
|
+ updateProgressBar();
|
|
|
+ });
|
|
|
+
|
|
|
+ // 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;
|
|
|
+ }
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 监控预加载资源
|
|
|
+ function setupPreloadMonitoring() {
|
|
|
+ const preloadLinks = document.querySelectorAll('link[rel="preload"], link[rel="modulepreload"]');
|
|
|
+ preloadProgress.total = preloadLinks.length;
|
|
|
+
|
|
|
+ preloadLinks.forEach(link => {
|
|
|
+ // 检查是否已经加载完成
|
|
|
+ if (link.sheet || link.complete) {
|
|
|
+ preloadProgress.loaded++;
|
|
|
+ } else {
|
|
|
+ link.addEventListener('load', () => {
|
|
|
+ preloadProgress.loaded++;
|
|
|
+ updateProgressBar();
|
|
|
+ });
|
|
|
+ link.addEventListener('error', () => {
|
|
|
+ preloadProgress.loaded++;
|
|
|
+ updateProgressBar();
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ updateProgressBar();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 监控客户端资源加载 (通过Performance API)
|
|
|
+ if (window.PerformanceObserver) {
|
|
|
+ const observer = new PerformanceObserver((list) => {
|
|
|
+ for (const entry of list.getEntries()) {
|
|
|
+ if (entry.entryType === 'resource' &&
|
|
|
+ (entry.name.includes('.js') || entry.name.includes('.css') ||
|
|
|
+ entry.name.includes('.ts') || entry.name.includes('.vue'))) {
|
|
|
+ clientProgress.loaded++;
|
|
|
+ updateProgressBar();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ observer.observe({ entryTypes: ['resource'] });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 初始化时统计需要加载的资源
|
|
|
+ document.addEventListener('DOMContentLoaded', () => {
|
|
|
+ const scripts = document.querySelectorAll('script[src]');
|
|
|
+ const links = document.querySelectorAll('link[href][rel="stylesheet"]');
|
|
|
+ clientProgress.total = scripts.length + links.length;
|
|
|
+
|
|
|
+ setupPreloadMonitoring();
|
|
|
+ updateProgressBar();
|
|
|
});
|
|
|
- }
|
|
|
- `,
|
|
|
- injectTo: 'body'
|
|
|
- }
|
|
|
- ]
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+
|
|
|
+ // 如果DOM已经加载完成
|
|
|
+ if (document.readyState === 'loading') {
|
|
|
+ // DOM还在加载中,等待DOMContentLoaded
|
|
|
+ } 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);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ `,
|
|
|
+ injectTo: 'body'
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|