Repository navigation
Dev server triggers spurious rebuild due to browser-field disabled files leaking into watchFiles #33160
Description
Issue: Dev server triggers spurious rebuild due to browser-field disabled files leaking into watchFiles
Command
serve
Is this a regression?
Yes — this did not occur with the Webpack-based builder (@angular-devkit/build-angular:browser). It is specific to the @angular-devkit/build-angular:application builder (esbuild/Vite).
Description
When running ng serve (or nx serve with the Angular dev-server), the application builds successfully, then immediately triggers "Changes detected. Rebuilding..." without any user file changes. This results in a full second rebuild on every serve startup, adding ~30–50 seconds of wasted time.
Root Cause
The issue is in isInternalBundlerFile() in bundler-context.ts. This function is responsible for filtering out non-real files from the watch file list. It currently handles (disabled): paths only when the suffix is a Node.js builtin module (e.g., fs, path, util):
// Current code (v19.2.25):
function isInternalBundlerFile(file: string): boolean {
// ...
const DISABLED_BUILTIN = '(disabled):';
const disabledIndex = file.indexOf(DISABLED_BUILTIN);
if (disabledIndex >= 0) {
return builtinModules.includes(file.slice(disabledIndex + DISABLED_BUILTIN.length));
}
return false;
}However, esbuild also generates (disabled): prefixed inputs for browser-field disabled files — i.e., when a third-party package has "browser": { "./some-file.js": false } in its package.json. In this case, esbuild records the input as:
(disabled):node_modules/object-inspect/util.inspect
Since "node_modules/object-inspect/util.inspect" is not in builtinModules, isInternalBundlerFile() returns false, and this non-existent path gets added to the watch file list.
The path is then joined with the workspace root to form:
P:\Projects\my-app\(disabled):node_modules\object-inspect\util.inspect
Watchpack (the underlying file watcher) detects that this file does not exist and reports it as "removed", triggering an immediate rebuild.
Specific package triggering this
object-inspect (v1.13.4) — a transitive dependency commonly pulled in via qs → side-channel → object-inspect. Its package.json contains:
{
"browser": {
"./util.inspect.js": false
}
}And util.inspect.js contains:
module.exports = require('util').inspect;This is a legitimate pattern — the package disables the Node.js util.inspect wrapper in browser environments. But esbuild's metafile input entry (disabled):node_modules/object-inspect/util.inspect is not properly filtered by the Angular builder.
Suggested Fix
Change isInternalBundlerFile() to treat any path containing (disabled): as internal, not just Node.js builtins:
function isInternalBundlerFile(file: string): boolean {
if (file[0] === '<' && file.at(-1) === '>') {
return true;
}
// Any (disabled): path is a virtual esbuild entry that doesn't exist on disk
if (file.includes('(disabled):')) {
return true;
}
return false;
}This is safe because (disabled): is an esbuild-internal convention for files that have been stubbed out (whether builtins or browser-field disabled files). None of these paths exist on disk, so watching them is always incorrect.
Minimal Reproduction
- Create a new Angular 19 application using the
applicationbuilder - Install a package that depends on
object-inspect(e.g.,npm install qs) - Import it somewhere in the app:
import qs from 'qs'; - Run
ng serve - Observe:
- Build completes successfully
- "Watch mode enabled. Watching for file changes..." appears
- Immediately followed by "Changes detected. Rebuilding..." (with no user action)
To confirm the cause, add this line before the for await loop in build-action.js:
require('node:fs').writeFileSync('watcher-debug.log', changes.toDebugString(), 'utf8');The log will show:
{
"added": [],
"modified": [],
"removed": [
"P:\\path\\to\\project\\(disabled):node_modules\\object-inspect\\util.inspect"
]
}Your Environment
Angular CLI: 19.2.25
Node: 20.12.2
Package Manager: npm 10.8.3
OS: win32 x64
Angular: 19.2.21
Package Version
---------------------------------------------------------
@angular-devkit/architect 0.1902.25
@angular-devkit/build-angular 19.2.25
@angular-devkit/core 19.2.25
@angular/build 19.2.25
@angular/cli 19.2.25
Anything else relevant?
- The issue likely affects any package using the
"browser": { "./file.js": false }pattern inpackage.json.object-inspectis just one common example. - The problem is Windows-specific in terms of symptoms (watchpack on Windows uses
ReadDirectoryChangesWwhich is more sensitive), but the underlying bug (adding non-existent paths to the watch list) exists cross-platform. - A previous related issue #25197 was fixed via #26182 (ignoring dot-directories). This is a different manifestation of the same class of problem.
- The relevant source file is:
packages/angular/build/src/tools/esbuild/bundler-context.ts, functionisInternalBundlerFile().
Activity
fix(@angular/build): ignore virtual esbuild paths with (disabled):
fix(@angular/build): ignore virtual esbuild paths with (disabled):
fix(@angular/build): ignore virtual esbuild paths with (disabled):
angular-automatic-lock-bot commentedon Jun 14, 2026
This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.
Read more about our automatic conversation locking policy.
This action has been performed automatically by a bot.