From 31caef8823a2574aab2ac8d1ca8f52809f2badeb Mon Sep 17 00:00:00 2001 From: Xavier Stouder Date: Thu, 1 Jan 2026 01:36:28 +0100 Subject: [PATCH 1/3] esm: ensure watch mode restarts after syntax errors Move watch dependency reporting earlier in module resolution to ensure file dependencies are tracked even when parsing fails. Fixes: https://github.com/nodejs/node/issues/61153 --- lib/internal/modules/esm/loader.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/internal/modules/esm/loader.js b/lib/internal/modules/esm/loader.js index 40dafb59687b28..0bff0763fcf58f 100644 --- a/lib/internal/modules/esm/loader.js +++ b/lib/internal/modules/esm/loader.js @@ -542,6 +542,12 @@ class ModuleLoader { */ #getOrCreateModuleJobAfterResolve(parentURL, resolveResult, request, requestType) { const { url, format } = resolveResult; + + if (process.env.WATCH_REPORT_DEPENDENCIES && process.send) { + const type = requestType === kRequireInImportedCJS ? 'require' : 'import'; + process.send({ [`watch:${type}`]: [url] }); + } + // TODO(joyeecheung): update the module requests to use importAttributes as property names. const importAttributes = resolveResult.importAttributes ?? request.attributes; let job = this.loadCache.get(url, importAttributes.type); @@ -570,11 +576,6 @@ class ModuleLoader { assert(moduleOrModulePromise instanceof ModuleWrap, `Expected ModuleWrap for loading ${url}`); } - if (process.env.WATCH_REPORT_DEPENDENCIES && process.send) { - const type = requestType === kRequireInImportedCJS ? 'require' : 'import'; - process.send({ [`watch:${type}`]: [url] }); - } - const { ModuleJob, ModuleJobSync } = require('internal/modules/esm/module_job'); // TODO(joyeecheung): use ModuleJobSync for kRequireInImportedCJS too. const ModuleJobCtor = (requestType === kImportInRequiredESM ? ModuleJobSync : ModuleJob); From 0e69c4da38bcbdf9b6ed5e1fae9e0e813542a6a6 Mon Sep 17 00:00:00 2001 From: Xavier Stouder Date: Fri, 2 Jan 2026 15:43:35 +0100 Subject: [PATCH 2/3] test --- test/sequential/test-watch-mode.mjs | 47 +++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/test/sequential/test-watch-mode.mjs b/test/sequential/test-watch-mode.mjs index b9e57cb30bedc9..c8ff0b43b39f82 100644 --- a/test/sequential/test-watch-mode.mjs +++ b/test/sequential/test-watch-mode.mjs @@ -884,4 +884,51 @@ process.on('message', (message) => { await done(); } }); + + it('should watch changes even when there is syntax errors during esm loading', async () => { + // Create initial file with valid code + const initialContent = `console.log('hello, world');`; + const file = createTmpFile(initialContent, '.mjs'); + + const { done, restart } = runInBackground({ + args: ['--watch', file], + completed: 'Completed running', + shouldFail: true, + }); + + try { + const { stdout, stderr } = await restart(); + assert.strictEqual(stderr, ''); + assert.deepStrictEqual(stdout, [ + 'hello, world', + `Completed running ${inspect(file)}. Waiting for file changes before restarting...`, + ]) + + // Update file with syntax error + const syntaxErrorContent = `console.log('hello, wor`; + writeFileSync(file, syntaxErrorContent); + + // Wait for the failed restart + const { stderr: stderr2, stdout: stdout2 } = await restart(); + assert.match(stderr2, /SyntaxError: Invalid or unexpected token/); + assert.deepStrictEqual(stdout2, [ + `Restarting ${inspect(file)}`, + `Failed running ${inspect(file)}. Waiting for file changes before restarting...`, + ]) + + writeFileSync(file, `console.log('hello again, world');`); + + const { stderr: stderr3, stdout: stdout3 } = await restart(); + + // Verify it recovered and ran successfully + assert.strictEqual(stderr3, ''); + assert.deepStrictEqual(stdout3, [ + `Restarting ${inspect(file)}`, + 'hello again, world', + `Completed running ${inspect(file)}. Waiting for file changes before restarting...`, + ]); + } finally { + await done(); + } + }); }); From 91fddbe030bab74a6bbe6b8fac92bdd1ce067430 Mon Sep 17 00:00:00 2001 From: Xavier Stouder Date: Fri, 2 Jan 2026 16:30:38 +0100 Subject: [PATCH 3/3] lint --- test/sequential/test-watch-mode.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/sequential/test-watch-mode.mjs b/test/sequential/test-watch-mode.mjs index c8ff0b43b39f82..a141c3c9126eb3 100644 --- a/test/sequential/test-watch-mode.mjs +++ b/test/sequential/test-watch-mode.mjs @@ -902,7 +902,7 @@ process.on('message', (message) => { assert.deepStrictEqual(stdout, [ 'hello, world', `Completed running ${inspect(file)}. Waiting for file changes before restarting...`, - ]) + ]); // Update file with syntax error const syntaxErrorContent = `console.log('hello, wor`; @@ -914,7 +914,7 @@ process.on('message', (message) => { assert.deepStrictEqual(stdout2, [ `Restarting ${inspect(file)}`, `Failed running ${inspect(file)}. Waiting for file changes before restarting...`, - ]) + ]); writeFileSync(file, `console.log('hello again, world');`);