Skip to content

Commit

Permalink
feat: Move commonjs exports down for worklet files (#6316)
Browse files Browse the repository at this point in the history
## Summary

Since Reanimated Babel Plugin breaks hoisting and [newer Typescript
hoists CommonJS exports by
default](microsoft/TypeScript#57669) we
sometimes need to dehoist those exports.

## Test plan

- [x] Add unit test suite.
- [x] Runtime tests pass.
  • Loading branch information
tjzel authored Jul 23, 2024
1 parent 6029e02 commit 818ed78
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -990,6 +990,93 @@ exports[`babel plugin for file workletization doesn't workletize function outsid
}"
`;

exports[`babel plugin for file workletization moves CommonJS export to the bottom of the file 1`] = `
"var _worklet_15780239751281_init_data = {
code: "function foo_null1(){}",
location: "/dev/null",
sourceMap: "\\"mock source map\\"",
version: "x.y.z"
};
var foo = function () {
var _e = [new global.Error(), 1, -27];
var foo_null1 = function foo_null1() {};
foo_null1.__closure = {};
foo_null1.__workletHash = 15780239751281;
foo_null1.__initData = _worklet_15780239751281_init_data;
foo_null1.__stackDetails = _e;
return foo_null1;
}();
var bar = 1;
exports.foo = foo;"
`;

exports[`babel plugin for file workletization moves multiple CommonJS exports to the bottom of the file 1`] = `
"var _worklet_15780239751281_init_data = {
code: "function foo_null1(){}",
location: "/dev/null",
sourceMap: "\\"mock source map\\"",
version: "x.y.z"
};
var foo = function () {
var _e = [new global.Error(), 1, -27];
var foo_null1 = function foo_null1() {};
foo_null1.__closure = {};
foo_null1.__workletHash = 15780239751281;
foo_null1.__initData = _worklet_15780239751281_init_data;
foo_null1.__stackDetails = _e;
return foo_null1;
}();
var _worklet_552304524261_init_data = {
code: "function bar_null2(){}",
location: "/dev/null",
sourceMap: "\\"mock source map\\"",
version: "x.y.z"
};
var bar = function () {
var _e = [new global.Error(), 1, -27];
var bar_null2 = function bar_null2() {};
bar_null2.__closure = {};
bar_null2.__workletHash = 552304524261;
bar_null2.__initData = _worklet_552304524261_init_data;
bar_null2.__stackDetails = _e;
return bar_null2;
}();
var _worklet_9955902016844_init_data = {
code: "function baz_null3(){}",
location: "/dev/null",
sourceMap: "\\"mock source map\\"",
version: "x.y.z"
};
var baz = function () {
var _e = [new global.Error(), 1, -27];
var baz_null3 = function baz_null3() {};
baz_null3.__closure = {};
baz_null3.__workletHash = 9955902016844;
baz_null3.__initData = _worklet_9955902016844_init_data;
baz_null3.__stackDetails = _e;
return baz_null3;
}();
var _worklet_3271684035845_init_data = {
code: "function foobar_null4(){}",
location: "/dev/null",
sourceMap: "\\"mock source map\\"",
version: "x.y.z"
};
var foobar = function () {
var _e = [new global.Error(), 1, -27];
var foobar_null4 = function foobar_null4() {};
foobar_null4.__closure = {};
foobar_null4.__workletHash = 3271684035845;
foobar_null4.__initData = _worklet_3271684035845_init_data;
foobar_null4.__stackDetails = _e;
return foobar_null4;
}();
exports.foo = foo;
exports.bar = bar;
exports.baz = baz;
exports.foobar = foobar;"
`;

exports[`babel plugin for file workletization workletizes ArrowFunctionExpression 1`] = `
"var _worklet_2420910284744_init_data = {
code: "function null1(){return'bar';}",
Expand Down
30 changes: 30 additions & 0 deletions packages/react-native-reanimated/__tests__/plugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2391,6 +2391,36 @@ describe('babel plugin', () => {
expect(code).not.toHaveWorkletData();
expect(code).toMatchSnapshot();
});

it('moves CommonJS export to the bottom of the file', () => {
const input = html`<script>
'worklet';
exports.foo = foo;
function foo() {}
const bar = 1;
</script>`;

const { code } = runPlugin(input);
expect(code).toContain('var bar = 1;\nexports.foo = foo;');
expect(code).toMatchSnapshot();
});

it('moves multiple CommonJS exports to the bottom of the file', () => {
const input = html`<script>
'worklet';
exports.foo = foo;
exports.bar = bar;
function foo() {}
function bar() {}
function baz() {}
exports.baz = baz;
exports.foobar = foobar;
function foobar() {}
</script>`;

const { code } = runPlugin(input);
expect(code).toMatchSnapshot();
});
});

describe('for context objects', () => {
Expand Down
19 changes: 19 additions & 0 deletions packages/react-native-reanimated/plugin/build/plugin.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 33 additions & 0 deletions packages/react-native-reanimated/plugin/src/file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ import {
directive,
directiveLiteral,
identifier,
isAssignmentExpression,
isBlockStatement,
isExpressionStatement,
isIdentifier,
isMemberExpression,
isObjectProperty,
objectProperty,
returnStatement,
Expand Down Expand Up @@ -57,6 +60,7 @@ export function processIfWorkletFile(
*/
function processWorkletFile(programPath: NodePath<Program>) {
const statements = programPath.get('body');
dehoistCommonJSExports(programPath.node);
statements.forEach((statement) => {
const candidatePath = getCandidate(statement);
processWorkletizableEntity(candidatePath);
Expand Down Expand Up @@ -189,3 +193,32 @@ function appendWorkletClassMarker(classBody: ClassBody) {
classProperty(identifier('__workletClass'), booleanLiteral(true))
);
}

function dehoistCommonJSExports(program: Program) {
const statements = program.body;
let end = statements.length;
let current = 0;

while (current < end) {
const statement = statements[current];
if (!isCommonJSExport(statement)) {
current++;
continue;
}
const exportStatement = statements.splice(current, 1);
statements.push(...exportStatement);
// We just removed one element from non-processed part,
// so we need to decrement the end index but not the current index.
end--;
}
}

function isCommonJSExport(statement: Statement) {
return (
isExpressionStatement(statement) &&
isAssignmentExpression(statement.expression) &&
isMemberExpression(statement.expression.left) &&
isIdentifier(statement.expression.left.object) &&
statement.expression.left.object.name === 'exports'
);
}

0 comments on commit 818ed78

Please sign in to comment.