Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .changeset/fix-story-watching-chokidar-v4.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"@ladle/react": patch
---

Fix story file watching for add/remove detection with chokidar v4

Chokidar v4 no longer supports glob patterns directly, so the watcher now:

- Extracts base directories from glob patterns using `getGlobBasePath`
- Filters story files using `STORY_FILE_REGEX` in the `ignored` callback
- Properly handles the case where `stats` is undefined during initial directory checks

This fix ensures that adding or removing story files triggers a full reload as expected.
48 changes: 48 additions & 0 deletions packages/ladle/lib/cli/story-watcher.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import chokidar from "chokidar";

/**
* Extract the base directory from a glob pattern.
* e.g., "src/**\/*.stories.tsx" -> "src"
* @param {string} pattern
* @returns {string}
*/
export const getGlobBasePath = (pattern) => {
// Normalise path separators to forward slashes for cross-platform compatibility
const normalised = pattern.replace(/\\/g, "/");
const parts = normalised.split("/");
const baseParts = [];
for (const part of parts) {
if (part.includes("*") || part.includes("{") || part.includes("[")) break;
baseParts.push(part);
}
return baseParts.length > 0 ? baseParts.join("/") : ".";
};

// Story file pattern - matches .stories.{js,jsx,ts,tsx,mdx}
export const STORY_FILE_REGEX = /\.stories\.(js|jsx|ts|tsx|mdx)$/;
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should not redefine this here, there is already a default and users can customize it

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, apologies, missed that 😅 just pushed an update to use the given pattern and also realised picomatch can find the base path so removed the custom script to do it


/**
* Creates a chokidar watcher configured to watch story files.
* @param {string | string[]} storyPatterns - Glob pattern(s) for stories
* @param {object} options - Optional chokidar options override
* @returns {import("chokidar").FSWatcher}
*/
export const createStoryWatcher = (storyPatterns, options = {}) => {
const patterns = Array.isArray(storyPatterns)
? storyPatterns
: [storyPatterns];
const baseDirs = [...new Set(patterns.map(getGlobBasePath))];

return chokidar.watch(baseDirs, {
persistent: true,
ignoreInitial: true,
ignored: (filePath, stats) => {
// Don't ignore directories - we need to traverse into them
// In chokidar v4, stats can be undefined for initial directory checks
if (stats === undefined || stats?.isDirectory()) return false;
// Only watch story files
return !STORY_FILE_REGEX.test(filePath);
},
...options,
});
};
7 changes: 2 additions & 5 deletions packages/ladle/lib/cli/vite-dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ import path from "path";
import getPort from "get-port";
import { globby } from "globby";
import boxen from "boxen";
import chokidar from "chokidar";
import openBrowser from "./open-browser.js";
import debug from "./debug.js";
import getBaseViteConfig from "./vite-base.js";
import { getMetaJsonObject } from "./vite-plugin/generate/get-meta-json.js";
import { getEntryData } from "./vite-plugin/parse/get-entry-data.js";
import { connectToKoa } from "./vite-plugin/connect-to-koa.js";
import { createStoryWatcher } from "./story-watcher.js";

/**
* @param config {import("../shared/types").Config}
Expand Down Expand Up @@ -167,10 +167,7 @@ const bundler = async (config, configFolder) => {

if (config.noWatch === false) {
// trigger full reload when new stories are added or removed
const watcher = chokidar.watch(config.stories, {
persistent: true,
ignoreInitial: true,
});
const watcher = createStoryWatcher(config.stories);
let checkSum = "";
const getChecksum = async () => {
try {
Expand Down
Loading
Loading