Metro strips imports when import references are transformed with variable shadowing
I think I encountered a bug in Metro regarding transformation of module imports.
In Reanimated (Worklets) Babel plugin I'm transforming the following code:
import {foo} from './file.js';
function bar(){
foo();
};
to
import {foo} from './file.js';
const bar = function factory(foo){
return function bar(){
foo();
};
}(foo);
Basically I'm replacing a FunctionDeclaration node with a VariableDeclaration node. After the replacement I make sure to recrawl the scope so foo in the arguments of factory would be correctly bound to foo from the import.
After these transformations Metro strips the import totally and then foo is not defined and file.js is not loaded into the bundle. Relevant part of Metro output below:
__d(function (global, _$$_REQUIRE, _$$_IMPORT_DEFAULT, _$$_IMPORT_ALL, module, exports, _dependencyMap) {
var bar = function factory(foo) {
var bar = function bar() {
foo();
console.log('bar');
};
return bar;
}(foo);
},0,[],"filefile.js");
As you can see, foo isn't defined anywhere. Calling visit on the ancestor Program node (or any other node) doesn't fix it.
This doesn't happen when I put the already transformed file into Metro.
The reason why I'm quite positive this is a bug is due to fact that renaming foo actually makes the import to be properly transformed. If I transform the original file to:
(note _foo here)
import {foo} from './file.js';
const bar = function factory(_foo){
return function bar(){
_foo();
};
}(foo);
The import is transformed properly, and the Metro output is:
__d(function (global, _$$_REQUIRE, _$$_IMPORT_DEFAULT, _$$_IMPORT_ALL, module, exports, _dependencyMap) {
var bar = function factory(_foo) {
var bar = function bar() {
_foo();
};
return bar;
}(_$$_REQUIRE(_dependencyMap[0], "./file.js").foo);
},0,[1],"filefile.js");
Interesting! Thanks for the report - would you be able to share (a minimal version of) your Babel plugin, so we can repro/add a test case?
@robhogan Here's the minimalistic version of the plugin: https://github.com/software-mansion/react-native-reanimated/tree/%40tjzel/metro-bug/packages/react-native-worklets/plugin.
The plugin code is in index.js and the sources to look up are in src.
With this minimalistic repro I managed to get an even worse transformation, note that JavaScript is totally malformed in the line that defines function Factory. I remember running into that problem before (when analyzing this problem) and I somehow fixed it, but I don't remember how at the moment.
__d(function (global, _$$_REQUIRE, _$$_IMPORT_DEFAULT, _$$_IMPORT_ALL, module, exports, _dependencyMap) {
var bar = function Factory(_$$_REQUIRE(_dependencyMap[0], "./file.js").foo) {
var bar = function bar() {
foo();
};
return bar;
}(_$$_REQUIRE(_dependencyMap[0], "./file.js").foo);
},0,[1],"filefile.js");
Hey @robhogan, after handling some issues I managed to discover this is an issue on both sides. Turns out that I wrongfully reused the same Babel Nodes (identifier nodes)
It seems that when Metro replaces an identifier with _$$_REQUIRE it mutates the node itself, thus changing it in all the places when it is referenced. I believe that actually it should instead replace the node, i.e. by invoking nodePath.replace(...).