next.js
7a7d4886 - Turbopack: don't scope hoist modules using direct eval (#100308)

Commit
19 days ago
Turbopack: don't scope hoist modules using direct eval (#100308) ### What? Turbopack scope hoisting no longer merges modules that contain a direct `eval(...)` call. Such modules are emitted as their own module factories, and the rest of the graph is still merged. ### Why? Merging puts modules into one shared scope and renames colliding top-level bindings. A direct `eval` resolves names at runtime against its lexical scope, so after a rename it can read the wrong binding. For example, `eval('local')` can return another module's `local`, or throw. ### How? - **Detection:** while building the analyzer's variable graph, each call expression is checked. If the callee (with parentheses stripped) is the unresolved global `eval` identifier, a module-wide flag is set. Nested functions, classes and IIFEs are covered. - **What doesn't count:** indirect forms leave the module mergeable. These are `(0, eval)(…)`, `globalThis.eval(…)`, `eval?.(…)`, aliases, `new eval(…)`, and a locally bound `eval`. - **Plumbing:** the flag is passed into the module analysis result, and `EcmascriptModuleAsset::is_mergeable` returns `false` when it is set. The existing rule that only ESM modules are mergeable is unchanged. - **Split modules:** a module with exports can be split into a facade plus a `<locals>` module. The `<locals>` module carries the original code but used the trait's default `is_mergeable`, which is always `true`, so it would have bypassed the opt-out. It now uses the original module's answer. ### Tests - **Analyzer unit test:** covers direct, indirect and shadowed `eval` forms. It includes sloppy-script shadowing, which strict ESM can't express. - **Snapshot `scope-hoisting/direct-eval`:** the module with a direct `eval` stays separate, while the module that only uses indirect `eval` is still merged. - **Execution test `scope-hoisting/direct-eval`:** a merge would rename the `eval` module's colliding local. With the opt-out disabled, the test fails (`"other"` instead of `"direct"`). <!-- NEXT_JS_LLM --> <!-- fleet 1ab5c143-50eb-4959-abe0-907ef30c1402 --> Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
Author
Parents
Loading