当前位置: 首页 > news >正文

实现优雅的热重载:基于 PicoServer 的 Live Reload 方案

热重载的本质

热重载解决的问题:文件保存后浏览器自动刷新

传统流程:修改 → 保存 → 切换窗口 → F5(4 步)
热重载后:修改 → 保存(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 行
http://www.jsqmd.com/news/1086238/

相关文章:

  • 【深度学习】【部署】Flask + PyTorch模型服务化:从API设计到生产环境实践【进阶】
  • 从零到一:基于PyTorch与EcapaTdnn构建高精度声纹识别系统
  • 银川黄金白银回收铂金旧金回收无套路门店 TOP 榜单 实地测评资料整理
  • Android音量调节进阶:从框架到HAL的实战调优指南
  • N_m3u8DL-RE:免费高效的流媒体下载工具完全指南
  • 终极指南:如何在Windows上完美释放Apple触控板的全部潜力
  • 矿卡CMP 40HX实战:优化Stable Diffusion WebUI,实现AI绘画效率跃升
  • 提离职像给一个老服务做下线通知:把“开口“这段流程拆清楚
  • Obsidian Pandoc插件技术解析:架构设计与多格式文档转换实现
  • 朋友圈广告:为什么它能让企业线上获客更简单
  • 2026年,想找性价比高且款式多的永康装甲门,哪家才是首选?
  • 广告AI助手设计:从Jarvis执行者到HAL合伙人
  • 云浮高口碑黄金铂金回收白银回收实体老店排行 5 家靠谱门店电话地址全收录
  • 微信好友检测工具完整指南:快速发现谁删除了你
  • CocosCreator长列表性能优化实战:基于对象池与动态渲染的无尽循环列表实现
  • MoE模型治理三重挑战:路由偏差、专家脆弱与病态路由
  • STM32H743+CubeMX-主从定时器联动:TIM1精准输出PWM,TIM2无中断同步计数
  • 3个高效技巧:让Illustrator脚本成为你的设计加速器
  • CMake 30:循环语法全解|foreach_while双循环精讲、迭代技巧与实战避坑指南
  • WCET分析工具实战:从理论到ARM平台精准评估
  • 【PHP运维】CentOS 7下通过Remi仓库yum升级至PHP 8.2实战
  • 扬州黄金白银回收铂金旧金回收无套路门店 TOP 榜单 实地测评资料整理
  • 编译原理《算符优先分析法的实战演练与代码剖析》
  • 瑞萨PG-FP6编程器MCU支持列表解析与量产烧录实战指南
  • 文档驱动开发:开源项目冷启动阶段的文档规范与交互式示例设计
  • 构建情报驱动自动化闭环:从漏洞预警到动态防御的实战体系
  • RA8M2 DAC12与TSN模块实战:从寄存器配置到高精度模拟信号处理
  • 5G NR PUCCH Format 0/1/2/3/4 资源复用与容量解析
  • openYuanrong进阶教程——使用 yr.wait 限制并发/待处理任务的数量
  • 阳江黄金白银回收铂金旧金回收无套路门店 TOP 榜单 实地测评资料整理