Skip to content

Commit ecb3a89

Browse files
committed
fix #3062: watch mode with NODE_PATH edge case
1 parent a4e19a7 commit ecb3a89

File tree

3 files changed

+56
-1
lines changed

3 files changed

+56
-1
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@
2121
}
2222
```
2323

24+
* Fix watch mode with `NODE_PATH` ([#3062](https://github.com/evanw/esbuild/issues/3062))
25+
26+
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.
27+
2428
## 0.17.16
2529

2630
* Fix CSS nesting transform for triple-nested rules that start with a combinator ([#3046](https://github.com/evanw/esbuild/issues/3046))

internal/fs/fs_real.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,10 @@ func (fs *realFS) ReadFile(path string) (contents string, canonicalError error,
212212
data, ok := fs.watchData[path]
213213
if canonicalError != nil {
214214
data.state = stateFileMissing
215-
} else if !ok {
215+
} else if !ok || data.state == stateDirUnreadable {
216+
// Note: If "ReadDirectory" is called before "ReadFile" with this same
217+
// path, then "data.state" will be "stateDirUnreadable". In that case
218+
// we want to transition to "stateFileNeedModKey" because it's a file.
216219
data.state = stateFileNeedModKey
217220
}
218221
data.fileContents = fileContents

scripts/js-api-tests.js

+48
Original file line numberDiff line numberDiff line change
@@ -4062,6 +4062,54 @@ let watchTests = {
40624062
await context.dispose()
40634063
}
40644064
},
4065+
4066+
// See: https://github.com/evanw/esbuild/issues/3062
4067+
async watchNodePaths({ esbuild, testDir }) {
4068+
const input = path.join(testDir, 'in.js')
4069+
const outfile = path.join(testDir, 'out.js')
4070+
const libDir = path.join(testDir, 'lib')
4071+
const libFile = path.join(libDir, 'foo.js')
4072+
await mkdirAsync(libDir, { recursive: true })
4073+
await writeFileAsync(input, `
4074+
import { foo } from ${JSON.stringify(path.basename(libFile))}
4075+
console.log(foo)
4076+
`)
4077+
4078+
const { rebuildUntil, plugin } = makeRebuildUntilPlugin()
4079+
const context = await esbuild.context({
4080+
entryPoints: [input],
4081+
outfile,
4082+
write: false,
4083+
bundle: true,
4084+
minifyWhitespace: true,
4085+
format: 'esm',
4086+
logLevel: 'silent',
4087+
plugins: [plugin],
4088+
nodePaths: [libDir],
4089+
})
4090+
4091+
try {
4092+
const result = await rebuildUntil(
4093+
() => {
4094+
context.watch()
4095+
writeFileAtomic(libFile, `export let foo = 0`)
4096+
},
4097+
result => result.outputFiles.length === 1,
4098+
)
4099+
assert.strictEqual(result.outputFiles[0].text, `var foo=0;console.log(foo);\n`)
4100+
4101+
// Make sure watch mode works for files imported via NODE_PATH
4102+
for (let i = 1; i <= 3; i++) {
4103+
const result2 = await rebuildUntil(
4104+
() => writeFileAtomic(libFile, `export let foo = ${i}`),
4105+
result => result.outputFiles.length === 1 && result.outputFiles[0].text.includes(i),
4106+
)
4107+
assert.strictEqual(result2.outputFiles[0].text, `var foo=${i};console.log(foo);\n`)
4108+
}
4109+
} finally {
4110+
await context.dispose()
4111+
}
4112+
},
40654113
}
40664114

40674115
let serveTests = {

0 commit comments

Comments
 (0)