mirror of
https://github.com/lobehub/lobehub.git
synced 2026-09-20 04:56:13 +08:00
2933b9fcc5
* ⚡️ perf(desktop): show the main window on the loading screen's first paint `ready-to-show` only fires with the `load` event for the desktop entry, which lands ~250ms after the static loading screen has actually painted. The preload now reports that first paint (double rAF after `#loading-screen` enters the DOM) and the main process shows the window on whichever signal arrives first. Measured on the packaged app (warm start, window creation = 0): window visible at +140ms instead of +370ms, with the loading screen already painted. Claude-Session: https://claude.ai/code/session_012z4cpfrWDXZDtNgT4uJKoS * ⚡️ perf(desktop): enable the V8 compile cache for the main process bundle Injected as an entry banner because rolldown hoists chunk requires above any statement in the entry source, so `main-app` would otherwise load uncached. Main bundle load 55ms → 37ms warm; exec → main.js ~10ms faster on a packaged build. Claude-Session: https://claude.ai/code/session_012z4cpfrWDXZDtNgT4uJKoS * ✨ feat(desktop): show Vite transform progress on the dev loading screens A cold `bun run dev` sits on the loading logo for 10s+ while Vite transforms thousands of modules. A serve-only plugin streams the transform count and the current file over the HMR socket, and an injected client renders it at the bottom of the window for as long as any boot placeholder (`#loading-screen`, `#boot-shell`, `#app-shell-fallback`) is on screen. Claude-Session: https://claude.ai/code/session_012z4cpfrWDXZDtNgT4uJKoS
83 lines
3.1 KiB
TypeScript
83 lines
3.1 KiB
TypeScript
import type { Plugin, ViteDevServer } from 'vite';
|
|
|
|
const EVENT = 'lobe:dev-loading-progress';
|
|
const BROADCAST_INTERVAL_MS = 80;
|
|
|
|
const resolveHmrUrl = (server: ViteDevServer) => {
|
|
const { hmr, host, port } = server.config.server;
|
|
if (hmr === false) return;
|
|
const options = typeof hmr === 'object' ? hmr : {};
|
|
const wsHost = options.host ?? (typeof host === 'string' ? host : 'localhost');
|
|
const wsPort = options.clientPort ?? options.port ?? port;
|
|
return `${options.protocol ?? 'ws'}://${wsHost}:${wsPort}`;
|
|
};
|
|
|
|
// Lives as long as any boot placeholder is on screen: the static `#loading-screen`,
|
|
// then the React boot shell / route fallback skeletons that follow it in dev.
|
|
const PLACEHOLDER_IDS = ['loading-screen', 'boot-shell', 'app-shell-fallback'];
|
|
|
|
const clientScript = (hmrUrl: string) => `(function () {
|
|
var ids = ${JSON.stringify(PLACEHOLDER_IDS)};
|
|
var el = document.createElement('div');
|
|
el.id = 'loading-dev-progress';
|
|
el.style.cssText = 'position: fixed; inset-block-end: 12px; inset-inline: 0; z-index: 100000; pointer-events: none; text-align: center; font: 12px ui-monospace, monospace; opacity: 0.55; white-space: nowrap;';
|
|
var count = 0, file = '', start = performance.now();
|
|
function placeholderVisible() {
|
|
for (var i = 0; i < ids.length; i++) if (document.getElementById(ids[i])) return true;
|
|
return false;
|
|
}
|
|
function render() {
|
|
var visible = placeholderVisible();
|
|
if (visible && !el.isConnected) document.body.appendChild(el);
|
|
if (!visible && el.isConnected) el.remove();
|
|
if (visible) el.textContent = 'vite dev · ' + count + ' modules · ' + ((performance.now() - start) / 1000).toFixed(1) + 's · ' + file;
|
|
requestAnimationFrame(render);
|
|
}
|
|
render();
|
|
try {
|
|
var ws = new WebSocket(${JSON.stringify(hmrUrl)}, 'vite-hmr');
|
|
ws.onmessage = function (e) {
|
|
var m = JSON.parse(e.data);
|
|
if (m.type === 'custom' && m.event === ${JSON.stringify(EVENT)}) { count = m.data.count; file = m.data.file; }
|
|
};
|
|
} catch (_) {}
|
|
})();`;
|
|
|
|
/**
|
|
* Dev-only: streams Vite's transform progress onto the static loading screen so a
|
|
* cold `dev` start shows "N modules · Xs · current file" instead of a frozen logo.
|
|
*/
|
|
export const devLoadingProgress = (): Plugin => {
|
|
let server: ViteDevServer | undefined;
|
|
let count = 0;
|
|
let latest = '';
|
|
let timer: NodeJS.Timeout | undefined;
|
|
|
|
const scheduleBroadcast = () => {
|
|
if (timer || !server) return;
|
|
timer = setTimeout(() => {
|
|
timer = undefined;
|
|
server?.hot.send({ data: { count, file: latest }, event: EVENT, type: 'custom' });
|
|
}, BROADCAST_INTERVAL_MS);
|
|
};
|
|
|
|
return {
|
|
apply: 'serve',
|
|
configureServer(devServer) {
|
|
server = devServer;
|
|
},
|
|
name: 'dev-loading-progress',
|
|
transform(_code, id) {
|
|
if (!server) return;
|
|
count += 1;
|
|
latest = id.split('?')[0].replace(server.config.root, '').replace(/^\//, '');
|
|
scheduleBroadcast();
|
|
},
|
|
transformIndexHtml() {
|
|
const hmrUrl = server && resolveHmrUrl(server);
|
|
if (!hmrUrl) return [];
|
|
return [{ children: clientScript(hmrUrl), injectTo: 'body', tag: 'script' }];
|
|
},
|
|
};
|
|
};
|