From ecb3a8933f2058cfba93e600fccc100473d1df91 Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Sun, 16 Apr 2023 15:46:36 -0400 Subject: [PATCH] fix #3062: watch mode with `NODE_PATH` edge case --- CHANGELOG.md | 4 ++++ internal/fs/fs_real.go | 5 ++++- scripts/js-api-tests.js | 48 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bf95ec72b1..bdda24a3c61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,10 @@ } ``` +* Fix watch mode with `NODE_PATH` ([#3062](https://github.com/evanw/esbuild/issues/3062)) + + Node has a rarely-used feature where you can extend the set of directories that node searches for packages using the `NODE_PATH` environment variable. While esbuild supports this too, previously a bug prevented esbuild's watch mode from picking up changes to imported files that were contained directly in a `NODE_PATH` directory. You're supposed to use `NODE_PATH` for packages, but some people abuse this feature by putting files in that directory instead (e.g. `node_modules/some-file.js` instead of `node_modules/some-pkg/some-file.js`). The watch mode bug happens when you do this because esbuild first tries to read `some-file.js` as a directory and then as a file. Watch mode was incorrectly waiting for `some-file.js` to become a valid directory. This release fixes this edge case bug by changing watch mode to watch `some-file.js` as a file when this happens. + ## 0.17.16 * Fix CSS nesting transform for triple-nested rules that start with a combinator ([#3046](https://github.com/evanw/esbuild/issues/3046)) diff --git a/internal/fs/fs_real.go b/internal/fs/fs_real.go index 9adb7059e7a..c8aad07a218 100644 --- a/internal/fs/fs_real.go +++ b/internal/fs/fs_real.go @@ -212,7 +212,10 @@ func (fs *realFS) ReadFile(path string) (contents string, canonicalError error, data, ok := fs.watchData[path] if canonicalError != nil { data.state = stateFileMissing - } else if !ok { + } else if !ok || data.state == stateDirUnreadable { + // Note: If "ReadDirectory" is called before "ReadFile" with this same + // path, then "data.state" will be "stateDirUnreadable". In that case + // we want to transition to "stateFileNeedModKey" because it's a file. data.state = stateFileNeedModKey } data.fileContents = fileContents diff --git a/scripts/js-api-tests.js b/scripts/js-api-tests.js index 08a132737d0..b43b4c75302 100644 --- a/scripts/js-api-tests.js +++ b/scripts/js-api-tests.js @@ -4062,6 +4062,54 @@ let watchTests = { await context.dispose() } }, + + // See: https://github.com/evanw/esbuild/issues/3062 + async watchNodePaths({ esbuild, testDir }) { + const input = path.join(testDir, 'in.js') + const outfile = path.join(testDir, 'out.js') + const libDir = path.join(testDir, 'lib') + const libFile = path.join(libDir, 'foo.js') + await mkdirAsync(libDir, { recursive: true }) + await writeFileAsync(input, ` + import { foo } from ${JSON.stringify(path.basename(libFile))} + console.log(foo) + `) + + const { rebuildUntil, plugin } = makeRebuildUntilPlugin() + const context = await esbuild.context({ + entryPoints: [input], + outfile, + write: false, + bundle: true, + minifyWhitespace: true, + format: 'esm', + logLevel: 'silent', + plugins: [plugin], + nodePaths: [libDir], + }) + + try { + const result = await rebuildUntil( + () => { + context.watch() + writeFileAtomic(libFile, `export let foo = 0`) + }, + result => result.outputFiles.length === 1, + ) + assert.strictEqual(result.outputFiles[0].text, `var foo=0;console.log(foo);\n`) + + // Make sure watch mode works for files imported via NODE_PATH + for (let i = 1; i <= 3; i++) { + const result2 = await rebuildUntil( + () => writeFileAtomic(libFile, `export let foo = ${i}`), + result => result.outputFiles.length === 1 && result.outputFiles[0].text.includes(i), + ) + assert.strictEqual(result2.outputFiles[0].text, `var foo=${i};console.log(foo);\n`) + } + } finally { + await context.dispose() + } + }, } let serveTests = {