# Cordis Loader Config File Watching and Auto-Reload: A Complete Guide

> Master Cordis loader config file watching and auto-reload with our guide. Discover HMR, Chokidar integration, and effortless plugin updates for your Cordis projects.

- Repository: [Cordiverse/cordis](https://github.com/cordiverse/cordis)
- Tags: how-to-guide
- Published: 2026-08-25

---

**Cordis provides a built-in HMR (Hot Module Reload) service that watches loader configuration files using Chokidar and automatically reloads plugins when changes are detected, supporting both partial reloads for cached modules and full process restarts for external dependencies.**

The Cordis framework from `cordiverse/cordis` includes a sophisticated file watching system that monitors loader configurations like [`cordis.yml`](https://github.com/cordiverse/cordis/blob/main/cordis.yml) and triggers automatic plugin reloads during development. Understanding how this **Cordis loader config file watching and auto-reload** mechanism works is essential for building efficient development workflows that eliminate manual restarts.

## How the HMR File Watcher Initializes

The HMR service creates a file system watcher that mirrors the loader's own configuration schema, ensuring that any changes to your setup files are immediately detected.

### Creating the Chokidar Instance

In [`packages/hmr/src/index.ts`](https://github.com/cordiverse/cordis/blob/main/packages/hmr/src/index.ts), the service imports the `watch` function from **Chokidar** to instantiate an `FSWatcher`. The initialization occurs between lines 108-113:

```typescript
import { watch } from 'chokidar';
// ...
this.watcher = watch(root, {
  ...this.config,
  cwd: this.baseDir,
  ignored: path => match(relative(this.baseDir, path)),
});

```

The `root` parameter defines which directories to monitor, while the `ignored` function filters out paths matching patterns like `node_modules` or hidden files. The `cwd` is set to `this.baseDir`, ensuring all paths are resolved relative to your project's base directory.

### Configuring Watch Paths and Ignores

The HMR configuration interface extends `ChokidarOptions` and is validated using Schemastery. Defined in lines 81-99 of [`packages/hmr/src/index.ts`](https://github.com/cordiverse/cordis/blob/main/packages/hmr/src/index.ts), the schema specifies:

```typescript
export interface Config extends ChokidarOptions {
  base?: string;
  root: string[];
  debounce: number;
  ignored: string[];
}
export const Config = z.object({
  base: z.string(),
  root: z.array(String).role('table').default(['.']),
  ignored: z.array(String).role('table').default([
    '**/node_modules', '**/.*', 'cache', 'data',
  ]),
  debounce: z.natural().role('ms').default(100),
});

```

By default, the watcher monitors the current directory (`.`) while ignoring `node_modules`, dotfiles, and `cache` or `data` directories. The `debounce` setting prevents excessive reloads by waiting 100ms after the last change event before triggering actions.

## Handling File Changes and Reload Strategies

When the watcher detects a file modification, the service classifies the change and executes the appropriate reload strategy without manual intervention.

### Classifying Change Events

The core logic resides in the `'change'` event handler defined in [`packages/hmr/src/index.ts`](https://github.com/cordiverse/cordis/blob/main/packages/hmr/src/index.ts) (lines 124-149). When a file changes, the service resolves the absolute path and converts it to a file URL for comparison:

```typescript
this.watcher.on('change', async (path) => {
  const filename = resolve(this.baseDir, path);
  const url = pathToFileURL(filename).href;

  if (this.externals.has(url)) return loader.exit();               // full reload
  if (loader.internal!.loadCache.has(url)) {                       // partial reload
    this.stashed.add(url);
    return partialReload();
  }
  for (const entry of this.ctx.loader.entries()) {                 // config reload
    const include = entry.subtree as Include | undefined;
    if (include?.filename !== filename) continue;
    await include.refresh();
    return;
  }
  this.ctx.emit('hmr/change', url);
});

```

The handler distinguishes between three critical scenarios:

- **External framework files** – Triggers `loader.exit()` for a full process restart
- **Cached module files** – Queues a `partialReload()` after stashing the URL
- **Loader config files** – Calls `include.refresh()` to reload the configuration in-place

### Partial Reload vs. Full Restart

The **partial reload** mechanism clears specific entries from both the ESM `loadCache` and CJS `require.cache` before re-importing affected plugin entry files. This process maintains application state while updating only the changed code. If any step fails, the service rolls back changes to preserve process stability.

Conversely, external dependencies identified in `this.externals` require a complete process restart via `loader.exit()`, as they cannot be safely unlinked from the module graph during runtime.

## Loader Configuration Schema Integration

Because the HMR service reuses the loader's configuration interface, changes to [`cordis.yml`](https://github.com/cordiverse/cordis/blob/main/cordis.yml) automatically trigger the config reload path. The `include.refresh()` method, implemented in [`packages/loader/src/config/entry.ts`](https://github.com/cordiverse/cordis/blob/main/packages/loader/src/config/entry.ts), re-parses the YAML configuration and updates plugin entries without restarting the entire application context.

This integration means that editing plugin paths, entry names, or other loader settings in your configuration file results in immediate, automatic reloading of the affected components.

## Practical Implementation Examples

### Basic Cordis App with HMR Enabled

Enable file watching by installing the HMR plugin and configuring the watcher roots:

```typescript
import { Context } from 'cordis';
import loader from '@cordisjs/plugin-loader';
import hmr from '@cordisjs/plugin-hmr';

const ctx = new Context({
  loader: {
    // cordis.yml will be automatically watched
    config: 'cordis.yml',
  },
  // HMR options (optional – defaults are fine)
  hmr: {
    root: ['src'],
    ignored: ['node_modules', '.git'],
    debounce: 150,
  },
});

await ctx.plugin(loader);
await ctx.plugin(hmr);
await ctx.start();

```

This configuration watches the `src` directory and automatically reloads plugins whenever source files or the loader config ([`cordis.yml`](https://github.com/cordiverse/cordis/blob/main/cordis.yml)) changes.

### Updating the Loader Config at Runtime

Consider this [`cordis.yml`](https://github.com/cordiverse/cordis/blob/main/cordis.yml) configuration:

```yaml
loader:
  entries:
    - name: my-plugin
      path: ./plugins/my-plugin.ts

```

If you edit `loader.entries[0].path` to point at a different file and save, the HMR watcher detects the change, calls `include.refresh()`, and loads the new plugin module automatically without requiring a manual restart.

### Manually Forcing a Reload

You can programmatically trigger the same reload logic used by the file watcher:

```typescript
// Somewhere in your code
ctx.emit('hmr/reload', new Map()); // Triggers the same reload logic as a file change

```

## Summary

- **Chokidar Integration**: The HMR service in [`packages/hmr/src/index.ts`](https://github.com/cordiverse/cordis/blob/main/packages/hmr/src/index.ts) uses Chokidar to create robust file system watchers with configurable ignore patterns and debouncing.
- **Triple Strategy**: File changes trigger one of three paths—full restart for externals, partial reload for cached modules, or in-place refresh for loader configs.
- **Schema Alignment**: The HMR configuration mirrors the loader's config schema, ensuring [`cordis.yml`](https://github.com/cordiverse/cordis/blob/main/cordis.yml) changes are detected and processed immediately.
- **Development Experience**: Automatic reloading eliminates manual restarts during plugin development, with support for both JavaScript module cache clearing and YAML config re-parsing.

## Frequently Asked Questions

### How does Cordis detect changes in cordis.yml?

Cordis detects changes through the HMR service's file watcher, which monitors paths specified in the `root` configuration array. When [`cordis.yml`](https://github.com/cordiverse/cordis/blob/main/cordis.yml) is modified, the change event handler in [`packages/hmr/src/index.ts`](https://github.com/cordiverse/cordis/blob/main/packages/hmr/src/index.ts) iterates through `this.ctx.loader.entries()` to find matching `Include` objects and calls `include.refresh()` to reload the configuration without restarting the process.

### What is the difference between partial and full reloads in Cordis?

**Partial reloads** clear specific entries from the ESM `loadCache` and CJS `require.cache` before re-importing changed plugin files, preserving the application state. **Full reloads** occur when external framework files change, triggering `loader.exit()` to restart the entire process because these dependencies cannot be safely removed from the module cache at runtime.

### Can I customize which files trigger a reload?

Yes, through the `ignored` array in the HMR configuration schema. By default, the watcher ignores `**/node_modules`, `**/.*`, `cache`, and `data` directories. You can customize this in your Context configuration under the `hmr` key to add or remove patterns according to your project structure.

### How do I enable file watching in a Cordis application?

Install the `@cordisjs/plugin-hmr` package and register it with your Context instance after the loader plugin. The watcher starts automatically when you call `ctx.start()`, monitoring the directories specified in the `root` configuration option and using the debounce setting to control reload timing.