-
Notifications
You must be signed in to change notification settings - Fork 4.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(metro-config): Remove extraneous Babel pass #25930
Conversation
68856a1
to
f9cdacd
Compare
f9cdacd
to
84cbeb9
Compare
As per the removed code comment, we couldn't remove even an empty re-traversal of the AST because `cloneInputAst: true` on it was needed. This is essentially just a deep structural clone of the AST and does nothing special. This was basically causing the traverse cache to be ignored, since all references of the AST would be swapped out. The Babel traverse cache keeps track of paths and scopes, and re-uses them to avoid having to re-crawl the AST. Hence, the `cloneInputAst: true` was needed to clear this cache. An alternative to this is: require('@babel/core').traverse.cache.clear(); However, this was only needed because the `constantFoldingPlugin` removes unused declarations. This was breaking our output since the `@babel/plugin-transform-modules-commonjs` wasn't creating reference paths to its CommonJS helpers. This in turn is a bug in Babel. All calls to `hub.addHelper` add declarations, but don't actually add references to the identifiers it generates. This means that `constantFoldingPlugin` only works properly if we update the scope of the AST by re-crawling it, i.e.: programPath.scope.crawl(); Which is likely cheaper than either cloning the AST or wiping out the entire traversal cache.
144cd00
to
f11ae49
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Finally had time to dig through this extensively today, genius work here.
Hi there! 👋 I'm a bot whose goal is to ensure your contributions meet our guidelines. I've found some issues in your pull request that should be addressed (click on them for more details) 👇
|
Closes ENG-10870
Why
We want to remove an extraneous Babel pass in the
metro-transform-worker
, as a follow-up task to #25833. The relevant todo is in the worker itself:expo/packages/@expo/metro-config/src/transform-worker/metro-transform-worker.ts
Lines 222 to 223 in b80decc
Previously, we couldn't remove this pass as without it
dev: false
builds would be missing_interopRequireWildcard
and_interopRequireDefault
Babel helper inline functions.However, removing it gets rid of an unnecessary
transformFromAstSync
call which may run without plugins and clones the entire Babel Input AST.How
Investigating the issue, it turns out that Babel has a cache for AST nodes that keeps track of previously created
path
andscope
objects. This is kept on@babel/traverse
'straverse.cache
.The
cloneInputAst: true
option was needed on at least onetransformFromAstSync
because it busts this path/scope cache. It internally performs a simple deep-clone of the AST and means that the next traverse call doesn't have a path/scope cache to fall back on. This will cause Babel to recrawl the scope of an AST.This basically means that this problem has two sides to it, which work together to cause the bug that removing
cloneInputAst: true
triggers:babel-transformer
is corrupting the scope early on (as part of@react-native/babel-preset
)transformFromAstSync
passes is corrupting the AST due to the corrupted scope.In our case, the Babel plugin corrupting the scope is
@babel/plugin-transform-modules-commonjs
. This plugin useshub.addHelper("interopRequireWildcard")
and similar calls that inject Babel helpers. In most cases, these calls tohub.addHelper
don't update anyreferencePaths
and thus the function declarations it injects are un-referenced from the perspective of Babel’s scope.The Babel plugin corrupting the AST and removing the above function declarations is Metro’s
constantFoldingPlugin
, which is only used whendev: true
is set. This plugin, among other things, removes unused declarations. When it sees the inserted function declarations with the corrupted scope, it removes them.There are several ways to ensure that the scope is correct after the initial Babel plugins are run, either:
hub.addHelper
(or inserts declaration without letting traversal continue) must update the scope manually.cloneInputAst: true
on theconstantFoldingPlugin
instead, or the via adeepClone
utility.cloneInputAst: true
to thedev: true
pass.traverse.cache.clear()
may be called to clear the entire traversal cacheprogramPath.scope.crawl()
can be called in or before theconstantFoldingPlugin
to make sure it always has an up-to-date scope.This PR, when applied, uses the last solution in the above list.
It also disables
cloneInputAst: true
on all transform passes, since only theconstantFoldingPlugin
uses the scope to alter the AST in a way that could break the output code.Test Plan
metro-transform-worker
that uses theconstantFoldingPlugin
and checks the output for code that will generate a_interopRequireWildcard
declarationconstantFoldingPlugin
again without having to run E2E testscloneInputAst
everywhere and then selectively stepping through code. RemovingconstantFoldingPlugin
also fixes the output.traverse.cache.clear()
in conjunction with other changesChecklist
npx expo prebuild
& EAS Build (eg: updated a module plugin).