Skip to content

Commit

Permalink
fix #3062: watch mode with NODE_PATH edge case
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Apr 16, 2023
1 parent a4e19a7 commit ecb3a89
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 1 deletion.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Expand Up @@ -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))
Expand Down
5 changes: 4 additions & 1 deletion internal/fs/fs_real.go
Expand Up @@ -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
Expand Down
48 changes: 48 additions & 0 deletions scripts/js-api-tests.js
Expand Up @@ -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 = {
Expand Down

0 comments on commit ecb3a89

Please sign in to comment.