feat(watchers): add Rust-powered watcher backend with runtime fallback and cross-platform test coverage
This commit is contained in:
113
readme.md
113
readme.md
@@ -1,6 +1,6 @@
|
||||
# @push.rocks/smartwatch
|
||||
|
||||
A lightweight, cross-runtime file watcher with glob pattern support for **Node.js**, **Deno**, and **Bun**. Zero heavyweight dependencies — just native file watching APIs for maximum performance. 🚀
|
||||
A cross-runtime file watcher with glob pattern support for Node.js, Deno, and Bun.
|
||||
|
||||
## Issue Reporting and Security
|
||||
|
||||
@@ -16,14 +16,25 @@ pnpm add @push.rocks/smartwatch
|
||||
|
||||
## Features
|
||||
|
||||
🌐 **Cross-Runtime** — Works seamlessly on Node.js 20+, Deno, and Bun
|
||||
🔍 **Glob Pattern Support** — Watch files using familiar patterns like `**/*.ts` and `src/**/*.{js,jsx}`
|
||||
📡 **RxJS Observables** — Subscribe to file system events using reactive streams
|
||||
🔄 **Dynamic Watching** — Add or remove watch patterns at runtime
|
||||
⚡ **Native Performance** — Uses `fs.watch()` on Node.js/Bun and `Deno.watchFs()` on Deno
|
||||
✨ **Write Stabilization** — Built-in debouncing prevents duplicate events during file writes
|
||||
🎯 **TypeScript First** — Full TypeScript support with comprehensive type definitions
|
||||
📦 **Minimal Footprint** — No chokidar, no FSEvents bindings — just ~500 lines of focused code
|
||||
- **Cross-Runtime** — Works on Node.js 20+, Deno, and Bun
|
||||
- **Glob Pattern Support** — Watch files using patterns like `**/*.ts` and `src/**/*.{js,jsx}`
|
||||
- **RxJS Observables** — Subscribe to file system events using reactive streams
|
||||
- **Dynamic Watching** — Add or remove watch patterns at runtime
|
||||
- **Write Stabilization** — Built-in debouncing and awaitWriteFinish support for atomic writes
|
||||
- **TypeScript First** — Full TypeScript support with comprehensive type definitions
|
||||
|
||||
## How It Works
|
||||
|
||||
smartwatch selects the best file watching backend for the current runtime:
|
||||
|
||||
| Runtime | Backend |
|
||||
|-----------------|----------------------------------|
|
||||
| **Node.js/Bun** | [chokidar](https://github.com/paulmillr/chokidar) v5 (uses `fs.watch()` internally) |
|
||||
| **Deno** | Native `Deno.watchFs()` API |
|
||||
|
||||
On Node.js and Bun, chokidar provides robust cross-platform file watching with features like atomic write detection, inode tracking, and write stabilization. On Deno, native APIs are used directly with built-in debouncing and temporary file filtering.
|
||||
|
||||
Glob patterns are handled through [picomatch](https://github.com/micromatch/picomatch) — base paths are extracted from patterns and watched natively, while events are filtered through matchers before emission.
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -34,8 +45,8 @@ import { Smartwatch } from '@push.rocks/smartwatch';
|
||||
|
||||
// Create a watcher with glob patterns
|
||||
const watcher = new Smartwatch([
|
||||
'./src/**/*.ts', // Watch all TypeScript files in src
|
||||
'./public/assets/**/*' // Watch all files in public/assets
|
||||
'./src/**/*.ts',
|
||||
'./public/assets/**/*'
|
||||
]);
|
||||
|
||||
// Start watching
|
||||
@@ -49,15 +60,9 @@ Use RxJS observables to react to file system changes:
|
||||
```typescript
|
||||
// Get an observable for file changes
|
||||
const changeObservable = await watcher.getObservableFor('change');
|
||||
|
||||
changeObservable.subscribe({
|
||||
next: ([path, stats]) => {
|
||||
console.log(`File changed: ${path}`);
|
||||
console.log(`New size: ${stats?.size} bytes`);
|
||||
},
|
||||
error: (err) => {
|
||||
console.error(`Error: ${err}`);
|
||||
}
|
||||
changeObservable.subscribe(([path, stats]) => {
|
||||
console.log(`File changed: ${path}`);
|
||||
console.log(`New size: ${stats?.size} bytes`);
|
||||
});
|
||||
|
||||
// Watch for new files
|
||||
@@ -103,7 +108,6 @@ watcher.remove('./src/**/*.test.ts');
|
||||
### Stopping the Watcher
|
||||
|
||||
```typescript
|
||||
// Stop watching when done
|
||||
await watcher.stop();
|
||||
```
|
||||
|
||||
@@ -113,38 +117,31 @@ await watcher.stop();
|
||||
import { Smartwatch } from '@push.rocks/smartwatch';
|
||||
|
||||
async function watchProject() {
|
||||
// Initialize with patterns
|
||||
const watcher = new Smartwatch([
|
||||
'./src/**/*.ts',
|
||||
'./package.json'
|
||||
]);
|
||||
|
||||
// Start the watcher
|
||||
await watcher.start();
|
||||
console.log('👀 Watching for changes...');
|
||||
console.log('Watching for changes...');
|
||||
|
||||
// Subscribe to changes
|
||||
const changes = await watcher.getObservableFor('change');
|
||||
changes.subscribe(([path, stats]) => {
|
||||
console.log(`📝 Modified: ${path}`);
|
||||
console.log(` Size: ${stats?.size ?? 'unknown'} bytes`);
|
||||
console.log(`Modified: ${path} (${stats?.size ?? 'unknown'} bytes)`);
|
||||
});
|
||||
|
||||
// Subscribe to new files
|
||||
const additions = await watcher.getObservableFor('add');
|
||||
additions.subscribe(([path]) => {
|
||||
console.log(`✨ New file: ${path}`);
|
||||
console.log(`New file: ${path}`);
|
||||
});
|
||||
|
||||
// Subscribe to deletions
|
||||
const deletions = await watcher.getObservableFor('unlink');
|
||||
deletions.subscribe(([path]) => {
|
||||
console.log(`🗑️ Deleted: ${path}`);
|
||||
console.log(`Deleted: ${path}`);
|
||||
});
|
||||
|
||||
// Handle graceful shutdown
|
||||
process.on('SIGINT', async () => {
|
||||
console.log('\n🛑 Stopping watcher...');
|
||||
await watcher.stop();
|
||||
process.exit(0);
|
||||
});
|
||||
@@ -153,41 +150,6 @@ async function watchProject() {
|
||||
watchProject();
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
smartwatch uses native file watching APIs for each runtime:
|
||||
|
||||
| Runtime | API Used |
|
||||
|-----------------|----------------------------------|
|
||||
| **Node.js 20+** | `fs.watch({ recursive: true })` |
|
||||
| **Deno** | `Deno.watchFs()` |
|
||||
| **Bun** | Node.js compatibility layer |
|
||||
|
||||
### Under the Hood
|
||||
|
||||
Native file watching APIs don't support glob patterns directly, so smartwatch handles pattern matching internally:
|
||||
|
||||
1. **Base path extraction** — Extracts the static portion from each glob pattern (e.g., `./src/` from `./src/**/*.ts`)
|
||||
2. **Efficient watching** — Native watchers monitor only the base directories
|
||||
3. **Pattern filtering** — Events are filtered through [picomatch](https://github.com/micromatch/picomatch) matchers before emission
|
||||
4. **Event deduplication** — Built-in throttling prevents duplicate events from rapid file operations
|
||||
|
||||
### Write Stabilization
|
||||
|
||||
smartwatch includes built-in write stabilization (similar to chokidar's `awaitWriteFinish`). When a file is being written, events are held until the file size stabilizes, preventing multiple events for a single write operation.
|
||||
|
||||
Default settings:
|
||||
- **Stability threshold**: 300ms
|
||||
- **Poll interval**: 100ms
|
||||
|
||||
## Requirements
|
||||
|
||||
| Runtime | Version |
|
||||
|-----------------|----------------------------------------|
|
||||
| **Node.js** | 20+ (required for native recursive watching) |
|
||||
| **Deno** | Any version with `Deno.watchFs()` support |
|
||||
| **Bun** | Uses Node.js compatibility |
|
||||
|
||||
## API Reference
|
||||
|
||||
### `Smartwatch`
|
||||
@@ -226,18 +188,13 @@ type TFsEvent = 'add' | 'addDir' | 'change' | 'error' | 'unlink' | 'unlinkDir' |
|
||||
type TSmartwatchStatus = 'idle' | 'starting' | 'watching';
|
||||
```
|
||||
|
||||
## Why smartwatch?
|
||||
## Requirements
|
||||
|
||||
| Feature | smartwatch | chokidar |
|
||||
|-------------------------|----------------------|--------------------|
|
||||
| Native API | ✅ Direct `fs.watch` | ❌ FSEvents bindings |
|
||||
| Cross-runtime | ✅ Node, Deno, Bun | ❌ Node only |
|
||||
| Dependencies | 4 small packages | ~20 packages |
|
||||
| Write stabilization | ✅ Built-in | ✅ Built-in |
|
||||
| Glob support | ✅ picomatch | ✅ anymatch |
|
||||
| Bundle size | ~15KB | ~200KB+ |
|
||||
|
||||
If you need a lightweight file watcher without native compilation headaches, smartwatch has you covered.
|
||||
| Runtime | Version |
|
||||
|-----------------|----------------------------------------|
|
||||
| **Node.js** | 20+ |
|
||||
| **Deno** | Any version with `Deno.watchFs()` support |
|
||||
| **Bun** | Uses Node.js compatibility layer |
|
||||
|
||||
## License and Legal Information
|
||||
|
||||
|
||||
Reference in New Issue
Block a user