Description
When using miyagi with an external build tool (esbuild) that watches source files and outputs to a folder configured in assets.folder, miyagi triggers two browser reloads for every file change instead of one.
Environment
- miyagi version: 4.x (with Twing)
- Node.js: 24.1.0
- OS: macOS
Setup
Our project has:
- Source files:
src/components/ (templates, mocks, CSS, JS)
- Build output:
build/assets/ (esbuild bundles CSS/JS here)
- miyagi config:
assets: {
css: ['build/assets/css/common.css', ...],
js: [{src: 'build/assets/js/common.js', type: 'module'}, ...],
folder: ['src/fonts', 'build/assets'],
},
components: {
folder: 'src/components',
}
We run miyagi start and esbuild --watch in parallel.
Steps to Reproduce
- Configure miyagi with
assets.folder pointing to esbuild's output directory
- Run miyagi and esbuild watch mode in parallel
- Edit a CSS file in
src/components/
- Observe terminal output
Expected Behavior
One browser reload after the file change is processed.
Actual Behavior
Two browser reloads occur:
- First reload when miyagi detects the source file change in
src/components/
- Second reload ~1-5 seconds later when miyagi detects esbuild's output in
build/assets/
Terminal output shows:
A file has been changed, miyagi is updating the state.
[watch] build started (change: "src/components/.../file.css")
[watch] build finished
A file has been changed, miyagi is updating the state. <-- second detection
Reloading browser window!
Updating done!
Reloading browser window! <-- second reload
Updating done!
Root Cause Analysis
Looking at lib/init/watcher.js, miyagi watches:
const foldersToWatch = [
...assets.folder.map((f) => path.join(global.config.assets.root, f)),
...assets.css...,
...assets.js...,
];
if (components.folder) {
foldersToWatch.push(components.folder);
}
Both src/components AND build/assets are watched. When a source file changes:
- miyagi detects it → triggers reload
- esbuild rebuilds to
build/assets/
- miyagi detects the build output → triggers another reload
The components.ignores filter doesn't help because:
- It only accepts strings (functions cause
sanitizePath.startsWith is not a function error)
- Glob patterns like
** cause regex errors
- Simple strings don't match full paths properly with anymatch
Suggested Solutions
Option 1: Separate "serve" from "watch"
Add a config option to serve static files WITHOUT watching them:
assets: {
folder: ['build/assets'],
watchFolder: false, // NEW: serve but don't watch
}
Option 2: Add watchIgnores config
Add a dedicated ignore pattern for the watcher that supports globs/functions:
watcher: {
ignores: ['**/build/**'], // or: (path) => path.includes('/build/')
}
Option 3: File type filtering for components folder
Allow specifying which file types to watch in the components folder:
components: {
folder: 'src/components',
watchPatterns: ['**/*.twig', '**/*.yaml', '**/*.md'], // Only watch these
}
Option 4: Increase debounce timeout
The current 10ms debounce in the watcher is too short to batch the source change with the build output change. Increasing it to ~1000-1500ms would help, though this is a workaround rather than a fix.
Additional Issue: Spurious "Updating configuration" events
During debugging, we also observed multiple "Updating configuration..." messages even when only component files were changed (not .miyagi.js). This appears to be related to fs.watch emitting spurious events on macOS. The config file watcher might benefit from debouncing or using a more robust file watching approach.
Workaround Attempts That Failed
- Adding
"build" or "build/assets" to components.ignores - doesn't match paths correctly
- Using glob patterns like
"**/build/**" - causes regex errors
- Using functions in ignores - causes
sanitizePath.startsWith is not a function
Happy to provide more details or help with a fix. This is a common setup for projects that use bundlers alongside miyagi.
Description
When using miyagi with an external build tool (esbuild) that watches source files and outputs to a folder configured in
assets.folder, miyagi triggers two browser reloads for every file change instead of one.Environment
Setup
Our project has:
src/components/(templates, mocks, CSS, JS)build/assets/(esbuild bundles CSS/JS here)We run
miyagi startandesbuild --watchin parallel.Steps to Reproduce
assets.folderpointing to esbuild's output directorysrc/components/Expected Behavior
One browser reload after the file change is processed.
Actual Behavior
Two browser reloads occur:
src/components/build/assets/Terminal output shows:
Root Cause Analysis
Looking at
lib/init/watcher.js, miyagi watches:Both
src/componentsANDbuild/assetsare watched. When a source file changes:build/assets/The
components.ignoresfilter doesn't help because:sanitizePath.startsWith is not a functionerror)**cause regex errorsSuggested Solutions
Option 1: Separate "serve" from "watch"
Add a config option to serve static files WITHOUT watching them:
Option 2: Add watchIgnores config
Add a dedicated ignore pattern for the watcher that supports globs/functions:
Option 3: File type filtering for components folder
Allow specifying which file types to watch in the components folder:
Option 4: Increase debounce timeout
The current 10ms debounce in the watcher is too short to batch the source change with the build output change. Increasing it to ~1000-1500ms would help, though this is a workaround rather than a fix.
Additional Issue: Spurious "Updating configuration" events
During debugging, we also observed multiple "Updating configuration..." messages even when only component files were changed (not
.miyagi.js). This appears to be related tofs.watchemitting spurious events on macOS. The config file watcher might benefit from debouncing or using a more robust file watching approach.Workaround Attempts That Failed
"build"or"build/assets"tocomponents.ignores- doesn't match paths correctly"**/build/**"- causes regex errorssanitizePath.startsWith is not a functionHappy to provide more details or help with a fix. This is a common setup for projects that use bundlers alongside miyagi.