fix(config): avoid duplicate CJS global shims - #1771
Conversation
commit: |
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. Add a regression test covering the scenario.
|
Pushed bc4cd50 to this branch: extended the same duplicate-declaration guard to |
There was a problem hiding this comment.
LGTM. The change extends the existing duplicate-declaration guard to require in addition to __dirname/__filename, which is exactly the right scope for bug A of #1345. The createRequire import and the const require = ... preamble line are gated on the same hasOwnRequire flag, so the alias-based import (__vinextCreateRequire) never collides with a user-supplied import { createRequire }. Verified all 170 tests in tests/next-config.test.ts pass locally.
A few non-blocking observations below.
| const requireBaseLiteral = JSON.stringify(path.join(dirname, "package.json")); | ||
| const hasOwnDirname = /\b(?:const|let|var)\s+__dirname\b/.test(code); | ||
| const hasOwnFilename = /\b(?:const|let|var)\s+__filename\b/.test(code); | ||
| const hasOwnRequire = /\b(?:const|let|var)\s+require\b/.test(code); |
There was a problem hiding this comment.
The three hasOwn* regexes use \b(?:const|let|var)\s+IDENT\b, which is consistent with the conservative-heuristic style already used by referencesCjsGlobals / reassignsModuleExports. Two known gaps (both acceptable here, since they don't trigger the duplicate-const crash this PR targets):
- Destructured declarations (
const { createRequire } = ...,const [require] = ...) won't match. function require() {}/function __dirname() {}won't match.
These are the same edge cases the surrounding heuristics already accept, so no change requested — just noting it for the record.
| ); | ||
|
|
||
| const config = await loadNextConfig(tmpDir); | ||
| expect(config?.env?.HAS_REQUIRE).toBe("function"); |
There was a problem hiding this comment.
Each new test covers one identifier in isolation, but the real-world failure in #1345 is the combined polyfill block where createRequire, __filename, and __dirname are all declared together (the canonical ESM CJS-shim preamble). Consider adding one test that declares all three at once to lock in the exact reproduction from the issue:
it("does not inject any CJS shim when the user declares the full polyfill block", async () => {
tmpDir = makeTempDir();
fs.writeFileSync(
path.join(tmpDir, "next.config.ts"),
`import { createRequire } from "node:module";\n` +
`import { dirname } from "node:path";\n` +
`import { fileURLToPath } from "node:url";\n` +
`const __filename = fileURLToPath(import.meta.url);\n` +
`const __dirname = dirname(__filename);\n` +
`const require = createRequire(import.meta.url);\n` +
`export default { env: { DIR: __dirname, HAS_REQUIRE: typeof require } };\n`,
);
const config = await loadNextConfig(tmpDir);
expect(fs.realpathSync(config?.env?.DIR as string)).toBe(fs.realpathSync(tmpDir));
expect(config?.env?.HAS_REQUIRE).toBe("function");
});|
Review submitted successfully. Here's my assessment of PR #1771: SummaryApproved. The change is well-scoped, correct, and consistent with the codebase's existing patterns. What the PR doesExtends the duplicate-declaration guard in Correctness verification
Non-blocking feedback posted inline
|
Description
Fixes bug A from #1345 by skipping vinext's injected next.config.ts
__dirname/__filenameshims when the config already declares those identifiers. This intentionally excludes the separate node_modules runtime shim work for bug B.Related Issue
Part of #1345
Potential Risk & Impact
Low. Scope is limited to next.config CJS global injection and preserves existing shim behavior when the identifiers are not user-declared.
How Has This Been Tested?
vp test run tests/next-config.test.ts -t "does not inject"vp test run tests/next-config.test.tsvp checkvp test(7145 passed, 4 skipped)