How to Configure HMR in Cordis With Debounce, Root, and Ignore Options

Configure Cordis HMR by setting root directories to watch, debounce milliseconds to batch rapid changes, and ignored glob patterns to exclude files—options passed directly to the underlying chokidar watcher.

The @cordisjs/plugin-hmr package provides hot module replacement for Cordis applications, allowing plugins to reload automatically when source files change. These three configuration options control precisely which files trigger reloads and how quickly those reloads occur.

Understanding the Core HMR Configuration Options

The HMR plugin accepts a configuration object that extends standard chokidar watcher options. According to the source code in packages/hmr/src/index.ts, three fields directly impact developer experience:

Option Type Default Purpose
root string[] ['.'] Directories to monitor, relative to the base path
debounce number 100 Milliseconds to wait before triggering reload after last change
ignored string[] ['**/node_modules', '**/.*', 'cache', 'data'] Glob patterns excluded from watching

These defaults are defined in the Hmr.Config interface at lines 8-13 of packages/hmr/src/index.ts.

How root Controls the Watch Scope

The root option determines where chokidar.watch() begins scanning. In packages/hmr/src/index.ts lines 8-13, the watcher initializes with:

const watcher = chokidar.watch(this.config.root, {
  ignored: this.config.ignored,
  // ...
})

Multiple directories can be specified as an array. Paths are resolved relative to the Cordis base directory.

How debounce Batches Rapid File Changes

The debounce value wraps the partialReload() method to prevent excessive reloads during rapid edits. At lines 25-26 of packages/hmr/src/index.ts:

this.debouncedReload = this.ctx.debounce(this.partialReload.bind(this), this.config.debounce)

The ctx.debounce utility itself is provided by @cordisjs/plugin-timer, implemented at lines 134-135 of packages/timer/src/index.ts. The test suite in packages/hmr/tests/index.spec.ts (lines 66-82) verifies that multiple changes within the debounce window trigger only a single reload.

How ignored Filters Unwanted Files

Files matching any pattern in ignored are excluded before the watcher emits events. The plugin uses picomatch for glob filtering as part of the chokidar initialization. Default patterns exclude node_modules, hidden files, and common data directories.

Configuring HMR in a Cordis YAML File

The most common approach uses a Cordis YAML configuration file. The plugin configuration is read during context initialization through ctx.loader.entries().


# cordis.yml

- id: timer
  name: '@cordisjs/plugin-timer'  # Required: provides ctx.debounce()

- id: hmr
  name: '@cordisjs/plugin-hmr'
  config:
    # Watch only these directories

    root:
      - src
    
    # Exclude from watching

    ignored:
      - '**/node_modules'
      - '**/.git'
      - '**/*.js'        # Skip compiled output if using TypeScript

      - 'dist/**'
    
    # Batch changes within 50ms

    debounce: 50

Configuring HMR Programmatically

For dynamic or environment-specific setups, pass the configuration object directly to ctx.plugin():

import { Context } from 'cordis';
import Loader from '@cordisjs/plugin-loader';
import Hmr from '@cordisjs/plugin-hmr';
import Timer from '@cordisjs/plugin-timer';

const ctx = new Context();

async function main() {
  // Required dependency: provides debounce utility
  await ctx.plugin(Timer);
  
  // Provides plugin loading infrastructure
  await ctx.plugin(Loader);
  
  // Configure HMR with custom options
  await ctx.plugin(Hmr, {
    root: ['src', 'lib'],                    // Monitor multiple directories
    ignored: [
      '**/node_modules',
      '**/.git',
      'dist/**',
      '**/*.test.ts'                         // Ignore test files
    ],
    debounce: 150                            // 150ms batching window
  });
  
  await ctx.start();
}

main();

Key Source Files and Implementation Details

File Lines Purpose
packages/hmr/src/index.ts 8-13, 25-26 Config interface, watcher creation, debounce wrapper
packages/hmr/tests/index.spec.ts 66-82 Debounce behavior verification
packages/timer/src/index.ts 134-135 ctx.debounce() implementation
packages/hmr/tests/cordis.yml — Example YAML configuration

Performance and Behavior Considerations

  • Lower debounce values (50ms or less) feel more responsive but may trigger multiple reloads during rapid saves
  • Higher debounce values (200ms+) reduce reload frequency but introduce noticeable delay
  • Restrictive root paths improve startup performance by reducing initial scan scope
  • Comprehensive ignored patterns prevent unnecessary reloads from build artifacts, lockfiles, and editor temporary files

The test suite at packages/hmr/tests/index.spec.ts demonstrates that proper configuration of these three options is essential for stable development workflows with large codebases.

Summary

  • root specifies which directories to watch; defaults to current working directory
  • debounce controls reload batching in milliseconds; defaults to 100ms
  • ignored filters files using glob patterns; excludes node_modules and hidden files by default
  • All options are passed directly to chokidar.watch() in packages/hmr/src/index.ts
  • The @cordisjs/plugin-timer dependency provides the underlying ctx.debounce() utility

Frequently Asked Questions

What happens if I don't install @cordisjs/plugin-timer?

The HMR plugin will fail to initialize because ctx.debounce() is undefined. The timer plugin is a required peer dependency that provides the debounce utility used at line 25 of packages/hmr/src/index.ts.

Can I use absolute paths in the root option?

Yes. The root array accepts any valid path that chokidar.watch() can resolve. Relative paths are resolved against the Cordis base directory; absolute paths are used as-is.

Why are my changes not triggering reloads despite correct root settings?

Check your ignored patterns. The default configuration excludes all dotfiles (**/.*). If your source files are in a hidden directory or match an ignored glob, they will not emit change events. Verify against the picomatch filtering in the watcher initialization.

Does debounce affect the initial load or only subsequent reloads?

The debounce option only affects hot reloads of already-loaded plugins. Initial plugin loading during context startup is not debounced. The debounced partialReload() wrapper created at lines 25-26 of packages/hmr/src/index.ts is invoked exclusively on file change events.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →