Fix require.cache/extensions missing in bundle-served CJS modules#53
Merged
Conversation
f6dae35 to
009f49a
Compare
When a load hook supplies `source` for a CommonJS module, Node's ESM->CJS translator (loadCJSModule) hands the module a re-invented require() that sets only `.resolve`/`.main` and omits `.cache` and `.extensions` (node:59666) -- unlike the require a disk-loaded CJS module gets. bundle=load is the only mode that serves CJS via a source override, so packages that read those properties (e.g. import-fresh, which does `require.cache[require.resolve(id)]`) crashed with "Cannot read properties of undefined" only under bundle=load. Repair the two properties from node:module before user code runs, via a single-line prelude prepended to the served CJS source. To keep a `'use strict'` directive in effect, the prelude is inserted after the module's Directive Prologue and prefixed with `;`; it adds no line break so stack traces / source maps keep their line numbering. Telling a directive apart from the head of a leading string expression is the ASI problem, so the prologue is walked by a small scanner (whitespace, line/block comments, and string-literal directive STATEMENTS). A leading string is a directive only when a real statement boundary follows it: `;`, `}`, a line comment, EOF, or a LINE boundary -- a line terminator, or a block comment that contains one -- whose next token does not continue the expression (punctuators like `.`/`+`/`,` or the `in`/`instanceof` keyword operators). So a module that opens with a string expression -- `'x'.toUpperCase()`, `'a' + b`, `'a,b'` newline-then-`.split()`, `'x'` newline-then-`instanceof Y` -- is left intact instead of being spliced mid-expression (a SyntaxError or silently wrong bytes), and a real directive followed by a multi-line banner comment still keeps strict mode. The load hook keys the repair off a named CJS_FORMATS set, consistent with the file's other format sets. Tests: - a bundle-served CJS module now sees require.cache / require.extensions that are the real Module._cache/_extensions; - the import-fresh-style `delete require.cache[...]; require(...)` pattern completes under bundle=load; - directive-prologue edge cases: a directive behind a leading comment stays strict, a comment-trailed directive loads without crashing, single-line and multi-line leading string expressions (including `.`/`in` continuations) are left intact, and a directive followed by a multi-line block comment stays strict; - an end-to-end test exercises the real import-fresh@3.3.1 package (vendored with its resolve-from / parent-module / callsites deps under the fixture's node_modules), reproducing identical output from a bundle with node_modules removed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GPC57FRzjwxtfqJGP69Bsk
009f49a to
ebde842
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes a Node.js compatibility issue (node:59666) where CommonJS modules served from bundles via
--bundle=loadreceive a re-inventedrequire()function that lacks the.cacheand.extensionsproperties. This causes crashes in packages likeimport-freshthat rely on these properties.Changes
Added
CJS_REQUIRE_REPAIRprelude: A minimal inline repair that restoresrequire.cacheandrequire.extensionsby pointing them to Node's internalModule._cacheandModule._extensionsfrom thenode:modulebuiltin.Implemented
repairCjsRequire()function: Intelligently injects the repair prelude into CommonJS source code while preserving line numbers and respecting shebangs and'use strict'directives. The prelude is inserted after any leading shebang and optional'use strict'directive to avoid demoting the directive.Applied repair in load hook: When serving CommonJS modules (both
commonjsandcommonjs-typescriptformats) via the load hook, the source is now automatically repaired before being returned to Node's ESM→CJS translator.Added comprehensive test coverage: Two new tests verify:
require()with.cacheand.extensionspropertiesimport-fresh-style pattern of deleting fromrequire.cacheand re-requiring works without crashingImplementation Details
typeofandtry/catchto gracefully degrade ifrequireis missing or the builtin module is unavailablerequirehttps://claude.ai/code/session_01GPC57FRzjwxtfqJGP69Bsk