文件监控 #

一、文件监控概述 #

Deno提供了 Deno.watchFs API用于监控文件系统的变化,常用于开发工具、热重载等场景。

二、基本用法 #

2.1 监控目录 #

typescript
const watcher = Deno.watchFs("./src");

for await (const event of watcher) {
  console.log("Event:", event.kind);
  console.log("Paths:", event.paths);
}

2.2 监控多个路径 #

typescript
const watcher = Deno.watchFs(["./src", "./tests"]);

for await (const event of watcher) {
  console.log(event);
}

2.3 监控选项 #

typescript
const watcher = Deno.watchFs("./src", {
  recursive: true  // 递归监控子目录
});

for await (const event of watcher) {
  console.log(event);
}

三、事件类型 #

3.1 事件种类 #

typescript
type FsEventKind = 
  | "any"        // 任何变化
  | "access"     // 文件访问
  | "create"     // 文件创建
  | "modify"     // 文件修改
  | "remove"     // 文件删除
  | "other";     // 其他事件

const watcher = Deno.watchFs("./src");

for await (const event of watcher) {
  switch (event.kind) {
    case "create":
      console.log("文件创建:", event.paths);
      break;
    case "modify":
      console.log("文件修改:", event.paths);
      break;
    case "remove":
      console.log("文件删除:", event.paths);
      break;
  }
}

3.2 事件结构 #

typescript
interface FsEvent {
  kind: FsEventKind;
  paths: string[];
  flag?: FsEventFlag;
}

四、实际应用 #

4.1 热重载服务器 #

typescript
async function startServer() {
  let process: Deno.ChildProcess | null = null;
  
  async function restart() {
    if (process) {
      process.kill();
      await process.status;
    }
    
    process = new Deno.Command("deno", {
      args: ["run", "--allow-net", "./server.ts"]
    }).spawn();
    
    console.log("Server started");
  }
  
  await restart();
  
  const watcher = Deno.watchFs("./src", { recursive: true });
  
  for await (const event of watcher) {
    if (event.kind === "modify" && event.paths.some(p => p.endsWith(".ts"))) {
      console.log("File changed, restarting...");
      await restart();
    }
  }
}

4.2 日志文件监控 #

typescript
async function watchLogFile(path: string) {
  let lastSize = 0;
  
  try {
    const info = await Deno.stat(path);
    lastSize = info.size;
  } catch {
    // 文件不存在
  }
  
  const watcher = Deno.watchFs(path);
  
  for await (const event of watcher) {
    if (event.kind === "modify") {
      try {
        const info = await Deno.stat(path);
        const newSize = info.size;
        
        if (newSize > lastSize) {
          const file = await Deno.open(path);
          await file.seek(lastSize, Deno.SeekMode.Start);
          
          const buffer = new Uint8Array(newSize - lastSize);
          await file.read(buffer);
          
          console.log(new TextDecoder().decode(buffer));
          
          lastSize = newSize;
          file.close();
        }
      } catch (error) {
        console.error("Error:", error);
      }
    }
  }
}

4.3 配置文件监控 #

typescript
interface Config {
  host: string;
  port: number;
}

async function watchConfig(path: string, onChange: (config: Config) => void) {
  let config: Config = { host: "localhost", port: 3000 };
  
  async function loadConfig() {
    try {
      const content = await Deno.readTextFile(path);
      config = JSON.parse(content);
      onChange(config);
    } catch (error) {
      console.error("Failed to load config:", error);
    }
  }
  
  await loadConfig();
  
  const watcher = Deno.watchFs(path);
  
  for await (const event of watcher) {
    if (event.kind === "modify") {
      // 防抖处理
      await new Promise(resolve => setTimeout(resolve, 100));
      await loadConfig();
    }
  }
}

await watchConfig("./config.json", (config) => {
  console.log("Config updated:", config);
});

4.4 文件同步工具 #

typescript
async function syncDirectories(src: string, dest: string) {
  // 初始同步
  await copyDir(src, dest);
  
  const watcher = Deno.watchFs(src, { recursive: true });
  
  for await (const event of watcher) {
    for (const path of event.paths) {
      const relativePath = path.replace(src, "");
      const destPath = `${dest}${relativePath}`;
      
      switch (event.kind) {
        case "create":
        case "modify":
          try {
            const info = await Deno.stat(path);
            if (info.isFile) {
              await Deno.copyFile(path, destPath);
              console.log(`Copied: ${relativePath}`);
            }
          } catch {
            // 忽略错误
          }
          break;
          
        case "remove":
          try {
            await Deno.remove(destPath);
            console.log(`Removed: ${relativePath}`);
          } catch {
            // 忽略错误
          }
          break;
      }
    }
  }
}

async function copyDir(src: string, dest: string) {
  await Deno.mkdir(dest, { recursive: true });
  
  for await (const entry of Deno.readDir(src)) {
    const srcPath = `${src}/${entry.name}`;
    const destPath = `${dest}/${entry.name}`;
    
    if (entry.isDirectory) {
      await copyDir(srcPath, destPath);
    } else {
      await Deno.copyFile(srcPath, destPath);
    }
  }
}

五、注意事项 #

5.1 事件去重 #

typescript
function debounce<T extends unknown[]>(
  fn: (...args: T) => void,
  delay: number
): (...args: T) => void {
  let timeoutId: number | undefined;
  
  return (...args: T) => {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => fn(...args), delay);
  };
}

const handleChange = debounce((paths: string[]) => {
  console.log("Changed:", paths);
}, 100);

const watcher = Deno.watchFs("./src", { recursive: true });

for await (const event of watcher) {
  handleChange(event.paths);
}

5.2 关闭监控 #

typescript
const watcher = Deno.watchFs("./src");

// 在某个条件下关闭
setTimeout(() => {
  watcher.close();
  console.log("Watcher closed");
}, 10000);

for await (const event of watcher) {
  console.log(event);
}

5.3 错误处理 #

typescript
try {
  const watcher = Deno.watchFs("./nonexistent");
  
  for await (const event of watcher) {
    console.log(event);
  }
} catch (error) {
  console.error("Watch error:", error);
}

六、总结 #

本章学习了:

  • 文件监控的基本用法
  • 事件类型
  • 实际应用场景
  • 注意事项

下一章,我们将学习流处理。

最后更新:2026-03-28