实现优雅的热重载:基于 PicoServer 的 Live Reload 方案 📅 2026/6/28 23:21:22 热重载的本质热重载解决的问题文件保存后浏览器自动刷新。传统流程修改 → 保存 → 切换窗口 → F54 步热重载后修改 → 保存2 步核心流程文件变更 → 服务端检测 → WebSocket 推送 → 浏览器刷新二、实现2.1 文件监控与防抖using PicoServer; using System.Timers; using System.IO; string staticRoot wwwroot; var app new WebAPIServer(); var watcher new FileSystemWatcher(staticRoot) { EnableRaisingEvents true, IncludeSubdirectories true }; var timer new System.Timers.Timer(200) { AutoReset false }; timer.Elapsed async (_, _) await app.WsBroadcastAsync(reload); void OnChange(object s, FileSystemEventArgs e) { var name Path.GetFileName(e.Name); if (string.IsNullOrEmpty(name) || name.StartsWith(.) || name.StartsWith(~)) return; // 过滤 IDE 临时文件 timer.Stop(); timer.Start(); // 200ms 内无新事件才触发 } watcher.Changed OnChange; watcher.Created OnChange; watcher.Deleted OnChange; watcher.Renamed OnChange;防抖的必要性一次保存操作可能触发 Changed、Created、Renamed 多个事件防抖将其合并为一次广播避免页面重复刷新。2.2 WebSocket 广播app.enableWebSocket true; app.WsOnConnectionChanged (_, _) Task.CompletedTask;PicoServer 开启 WebSocket 后通过以下方法广播await app.WsBroadcastAsync(reload);2.3 客户端脚本注入在返回 HTML 时自动注入 WebSocket 客户端代码开发者无需手动添加if (filePath.EndsWith(.html, StringComparison.OrdinalIgnoreCase)) { var content await File.ReadAllTextAsync(filePath); var script script new WebSocket(ws://location.host/ws-reload).onmessage e { if (e.data reload) location.reload(); }; /script ; content content.Contains(/body) ? content.Replace(/body, script /body) : content script; await res.WriteAsync(content, text/html); }2.4 路径放行/ws-reload路径需要交给 WebSocket 处理静态文件中间件应放行if (path /ws-reload) return true;三、完整示例using PicoServer; using System.Timers; var app new WebAPIServer(); app.enableWebSocket true; app.WsOnConnectionChanged (_, _) Task.CompletedTask; string staticRoot wwwroot; var watcher new FileSystemWatcher(staticRoot) { EnableRaisingEvents true }; var timer new System.Timers.Timer(200) { AutoReset false }; timer.Elapsed async (_, _) await app.WsBroadcastAsync(reload); watcher.Changed (s, e) { timer.Stop(); timer.Start(); }; app.AddMiddleware(async (req, res) { var path req.Url?.AbsolutePath ?? ; if (path /ws-reload) return true; // 解析文件路径 var reqPath path.TrimStart(/); if (string.IsNullOrEmpty(reqPath)) reqPath index.html; var filePath Path.GetFullPath(Path.Combine(staticRoot, reqPath)); // 安全检查 if (!filePath.StartsWith(staticRoot) || !File.Exists(filePath)) { res.StatusCode 404; await res.WriteAsync(Not Found); return false; } // HTML 文件注入脚本后返回 if (filePath.EndsWith(.html, StringComparison.OrdinalIgnoreCase)) { var content await File.ReadAllTextAsync(filePath); var script script new WebSocket(ws://location.host/ws-reload).onmessage e { if (e.data reload) location.reload(); }; /script ; content content.Contains(/body) ? content.Replace(/body, script /body) : content script; await res.WriteAsync(content, text/html); } else { // 非 HTML 文件直接返回 await res.SendFileAsync(filePath, false); } return false; }); app.StartServer(8080); Console.WriteLine(热重载已启动: http://localhost:8080); await Task.Delay(Timeout.Infinite);四、扩展方向CSS 热更新广播css消息客户端仅刷新样式链接多设备同步局域网内多端同时刷新差异化刷新根据文件类型决定刷新策略如.css增量更新.cshtml全页刷新五、关于 AOT热重载工具本身也应当保持轻量。Node.js 工具链通常需要安装运行时并下载数百 MB 依赖。通过 .NET AOT 编译整个热重载服务器可打包为4-10 MB 的单文件无需安装 .NET 运行时拷贝即用。六、总结组件代码量文件监控 防抖15 行WebSocket 广播1 行脚本注入10 行总计26 行