Install globalEvalWithSourceUrl on bridgeless ReactInstance - #58718
abzokhattab wants to merge 3 commits into
Conversation
Bridge mode already exposes this helper from JSIExecutor so Metro debug loaders can evaluate fetched JS via JSI. Hermes rejects JS eval() of Metro __d(...) source, so lazy chunks fail on New Architecture.
|
Hi @abzokhattab! Thank you for your pull request and welcome to our community. Action RequiredIn order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you. ProcessIn order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA. Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with If you have received this in error or have any questions, please contact us at cla@meta.com. Thanks! |
|
Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks! |
|
@fabriziocucci has imported this pull request. If you are a Meta employee, you can view this in D122329977. |
|
I don't think this addresses your problem. "Parsing source code unsupported" comes from Hermes and means the engine was built without eval support (HERMESVM_LEAN) |
|
Thanks for taking a look. I agree that message is Hermes rejecting runtime parse (no compiler /
typeof global.globalEvalWithSourceUrl
// bridgeless: "undefined"
// bridge: "function"If this VM were fully unable to compile source, the main Metro bundle would fail the same way. It does not. The gap is only that split-bundle loaders fall back to JS Happy to add a more targeted test if you want (call the helper with a small |
|
Please add a test (eg fantom?) documenting the difference in behaviour between eval and the globalEval |
Document that the helper is installed on the New Architecture runtime and can evaluate Metro-shaped source, including how that compares to JS eval().
|
Added the following test:
The first assertion is the bridgeless gap (helper missing → fail; installed → pass). |
| try { | ||
| // eslint-disable-next-line no-eval | ||
| eval(source); | ||
| } catch (e) { | ||
| evalError = e; | ||
| } |
There was a problem hiding this comment.
There's no assertions about the result of the eval.
Put the assert on evalError inline in the catch - although fantom tests should never run in Lean mode...
There was a problem hiding this comment.
Agreed, that version only exercised the helper. Reworked in c19158b:
Fantom (globalEvalWithSourceUrl-itest.js), no try/catch or lean branch anymore:
eval(source)and the helper both return17and set the same global.- An error thrown from the helper has the given source URL in its stack; the
eval()one does not. - Wrong arity throws.
C++ (ReactInstanceTest.cpp), which is where the behavioural difference can be forced deterministically: the fixture now takes a RuntimeConfig, and ReactInstanceWithoutEvalTest builds the runtime with withEnableEval(false). There eval('1 + 2') throws Parsing source code unsupported while globalEvalWithSourceUrl('1 + 2', 'chunk.js') returns 3. The default fixture asserts both return 3.
On the lean point: looking at lib/VM/JSLib/eval.cpp and API/hermes/hermes.cpp, raiseEvalUnsupported is reached from eval() when RuntimeConfig::EnableEval is false (and from the lean ifdef on legacy Hermes), but Runtime::evaluateJavaScript has no EnableEval gate. In the case I hit the main bundle was plain Metro source and loaded fine, so the compiler was present and the gate that fired was EnableEval, not a lean build. Either way the helper reaches the path the main bundle already uses. Updated the PR description to say that precisely.
The Fantom test now asserts eval()'s result and the source URL attribution difference. The C++ test builds a runtime with RuntimeConfig::EnableEval=false to show eval() throws while globalEvalWithSourceUrl still evaluates source.
Summary:
Problem: On the New Architecture (bridgeless), fetching and evaluating a Metro split bundle in development (
lazy=true,import(),React.lazy()) can fail with:The main bundle still loads.
Root cause:
global.globalEvalWithSourceUrlis never installed on bridgeless. Bridge mode installs it inJSIExecutor::initializeRuntime.The debug loader (
Libraries/Core/Devtools/loadBundleFromServer.js) prefers that helper and only falls back to JSeval()when it is missing. The two paths are not equivalent in Hermes:eval()/Functiongo throughevalInEnvironment, which raisesParsing source code unsupportedwhenRuntimeConfig::EnableEvalis false (and, on legacy Hermes, when the engine is built lean).globalEvalWithSourceUrlgoes throughRuntime::evaluateJavaScript, the same entry point used to run the main bundle. It is not gated byEnableEval, and it attaches the given source URL to stack traces.So on a runtime where
EnableEvalis off (or the engine is lean), the main bundle loads but every split bundle falls intoeval()and throws. Bridgeless has no way to reach the JSI path from JS today.Fix: Port the existing
JSIExecutorbinding intoReactInstance::initializeRuntimeso bridgeless exposes the same helper.Changelog:
[General] [Fixed] - Install globalEvalWithSourceUrl on bridgeless ReactInstance so debug bundle loaders can evaluate split bundles via JSI instead of eval()
Test Plan:
ReactInstanceTest.testGlobalEvalWithSourceUrlIsInstalled: helper is absent beforeinitializeRuntimeand present after.ReactInstanceTest.testGlobalEvalWithSourceUrlMatchesEvalWhenEvalIsEnabled: with the defaultRuntimeConfig,eval('1 + 2')andglobalEvalWithSourceUrl('1 + 2', 'chunk.js')both return3.ReactInstanceWithoutEvalTest.testGlobalEvalWithSourceUrlWorksWhenEvalIsDisabled: runtime built withRuntimeConfig::Builder().withEnableEval(false);eval('1 + 2')throwsParsing source code unsupported,globalEvalWithSourceUrl('1 + 2', 'chunk.js')returns3.Libraries/Core/Devtools/__tests__/globalEvalWithSourceUrl-itest.js: helper is installed on bridgeless;eval()and the helper evaluate the same source to the same result; an error thrown from the helper carries the given source URL in its stack while theeval()one does not; wrong arity throws.typeof global.globalEvalWithSourceUrlis'function'and aReact.lazy(() => import('./SomeModule'))split bundle loads withoutParsing source code unsupported.