Go 静态站点 Live Reload 实战

⭐⭐⭐⭐ (4星) Go 教程 开发工具

摘要

详细教程教你如何用 Go 为静态站点生成器实现 live reload 功能,监控文件变化自动重建并刷新浏览器。

实现步骤

1. 文件监听 (File Watcher)

使用 fsnotify 库监听文件变化:

watcher, err := fsnotify.NewWatcher()
defer watcher.Close()

// 遍历目录并添加监听
filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error {
    if d.IsDir() {
        err := watcher.Add(path)
        // ...
    }
    return nil
})

// 事件循环处理创建/修改/删除/重命名
for {
    select {
    case event, ok := <-watcher.Events:
        if event.Has(fsnotify.Create) || event.Has(fsnotify.Remove) || 
           event.Has(fsnotify.Rename) || event.Has(fsnotify.Write) {
            builder.Build()
        }
    // ...
    }
}

2. Debouncing 防抖

文本编辑器保存文件会触发多个事件,需要防抖机制避免重复构建:

timer := time.NewTimer(math.MaxInt64)
timer.Stop()

for {
    select {
    case event := <-watcher.Events:
        if event.Has(fsnotify.Create) || event.Has(fsnotify.Write) {
            timer.Reset(200 * time.Millisecond)  // 200ms 延迟
        }
    case <-timer.C:
        builder.Build()  // 延迟后才执行构建
    }
}

编辑器保存文件的典型流程:创建临时文件 → 写入 → 重命名 → 删除备份,多个事件会被防抖机制合并。

3. Server-Sent Events (SSE)

用 SSE 替代 WebSocket 实现服务器推送,更轻量:

type SSEBroker struct {
    mu      sync.Mutex
    clients map[chan string]struct{}
}

func (b *SSEBroker) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "text/event-stream")
    ch := b.Subscribe()
    defer b.Unsubscribe(ch)
    
    for {
        select {
        case msg := <-ch:
            fmt.Fprintf(w, "data: %s\n\n", msg)
            flusher.Flush()
        case <-r.Context().Done():
            return
        }
    }
}

文件变化后调用 broker.Broadcast("reload") 通知客户端。

4. JavaScript 注入中间件

通过中间件在 HTML 页面注入 SSE 客户端脚本:

func withLiveReload(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        buf := &bufferedHTTPWriter{ResponseWriter: w, status: 200}
        next.ServeHTTP(buf, r)
        
        body := buf.buf.String()
        if strings.Contains(buf.Header().Get("Content-Type"), "text/html") {
            script := ``
            body = strings.Replace(body, "