fix(config): guard against duplicate __dirname/__filename injection - #1772
fix(config): guard against duplicate __dirname/__filename injection#1772james-elicx wants to merge 2 commits into
Conversation
cjsGlobalsInjectorPlugin unconditionally prepended const __dirname / const __filename without checking if the user had already declared them. A config file with its own ESM polyfill: const __dirname = dirname(fileURLToPath(import.meta.url)) would receive a duplicate declaration that Rolldown rejects with a parse error. Fix: check for existing const/let/var declarations of each identifier before emitting the preamble line. Tests: two new cases in next-config.test.ts covering the duplicate-declaration scenarios. Extracted from #1749 by @Divkix. Co-authored-by: Divanshu Chauhan <divkix@divkix.me>
commit: |
|
/bigbonk review for issues |
|
|
/bigbonk review for issues |
|
@james-elicx Bonk workflow was cancelled. View workflow run · To retry, trigger Bonk again. |
There was a problem hiding this comment.
Review: guard against duplicate __dirname/__filename injection
The core fix is correct, minimal, and well-tested. The two regression tests directly exercise the duplicate-declaration crash path and pass (169/169 in tests/next-config.test.ts). Approving the approach.
A few observations, none blocking:
1. require has the same latent collision, but is unguarded
The preamble also unconditionally injects const require = __vinextCreateRequire(...):
`const require = __vinextCreateRequire(${requireBaseLiteral});\n` +A config that declares its own require (e.g. const require = createRequire(import.meta.url) — a very common ESM polyfill, even more common than the __dirname one) hits the exact same duplicate-const Rolldown parse error this PR fixes. The __dirname/__filename polyfill and the createRequire polyfill almost always appear together in real-world ESM configs, so a user who triggers one is likely to trigger the other.
Since this PR scopes itself to __dirname/__filename, it's fine to leave require for a follow-up, but it's worth a tracking note — the fix as-is is incomplete for the broader "user wrote their own ESM polyfills" case it's motivated by. Consider extending the same guard:
const hasOwnRequire = /\b(?:const|let|var)\s+require\b/.test(code);
// ...
(hasOwnRequire ? "" : `const require = __vinextCreateRequire(${requireBaseLiteral});\n`) +2. Regex matches inside strings/comments (acceptable here)
/\b(?:const|let|var)\s+__dirname\b/ will false-positive on __dirname appearing inside a string or comment, e.g. export default { x: "const __dirname" }. In that case the guard would suppress injection even though the user never actually declared the binding, and a real __dirname reference elsewhere would then be undefined at runtime.
This is an extremely unlikely edge case and the existing referencesCjsGlobals/reassignsModuleExports heuristics in this file already accept the same class of string/comment false-positives (documented as "false positives are harmless"). So this matches the file's existing precision tradeoff — just flagging that the new guard is slightly less safe than those (a false positive here causes a missing binding, not just an unused one). Fine to keep given how contrived the trigger is.
3. Test for __dirname doesn't assert the value is correct
The __filename test correctly asserts .endsWith("next.config.ts"), proving the user's declaration won. The __dirname test only checks typeof === "string" and length > 0 — which would also pass if the injected shim value were used. Consider asserting dir matches tmpDir (or that it ends with the temp dir basename) to prove the user's declaration was actually preserved rather than shadowed. Minor — the __filename test already covers the "user declaration wins" semantics.
Verdict: LGTM as a focused fix. The require gap (#1) is the only thing I'd recommend addressing, ideally in this PR or an immediate follow-up, since it's the same bug class and the same real-world configs trigger it.
|
Review posted to PR #1772. SummaryThe fix is correct, minimal, and well-tested — 169/169 tests pass. I posted a review with three observations:
Verdict: LGTM as a focused fix. The |
Address /bigbonk review on #1772: - Extend the duplicate-declaration guard to `require`. The createRequire ESM polyfill (const require = createRequire(import.meta.url)) commonly appears alongside the __dirname/__filename polyfills and hits the same duplicate-const Rolldown parse error. Skip the createRequire import and the const require line when the user already declares require. - Strengthen the __dirname regression test to assert the resolved value matches the config directory (realpathSync on both sides for the macOS /var symlink), proving the user's declaration wins over the shim. - Add a require regression test covering the same duplicate-declaration scenario. Co-authored-by: Divanshu Chauhan <divkix@divkix.me>
|
Addressed the /bigbonk review in e2483f8:
All 170 |
|
/bigbonk review for issues |
|
All checks pass. I've verified the SummaryThe changes addressing the review are complete and verified: 1.
2. Regex string/comment matches — Left as-is
3.
Verification:
The fix now fully covers the "user wrote their own ESM polyfills" case the PR is motivated by — |
Extracted from #1749 (by @Divkix) as a standalone, focused fix. Closes #1345.
Problem
cjsGlobalsInjectorPlugin(packages/vinext/src/config/next-config.ts) unconditionally prependedconst __dirname = ...andconst __filename = ...without checking if the user had already declared them. A config file using a common ESM polyfill pattern:would receive a second
const __dirnamedeclaration, which Rolldown rejects with a duplicate-declaration parse error, crashing the build.Fix
Before emitting each preamble line, check for an existing declaration:
Only inject
const __dirname/const __filenamewhen the user hasn't already declared them.Tests
Two new regression tests in
tests/next-config.test.ts:const __dirname = dirname(fileURLToPath(import.meta.url))— verifies no duplicate-declaration error.const __filename = fileURLToPath(import.meta.url)— same.All 169
next-config.test.tstests pass;vp checkis clean.Credit
This change is extracted from #1749 by @Divkix, which bundled this guard (Bug A) together with a separate node_modules CJS-globals shim (Bug B). This PR isolates the config-file duplicate-declaration guard so it can be reviewed and merged independently. Full credit to @Divkix for the original fix.