fix(compile): namespace import of CJS default re-export resolves member value (#4841)#4874
Merged
Merged
Conversation
…er value (#4841) `import * as ns from "<cjs-pkg>"` where a namespace member is a re-export of a CommonJS submodule's `default` (`var sfy = require('./sfy'); module.exports = { sfy }`, with `./sfy` being `module.exports = function`) resolved the member to a wrapper closure instead of the underlying value. The submodule records its export under the synthetic `"default"` suffix, not the consumer-visible member name. The namespace-import var-vs-function classifier keyed only on `(origin_path, member)`, missed the `(origin_path, "default")` entry, and classified the member as a FUNCTION. That routed `ns.sfy` through the singleton-closure wrap of the default getter, so `ns.sfy(args)` RETURNED the function value rather than being it (`typeof ns.sfy(...) === "function"`, length 0). This was the root cause of the Stripe SDK failure: Stripe's `utils.js` does `import * as qs from 'qs'` and `qs.stringify(data, opts).replace(/%5B/g, '[')`. `qs.stringify(...)` returned the qs stringify *function* (not a string), so `.replace` was undefined → `TypeError: replace is not a function` on every request. Verified on Linux: `stripe.products.create({ name })` now builds and dispatches the request (reaches Stripe's API) instead of throwing. Fix: the namespace arm now probes both `(origin_path, member)` and `(origin_path, origin_name)` against `exported_var_names`, mirroring the existing named-import arm. Adds an e2e regression test.
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 #4841 — Stripe SDK
stripe.products.create({ name })threwTypeError: replace is not a functionon every request.Root cause
import * as qs from "qs"thenqs.stringify(data, opts).replace(/%5B/g, "[")(Stripe'sutils.js).qs.stringify(...)returned the qs stringify function instead of a string, so.replacewas undefined.The real bug is in
import * as nsnamespace-member resolution.qs/lib/index.jsis CommonJS:The submodule records its export under the synthetic
"default"suffix, not the consumer-visible member namestringify. The namespace-import var-vs-function classifier keyed only on(origin_path, "stringify"), missed the(origin_path, "default")entry, and classifiedstringifyas a function. That routedns.stringifythrough the singleton-closure wrap of the default getter, sons.stringify(args)returned the function value instead of being it (typeof ns.stringify(...) === "function",.length === 0).The default import (
import qs from "qs"; qs.stringify) worked because it readsmodule.exports.stringifydirectly — only theimport * asnamespace path was broken.Minimal repro:
Fix
The namespace arm in
compile.rsnow probes both(origin_path, member)and(origin_path, origin_name)againstexported_var_names, mirroring the existing named-import arm (theorigin_key_under_origin_namedual-probe). With the member correctly classified as a var,ns.stringifyreads the getter's value.One-arm change + an e2e regression test.
Verification
Built on Linux x86_64 (where the bug reproduced) and ran the real Stripe SDK repro:
ERR: TypeError replace is not a functionstripe.products.create({ name })builds and dispatches the request, reaching Stripe's API (the request layer is no longer broken).qs.stringify({a:1,b:"x"})→"a=1&b=x"(was the function value).The remaining "unsettled top-level await" when awaiting the real network call is a separate, pre-existing async-HTTP-settling issue (already noted in #4841 as out of scope) — identical on macOS, and unrelated to the
.replacefailure this PR fixes.Regression-checked normal namespace imports (real
function/constexports, default imports) and the existingissue_4858/native_link_cacheintegration tests — all green.