Skip to content

Commit 9e16358

Browse files
Kikobeatsclaude
andauthored
fix(compile): check nodePaths directly instead of require.resolve (#66)
require.resolve({ paths }) walks up parent directories, finding packages in ancestor node_modules that esbuild's nodePaths cannot reach. This skipped installs for deps like cheerio, causing "Could not resolve" build failures in production. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 683e33c commit 9e16358

2 files changed

Lines changed: 42 additions & 6 deletions

File tree

src/compile/index.js

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,14 @@ module.exports = async (snippet, { tmpdir = DEFAULT_TMPDIR, allow = {}, nodePath
3434
? allDependencies.filter(dep => {
3535
const name = installDependencies.extractPackageName(dep)
3636
const version = dep.slice(name.length + 1)
37-
try {
38-
const pkgPath = require.resolve(path.join(name, 'package.json'), { paths: nodePaths })
39-
if (version === 'latest') return false
40-
return JSON.parse(readFileSync(pkgPath, 'utf8')).version !== version
41-
} catch {
42-
return true
37+
for (const np of nodePaths) {
38+
try {
39+
const pkg = JSON.parse(readFileSync(path.join(np, name, 'package.json'), 'utf8'))
40+
if (version === 'latest') return false
41+
return pkg.version !== version
42+
} catch {}
4343
}
44+
return true
4445
})
4546
: allDependencies
4647

test/index.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,41 @@ test('resolve require dependencies', async t => {
7171
t.is(await run(fn('foo')), false)
7272
})
7373

74+
test('install dependencies not directly in nodePaths', async t => {
75+
const path = require('path')
76+
const { mkdirSync, writeFileSync, rmSync } = require('fs')
77+
78+
// Simulate production: a nodePaths dir deep in node_modules
79+
// with a dependency (cheerio) only in a *parent* node_modules
80+
const testDir = path.join(require('os').tmpdir(), `isolated-fn-nodepaths-${Date.now()}`)
81+
const nodePathDir = path.join(testDir, 'deep', 'nested', 'node_modules')
82+
mkdirSync(nodePathDir, { recursive: true })
83+
84+
// Place a fake package in a parent directory's node_modules.
85+
// require.resolve({ paths }) would find it via traversal,
86+
// but esbuild's nodePaths does not traverse parents.
87+
const parentPkgDir = path.join(testDir, 'node_modules', 'is-standard-emoji')
88+
mkdirSync(parentPkgDir, { recursive: true })
89+
writeFileSync(
90+
path.join(parentPkgDir, 'package.json'),
91+
'{"name":"is-standard-emoji","version":"1.0.0"}'
92+
)
93+
writeFileSync(path.join(parentPkgDir, 'index.js'), 'module.exports = () => false')
94+
95+
const instance = require('..')({ nodePaths: [nodePathDir] })
96+
const fn = instance(emoji => {
97+
const isEmoji = require('is-standard-emoji@1.0.0')
98+
return isEmoji(emoji)
99+
})
100+
101+
// If the bug is present, the dep would be skipped (found via parent traversal)
102+
// and esbuild would use the fake parent version returning false.
103+
// With the fix, the dep is installed fresh into tmpdir and works correctly.
104+
t.is(await run(fn('🙌')), true)
105+
106+
rmSync(testDir, { recursive: true, force: true })
107+
})
108+
74109
test('runs async code', async t => {
75110
const fn = isolatedFunction(async duration => {
76111
const delay = ms => new Promise(resolve => setTimeout(resolve, ms))

0 commit comments

Comments
 (0)