diff --git a/packages/react-native-reanimated/__tests__/__snapshots__/plugin.test.ts.snap b/packages/react-native-reanimated/__tests__/__snapshots__/plugin.test.ts.snap
index 7a20480853d..7f1bf7f7934 100644
--- a/packages/react-native-reanimated/__tests__/__snapshots__/plugin.test.ts.snap
+++ b/packages/react-native-reanimated/__tests__/__snapshots__/plugin.test.ts.snap
@@ -926,6 +926,30 @@ var animatedStyle = useAnimatedStyle(function () {
}());"
`;
+exports[`babel plugin for function hooks workletizes hook wrapped worklet reference automatically 1`] = `
+"var _worklet_377598887181_init_data = {
+ code: "function style(){return{color:'red',backgroundColor:'blue'};}",
+ location: "/dev/null",
+ sourceMap: "\\"mock source map\\"",
+ version: "x.y.z"
+};
+var style = function () {
+ const _e = [new global.Error(), 1, -27];
+ const style = function () {
+ return {
+ color: 'red',
+ backgroundColor: 'blue'
+ };
+ };
+ style.__closure = {};
+ style.__workletHash = 377598887181;
+ style.__initData = _worklet_377598887181_init_data;
+ style.__stackDetails = _e;
+ return style;
+}();
+var animatedStyle = useAnimatedStyle(style);"
+`;
+
exports[`babel plugin for generators makes a generator worklet factory 1`] = `
"var _worklet_4939499253486_init_data = {
code: "function*foo(){yield'hello';yield'world';}",
@@ -1573,64 +1597,353 @@ var foo = Gesture.Tap().numberOfTaps(2).onBegin(function () {
}());"
`;
-exports[`babel plugin for runOnUI workletizes ArrowFunctionExpression inside runOnUI automatically 1`] = `
-"var _worklet_15854903236968_init_data = {
- code: "function anonymous(){console.log('Hello from the UI thread!');}",
+exports[`babel plugin for referenced worklets prefers AssignmentExpression over VariableDeclarator 1`] = `
+"var styleFactory = function styleFactory() {
+ return 1;
+};
+var _worklet_2302253389457_init_data = {
+ code: "function styleFactory(){return'AssignmentExpression';}",
location: "/dev/null",
sourceMap: "\\"mock source map\\"",
version: "x.y.z"
};
-runOnUI(function () {
+styleFactory = function () {
+ const _e = [new global.Error(), 1, -27];
+ const styleFactory = function () {
+ return 'AssignmentExpression';
+ };
+ styleFactory.__closure = {};
+ styleFactory.__workletHash = 2302253389457;
+ styleFactory.__initData = _worklet_2302253389457_init_data;
+ styleFactory.__stackDetails = _e;
+ return styleFactory;
+}();
+animatedStyle = useAnimatedStyle(styleFactory);"
+`;
+
+exports[`babel plugin for referenced worklets prefers FunctionDeclaration over AssignmentExpression 1`] = `
+"var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
+var _readOnlyError2 = _interopRequireDefault(require("@babel/runtime/helpers/readOnlyError"));
+var _worklet_8893014081746_init_data = {
+ code: "function styleFactory(){return'FunctionDeclaration';}",
+ location: "/dev/null",
+ sourceMap: "\\"mock source map\\"",
+ version: "x.y.z"
+};
+var styleFactory = function () {
var _e = [new global.Error(), 1, -27];
- var anonymous = function anonymous() {
- console.log('Hello from the UI thread!');
+ var styleFactory = function styleFactory() {
+ return 'FunctionDeclaration';
};
- anonymous.__closure = {};
- anonymous.__workletHash = 15854903236968;
- anonymous.__initData = _worklet_15854903236968_init_data;
- anonymous.__stackDetails = _e;
- return anonymous;
-}())();"
+ styleFactory.__closure = {};
+ styleFactory.__workletHash = 8893014081746;
+ styleFactory.__initData = _worklet_8893014081746_init_data;
+ styleFactory.__stackDetails = _e;
+ return styleFactory;
+}();
+(function styleFactory() {
+ return 'AssignmentExpression';
+}), (0, _readOnlyError2.default)("styleFactory");
+animatedStyle = useAnimatedStyle(styleFactory);"
`;
-exports[`babel plugin for runOnUI workletizes named FunctionExpression inside runOnUI automatically 1`] = `
-"var _worklet_5662051517689_init_data = {
- code: "function hello(){console.log('Hello from the UI thread!');}",
+exports[`babel plugin for referenced worklets workletizes ArrowFunctionExpression on its AssignmentExpression 1`] = `
+"var styleFactory;
+var _worklet_155831007318_init_data = {
+ code: "function styleFactory(){return{};}",
location: "/dev/null",
sourceMap: "\\"mock source map\\"",
version: "x.y.z"
};
-runOnUI(function () {
+styleFactory = function () {
+ const _e = [new global.Error(), 1, -27];
+ const styleFactory = function () {
+ return {};
+ };
+ styleFactory.__closure = {};
+ styleFactory.__workletHash = 155831007318;
+ styleFactory.__initData = _worklet_155831007318_init_data;
+ styleFactory.__stackDetails = _e;
+ return styleFactory;
+}();
+animatedStyle = useAnimatedStyle(styleFactory);"
+`;
+
+exports[`babel plugin for referenced worklets workletizes ArrowFunctionExpression on its VariableDeclarator 1`] = `
+"var _worklet_155831007318_init_data = {
+ code: "function styleFactory(){return{};}",
+ location: "/dev/null",
+ sourceMap: "\\"mock source map\\"",
+ version: "x.y.z"
+};
+var styleFactory = function () {
+ const _e = [new global.Error(), 1, -27];
+ const styleFactory = function () {
+ return {};
+ };
+ styleFactory.__closure = {};
+ styleFactory.__workletHash = 155831007318;
+ styleFactory.__initData = _worklet_155831007318_init_data;
+ styleFactory.__stackDetails = _e;
+ return styleFactory;
+}();
+var animatedStyle = useAnimatedStyle(styleFactory);"
+`;
+
+exports[`babel plugin for referenced worklets workletizes ArrowFunctionExpression only on last AssignmentExpression 1`] = `
+"var styleFactory;
+styleFactory = function styleFactory() {
+ return 1;
+};
+var _worklet_2302253389457_init_data = {
+ code: "function styleFactory(){return'AssignmentExpression';}",
+ location: "/dev/null",
+ sourceMap: "\\"mock source map\\"",
+ version: "x.y.z"
+};
+styleFactory = function () {
+ const _e = [new global.Error(), 1, -27];
+ const styleFactory = function () {
+ return 'AssignmentExpression';
+ };
+ styleFactory.__closure = {};
+ styleFactory.__workletHash = 2302253389457;
+ styleFactory.__initData = _worklet_2302253389457_init_data;
+ styleFactory.__stackDetails = _e;
+ return styleFactory;
+}();
+animatedStyle = useAnimatedStyle(styleFactory);"
+`;
+
+exports[`babel plugin for referenced worklets workletizes FunctionDeclaration 1`] = `
+"var _worklet_155831007318_init_data = {
+ code: "function styleFactory(){return{};}",
+ location: "/dev/null",
+ sourceMap: "\\"mock source map\\"",
+ version: "x.y.z"
+};
+var styleFactory = function () {
var _e = [new global.Error(), 1, -27];
- var hello = function hello() {
- console.log('Hello from the UI thread!');
- };
- hello.__closure = {};
- hello.__workletHash = 5662051517689;
- hello.__initData = _worklet_5662051517689_init_data;
- hello.__stackDetails = _e;
- return hello;
-}())();"
+ var styleFactory = function styleFactory() {
+ return {};
+ };
+ styleFactory.__closure = {};
+ styleFactory.__workletHash = 155831007318;
+ styleFactory.__initData = _worklet_155831007318_init_data;
+ styleFactory.__stackDetails = _e;
+ return styleFactory;
+}();
+var animatedStyle = useAnimatedStyle(styleFactory);"
`;
-exports[`babel plugin for runOnUI workletizes unnamed FunctionExpression inside runOnUI automatically 1`] = `
-"var _worklet_15854903236968_init_data = {
- code: "function anonymous(){console.log('Hello from the UI thread!');}",
+exports[`babel plugin for referenced worklets workletizes FunctionExpression on its AssignmentExpression 1`] = `
+"var styleFactory;
+var _worklet_155831007318_init_data = {
+ code: "function styleFactory(){return{};}",
location: "/dev/null",
sourceMap: "\\"mock source map\\"",
version: "x.y.z"
};
-runOnUI(function () {
+styleFactory = function () {
+ const _e = [new global.Error(), 1, -27];
+ const styleFactory = function () {
+ return {};
+ };
+ styleFactory.__closure = {};
+ styleFactory.__workletHash = 155831007318;
+ styleFactory.__initData = _worklet_155831007318_init_data;
+ styleFactory.__stackDetails = _e;
+ return styleFactory;
+}();
+animatedStyle = useAnimatedStyle(styleFactory);"
+`;
+
+exports[`babel plugin for referenced worklets workletizes FunctionExpression on its VariableDeclarator 1`] = `
+"var _worklet_155831007318_init_data = {
+ code: "function styleFactory(){return{};}",
+ location: "/dev/null",
+ sourceMap: "\\"mock source map\\"",
+ version: "x.y.z"
+};
+var styleFactory = function () {
+ const _e = [new global.Error(), 1, -27];
+ const styleFactory = function () {
+ return {};
+ };
+ styleFactory.__closure = {};
+ styleFactory.__workletHash = 155831007318;
+ styleFactory.__initData = _worklet_155831007318_init_data;
+ styleFactory.__stackDetails = _e;
+ return styleFactory;
+}();
+var animatedStyle = useAnimatedStyle(styleFactory);"
+`;
+
+exports[`babel plugin for referenced worklets workletizes FunctionExpression only on last AssignmentExpression 1`] = `
+"var styleFactory;
+styleFactory = function styleFactory() {
+ return 1;
+};
+var _worklet_2302253389457_init_data = {
+ code: "function styleFactory(){return'AssignmentExpression';}",
+ location: "/dev/null",
+ sourceMap: "\\"mock source map\\"",
+ version: "x.y.z"
+};
+styleFactory = function () {
+ const _e = [new global.Error(), 1, -27];
+ const styleFactory = function () {
+ return 'AssignmentExpression';
+ };
+ styleFactory.__closure = {};
+ styleFactory.__workletHash = 2302253389457;
+ styleFactory.__initData = _worklet_2302253389457_init_data;
+ styleFactory.__stackDetails = _e;
+ return styleFactory;
+}();
+animatedStyle = useAnimatedStyle(styleFactory);"
+`;
+
+exports[`babel plugin for referenced worklets workletizes ObjectExpression on its AssignmentExpression 1`] = `
+"var handler;
+var _worklet_16988157363598_init_data = {
+ code: "function onScroll(){}",
+ location: "/dev/null",
+ sourceMap: "\\"mock source map\\"",
+ version: "x.y.z"
+};
+handler = {
+ onScroll: function () {
+ const _e = [new global.Error(), 1, -27];
+ const onScroll = function () {};
+ onScroll.__closure = {};
+ onScroll.__workletHash = 16988157363598;
+ onScroll.__initData = _worklet_16988157363598_init_data;
+ onScroll.__stackDetails = _e;
+ return onScroll;
+ }()
+};
+var scrollHandler = useAnimatedScrollHandler(handler);"
+`;
+
+exports[`babel plugin for referenced worklets workletizes ObjectExpression on its VariableDeclarator 1`] = `
+"var _worklet_16988157363598_init_data = {
+ code: "function onScroll(){}",
+ location: "/dev/null",
+ sourceMap: "\\"mock source map\\"",
+ version: "x.y.z"
+};
+var handler = {
+ onScroll: function () {
+ const _e = [new global.Error(), 1, -27];
+ const onScroll = function () {};
+ onScroll.__closure = {};
+ onScroll.__workletHash = 16988157363598;
+ onScroll.__initData = _worklet_16988157363598_init_data;
+ onScroll.__stackDetails = _e;
+ return onScroll;
+ }()
+};
+var scrollHandler = useAnimatedScrollHandler(handler);"
+`;
+
+exports[`babel plugin for referenced worklets workletizes ObjectExpression only on last AssignmentExpression 1`] = `
+"var handler;
+handler = {
+ onScroll: function onScroll() {
+ return 1;
+ }
+};
+var _worklet_9384619123806_init_data = {
+ code: "function onScroll(){return'AssignmentExpression';}",
+ location: "/dev/null",
+ sourceMap: "\\"mock source map\\"",
+ version: "x.y.z"
+};
+handler = {
+ onScroll: function () {
+ const _e = [new global.Error(), 1, -27];
+ const onScroll = function () {
+ return 'AssignmentExpression';
+ };
+ onScroll.__closure = {};
+ onScroll.__workletHash = 9384619123806;
+ onScroll.__initData = _worklet_9384619123806_init_data;
+ onScroll.__stackDetails = _e;
+ return onScroll;
+ }()
+};
+var scrollHandler = useAnimatedScrollHandler(handler);"
+`;
+
+exports[`babel plugin for referenced worklets workletizes assigments that appear after the worklet is used 1`] = `
+"var styleFactory = function styleFactory() {
+ return {};
+};
+animatedStyle = useAnimatedStyle(styleFactory);
+var _worklet_12348123604788_init_data = {
+ code: "function anonymous(){return'AssignmentAfterUse';}",
+ location: "/dev/null",
+ sourceMap: "\\"mock source map\\"",
+ version: "x.y.z"
+};
+styleFactory = function () {
var _e = [new global.Error(), 1, -27];
var anonymous = function anonymous() {
- console.log('Hello from the UI thread!');
+ return 'AssignmentAfterUse';
};
anonymous.__closure = {};
- anonymous.__workletHash = 15854903236968;
- anonymous.__initData = _worklet_15854903236968_init_data;
+ anonymous.__workletHash = 12348123604788;
+ anonymous.__initData = _worklet_12348123604788_init_data;
anonymous.__stackDetails = _e;
return anonymous;
-}())();"
+}();"
+`;
+
+exports[`babel plugin for referenced worklets workletizes in immediate scope 1`] = `
+"var _worklet_155831007318_init_data = {
+ code: "function styleFactory(){return{};}",
+ location: "/dev/null",
+ sourceMap: "\\"mock source map\\"",
+ version: "x.y.z"
+};
+var styleFactory = function () {
+ const _e = [new global.Error(), 1, -27];
+ const styleFactory = function () {
+ return {};
+ };
+ styleFactory.__closure = {};
+ styleFactory.__workletHash = 155831007318;
+ styleFactory.__initData = _worklet_155831007318_init_data;
+ styleFactory.__stackDetails = _e;
+ return styleFactory;
+}();
+animatedStyle = useAnimatedStyle(styleFactory);"
+`;
+
+exports[`babel plugin for referenced worklets workletizes in nested scope 1`] = `
+"var _worklet_155831007318_init_data = {
+ code: "function styleFactory(){return{};}",
+ location: "/dev/null",
+ sourceMap: "\\"mock source map\\"",
+ version: "x.y.z"
+};
+function outerScope() {
+ var styleFactory = function () {
+ const _e = [new global.Error(), 1, -27];
+ const styleFactory = function () {
+ return {};
+ };
+ styleFactory.__closure = {};
+ styleFactory.__workletHash = 155831007318;
+ styleFactory.__initData = _worklet_155831007318_init_data;
+ styleFactory.__stackDetails = _e;
+ return styleFactory;
+ }();
+ function innerScope() {
+ animatedStyle = useAnimatedStyle(styleFactory);
+ }
+}"
`;
exports[`babel plugin for sequence expressions supports SequenceExpression 1`] = `
diff --git a/packages/react-native-reanimated/__tests__/plugin.test.ts b/packages/react-native-reanimated/__tests__/plugin.test.ts
index 289f447321c..1ebb9ca33bd 100644
--- a/packages/react-native-reanimated/__tests__/plugin.test.ts
+++ b/packages/react-native-reanimated/__tests__/plugin.test.ts
@@ -246,7 +246,7 @@ describe('babel plugin', () => {
const { code, ast } = runPlugin(input, { ast: true });
let closureBindings;
- traverse(ast, {
+ traverse(ast!, {
enter(path) {
if (
path.isAssignmentExpression() &&
@@ -534,38 +534,16 @@ describe('babel plugin', () => {
expect(code).toHaveWorkletData();
expect(code).toMatchSnapshot();
});
- });
-
- describe('for runOnUI', () => {
- it('workletizes ArrowFunctionExpression inside runOnUI automatically', () => {
- const input = html``;
-
- const { code } = runPlugin(input);
- expect(code).toHaveWorkletData();
- expect(code).toMatchSnapshot();
- });
- it('workletizes unnamed FunctionExpression inside runOnUI automatically', () => {
+ it('workletizes hook wrapped worklet reference automatically', () => {
const input = html``;
-
- const { code } = runPlugin(input);
- expect(code).toHaveWorkletData();
- expect(code).toMatchSnapshot();
- });
-
- it('workletizes named FunctionExpression inside runOnUI automatically', () => {
- const input = html``;
const { code } = runPlugin(input);
@@ -1013,7 +991,7 @@ describe('babel plugin', () => {
const { code, ast } = runPlugin(input, { ast: true });
let closureBindings;
- traverse(ast, {
+ traverse(ast!, {
enter(path) {
if (
path.isAssignmentExpression() &&
@@ -1768,4 +1746,218 @@ describe('babel plugin', () => {
expect(code).toMatchSnapshot();
});
});
+
+ describe('for referenced worklets', () => {
+ it('workletizes ArrowFunctionExpression on its VariableDeclarator', () => {
+ const input = html``;
+
+ const { code } = runPlugin(input);
+ expect(code).toHaveWorkletData(1);
+ expect(code).toMatchSnapshot();
+ });
+
+ it('workletizes ArrowFunctionExpression on its AssignmentExpression', () => {
+ const input = html``;
+
+ const { code } = runPlugin(input);
+ expect(code).toHaveWorkletData(1);
+ expect(code).toMatchSnapshot();
+ });
+
+ it('workletizes ArrowFunctionExpression only on last AssignmentExpression', () => {
+ const input = html``;
+
+ const { code } = runPlugin(input);
+ expect(code).toHaveWorkletData(1);
+ expect(code).toIncludeInWorkletString('AssignmentExpression');
+ expect(code).toMatchSnapshot();
+ });
+
+ it('workletizes FunctionExpression on its VariableDeclarator', () => {
+ const input = html``;
+
+ const { code } = runPlugin(input);
+ expect(code).toHaveWorkletData(1);
+ expect(code).toMatchSnapshot();
+ });
+
+ it('workletizes FunctionExpression on its AssignmentExpression', () => {
+ const input = html``;
+
+ const { code } = runPlugin(input);
+ expect(code).toHaveWorkletData(1);
+ expect(code).toMatchSnapshot();
+ });
+
+ it('workletizes FunctionExpression only on last AssignmentExpression', () => {
+ const input = html``;
+
+ const { code } = runPlugin(input);
+ expect(code).toHaveWorkletData(1);
+ expect(code).toIncludeInWorkletString('AssignmentExpression');
+ expect(code).toMatchSnapshot();
+ });
+
+ it('workletizes FunctionDeclaration', () => {
+ const input = html``;
+
+ const { code } = runPlugin(input);
+ expect(code).toHaveWorkletData(1);
+ expect(code).toMatchSnapshot();
+ });
+
+ it('workletizes ObjectExpression on its VariableDeclarator', () => {
+ const input = html``;
+
+ const { code } = runPlugin(input);
+ expect(code).toHaveWorkletData(1);
+ expect(code).toMatchSnapshot();
+ });
+
+ it('workletizes ObjectExpression on its AssignmentExpression', () => {
+ const input = html``;
+
+ const { code } = runPlugin(input);
+ expect(code).toHaveWorkletData(1);
+ expect(code).toMatchSnapshot();
+ });
+
+ it('workletizes ObjectExpression only on last AssignmentExpression', () => {
+ const input = html``;
+
+ const { code } = runPlugin(input);
+ expect(code).toHaveWorkletData(1);
+ expect(code).toIncludeInWorkletString('AssignmentExpression');
+ expect(code).toMatchSnapshot();
+ });
+
+ it('prefers FunctionDeclaration over AssignmentExpression', () => {
+ const input = html``;
+ console.log(input);
+ const { code } = runPlugin(input);
+ console.log(code);
+
+ expect(code).toHaveWorkletData(1);
+ expect(code).toIncludeInWorkletString('FunctionDeclaration');
+ expect(code).toMatchSnapshot();
+ });
+
+ it('prefers AssignmentExpression over VariableDeclarator', () => {
+ // This is an anti-pattern, but let's at least have a defined behavior here.
+ const input = html``;
+
+ const { code } = runPlugin(input);
+ expect(code).toHaveWorkletData(1);
+ expect(code).toIncludeInWorkletString('AssignmentExpression');
+ expect(code).toMatchSnapshot();
+ });
+
+ it('workletizes in immediate scope', () => {
+ const input = html``;
+
+ const { code } = runPlugin(input);
+ expect(code).toHaveWorkletData(1);
+ expect(code).toMatchSnapshot();
+ });
+
+ it('workletizes in nested scope', () => {
+ const input = html``;
+
+ const { code } = runPlugin(input);
+ expect(code).toHaveWorkletData(1);
+ expect(code).toMatchSnapshot();
+ });
+
+ it('workletizes assigments that appear after the worklet is used', () => {
+ const input = html``;
+
+ const { code } = runPlugin(input);
+ expect(code).toHaveWorkletData(1);
+ expect(code).toIncludeInWorkletString('AssignmentAfterUse');
+ expect(code).toMatchSnapshot();
+ });
+ });
});
diff --git a/packages/react-native-reanimated/plugin/build/plugin.js b/packages/react-native-reanimated/plugin/build/plugin.js
index cbc222c18cf..6ffae2f7ab2 100644
--- a/packages/react-native-reanimated/plugin/build/plugin.js
+++ b/packages/react-native-reanimated/plugin/build/plugin.js
@@ -30,12 +30,17 @@ var require_types = __commonJS({
"lib/types.js"(exports2) {
"use strict";
Object.defineProperty(exports2, "__esModule", { value: true });
- exports2.isWorkletizableFunctionType = exports2.WorkletizableFunction = void 0;
+ exports2.isWorkletizableObjectType = exports2.isWorkletizableFunctionType = exports2.WorkletizableObject = exports2.WorkletizableFunction = void 0;
exports2.WorkletizableFunction = "FunctionDeclaration|FunctionExpression|ArrowFunctionExpression|ObjectMethod";
+ exports2.WorkletizableObject = "ObjectExpression";
function isWorkletizableFunctionType(path) {
return path.isFunctionDeclaration() || path.isFunctionExpression() || path.isArrowFunctionExpression() || path.isObjectMethod();
}
exports2.isWorkletizableFunctionType = isWorkletizableFunctionType;
+ function isWorkletizableObjectType(path) {
+ return path.isObjectExpression();
+ }
+ exports2.isWorkletizableObjectType = isWorkletizableObjectType;
}
});
@@ -817,6 +822,88 @@ var require_layoutAnimationAutoworkletization = __commonJS({
}
});
+// lib/referencedWorklets.js
+var require_referencedWorklets = __commonJS({
+ "lib/referencedWorklets.js"(exports2) {
+ "use strict";
+ Object.defineProperty(exports2, "__esModule", { value: true });
+ exports2.findReferencedWorklet = void 0;
+ var types_12 = require_types();
+ function findReferencedWorklet(workletIdentifier, acceptWorkletizableFunction, acceptObject) {
+ const workletName = workletIdentifier.node.name;
+ const scope = workletIdentifier.scope;
+ const workletBinding = scope.getBinding(workletName);
+ if (!workletBinding) {
+ return void 0;
+ }
+ if (acceptWorkletizableFunction && workletBinding.path.isFunctionDeclaration()) {
+ return workletBinding.path;
+ }
+ const isConstant = workletBinding.constant;
+ if (isConstant) {
+ return findReferencedWorkletFromVariableDeclarator(workletBinding, acceptWorkletizableFunction, acceptObject);
+ }
+ return findReferencedWorkletFromAssignmentExpression(workletBinding, acceptWorkletizableFunction, acceptObject);
+ }
+ exports2.findReferencedWorklet = findReferencedWorklet;
+ function findReferencedWorkletFromVariableDeclarator(workletBinding, acceptWorkletizableFunction, acceptObject) {
+ const workletDeclaration = workletBinding.path;
+ if (!workletDeclaration.isVariableDeclarator()) {
+ return void 0;
+ }
+ const worklet = workletDeclaration.get("init");
+ if (acceptWorkletizableFunction && (0, types_12.isWorkletizableFunctionType)(worklet)) {
+ return worklet;
+ }
+ if (acceptObject && (0, types_12.isWorkletizableObjectType)(worklet)) {
+ return worklet;
+ }
+ return void 0;
+ }
+ function findReferencedWorkletFromAssignmentExpression(workletBinding, acceptWorkletizableFunction, acceptObject) {
+ const workletDeclaration = workletBinding.constantViolations.reverse().find((constantViolation) => constantViolation.isAssignmentExpression() && (acceptWorkletizableFunction && (0, types_12.isWorkletizableFunctionType)(constantViolation.get("right")) || acceptObject && (0, types_12.isWorkletizableObjectType)(constantViolation.get("right"))));
+ if (!workletDeclaration || !workletDeclaration.isAssignmentExpression()) {
+ return void 0;
+ }
+ const workletDefinition = workletDeclaration.get("right");
+ if (acceptWorkletizableFunction && (0, types_12.isWorkletizableFunctionType)(workletDefinition)) {
+ return workletDefinition;
+ }
+ if (acceptObject && (0, types_12.isWorkletizableObjectType)(workletDefinition)) {
+ return workletDefinition;
+ }
+ return void 0;
+ }
+ }
+});
+
+// lib/objectWorklets.js
+var require_objectWorklets = __commonJS({
+ "lib/objectWorklets.js"(exports2) {
+ "use strict";
+ Object.defineProperty(exports2, "__esModule", { value: true });
+ exports2.processWorkletizableObject = void 0;
+ var types_12 = require_types();
+ var workletSubstitution_12 = require_workletSubstitution();
+ function processWorkletizableObject(path, state) {
+ const properties = path.get("properties");
+ for (const property of properties) {
+ if (property.isObjectMethod()) {
+ (0, workletSubstitution_12.processWorklet)(property, state);
+ } else if (property.isObjectProperty()) {
+ const value = property.get("value");
+ if ((0, types_12.isWorkletizableFunctionType)(value)) {
+ (0, workletSubstitution_12.processWorklet)(value, state);
+ }
+ } else {
+ throw new Error(`[Reanimated] '${property.type}' as to-be workletized argument is not supported for object hooks.`);
+ }
+ }
+ }
+ exports2.processWorkletizableObject = processWorkletizableObject;
+ }
+});
+
// lib/autoworkletization.js
var require_autoworkletization = __commonJS({
"lib/autoworkletization.js"(exports2) {
@@ -825,11 +912,33 @@ var require_autoworkletization = __commonJS({
exports2.processCalleesAutoworkletizableCallbacks = exports2.processIfAutoworkletizableCallback = void 0;
var types_12 = require("@babel/types");
var types_2 = require_types();
- var assert_1 = require("assert");
var workletSubstitution_12 = require_workletSubstitution();
var gestureHandlerAutoworkletization_1 = require_gestureHandlerAutoworkletization();
var layoutAnimationAutoworkletization_1 = require_layoutAnimationAutoworkletization();
+ var referencedWorklets_1 = require_referencedWorklets();
+ var objectWorklets_1 = require_objectWorklets();
+ var objectHooks = /* @__PURE__ */ new Set([
+ "useAnimatedGestureHandler",
+ "useAnimatedScrollHandler"
+ ]);
+ var functionHooks = /* @__PURE__ */ new Set([
+ "useFrameCallback",
+ "useAnimatedStyle",
+ "useAnimatedProps",
+ "createAnimatedPropAdapter",
+ "useDerivedValue",
+ "useAnimatedScrollHandler",
+ "useAnimatedReaction",
+ "useWorkletCallback",
+ "withTiming",
+ "withSpring",
+ "withDecay",
+ "withRepeat",
+ "runOnUI",
+ "executeOnUIRuntimeSync"
+ ]);
var functionArgsToWorkletize = /* @__PURE__ */ new Map([
+ ["useAnimatedGestureHandler", [0]],
["useFrameCallback", [0]],
["useAnimatedStyle", [0]],
["useAnimatedProps", [0]],
@@ -845,10 +954,6 @@ var require_autoworkletization = __commonJS({
["runOnUI", [0]],
["executeOnUIRuntimeSync", [0]]
]);
- var objectHooks = /* @__PURE__ */ new Set([
- "useAnimatedGestureHandler",
- "useAnimatedScrollHandler"
- ]);
function processIfAutoworkletizableCallback(path, state) {
if ((0, gestureHandlerAutoworkletization_1.isGestureHandlerEventCallback)(path) || (0, layoutAnimationAutoworkletization_1.isLayoutAnimationCallback)(path)) {
(0, workletSubstitution_12.processWorklet)(path, state);
@@ -863,52 +968,40 @@ var require_autoworkletization = __commonJS({
if (name === void 0) {
return;
}
- if (objectHooks.has(name)) {
- const maybeWorklet = path.get("arguments.0");
- (0, assert_1.strict)(!Array.isArray(maybeWorklet), "[Reanimated] `workletToProcess` is an array.");
- if (maybeWorklet.isObjectExpression()) {
- processObjectHook(maybeWorklet, state);
- } else if (name === "useAnimatedScrollHandler") {
- if ((0, types_2.isWorkletizableFunctionType)(maybeWorklet)) {
- (0, workletSubstitution_12.processWorklet)(maybeWorklet, state);
- }
- }
- } else {
- const indices = functionArgsToWorkletize.get(name);
- if (indices === void 0) {
- return;
- }
- processArguments(path, indices, state);
+ if (functionHooks.has(name) || objectHooks.has(name)) {
+ const acceptWorkletizableFunction = functionHooks.has(name);
+ const acceptObject = objectHooks.has(name);
+ const argIndices = functionArgsToWorkletize.get(name);
+ const args = path.get("arguments").filter((_, index) => argIndices.includes(index));
+ processArgs(args, state, acceptWorkletizableFunction, acceptObject);
}
}
exports2.processCalleesAutoworkletizableCallbacks = processCalleesAutoworkletizableCallbacks;
- function processObjectHook(path, state) {
- const properties = path.get("properties");
- for (const property of properties) {
- if (property.isObjectMethod()) {
- (0, workletSubstitution_12.processWorklet)(property, state);
- } else if (property.isObjectProperty()) {
- const value = property.get("value");
- if ((0, types_2.isWorkletizableFunctionType)(value)) {
- (0, workletSubstitution_12.processWorklet)(value, state);
- }
- } else {
- throw new Error(`[Reanimated] '${property.type}' as to-be workletized argument is not supported for object hooks.`);
- }
- }
- }
- function processArguments(path, indices, state) {
- const argumentsArray = path.get("arguments");
- indices.forEach((index) => {
- const maybeWorklet = argumentsArray[index];
+ function processArgs(args, state, acceptWorkletizableFunction, acceptObject) {
+ args.forEach((arg) => {
+ const maybeWorklet = findWorklet(arg, acceptWorkletizableFunction, acceptObject);
if (!maybeWorklet) {
return;
}
if ((0, types_2.isWorkletizableFunctionType)(maybeWorklet)) {
(0, workletSubstitution_12.processWorklet)(maybeWorklet, state);
+ } else if ((0, types_2.isWorkletizableObjectType)(maybeWorklet)) {
+ (0, objectWorklets_1.processWorkletizableObject)(maybeWorklet, state);
}
});
}
+ function findWorklet(arg, acceptWorkletizableFunction, acceptObject) {
+ if (acceptWorkletizableFunction && (0, types_2.isWorkletizableFunctionType)(arg)) {
+ return arg;
+ }
+ if (acceptObject && (0, types_2.isWorkletizableObjectType)(arg)) {
+ return arg;
+ }
+ if (arg.isReferencedIdentifier() && arg.isIdentifier()) {
+ return (0, referencedWorklets_1.findReferencedWorklet)(arg, acceptWorkletizableFunction, acceptObject);
+ }
+ return void 0;
+ }
}
});
diff --git a/packages/react-native-reanimated/plugin/src/autoworkletization.ts b/packages/react-native-reanimated/plugin/src/autoworkletization.ts
index 792025391d3..a9e3eaa141d 100644
--- a/packages/react-native-reanimated/plugin/src/autoworkletization.ts
+++ b/packages/react-native-reanimated/plugin/src/autoworkletization.ts
@@ -1,14 +1,47 @@
import type { NodePath } from '@babel/core';
-import type { CallExpression, ObjectExpression } from '@babel/types';
+import type { CallExpression } from '@babel/types';
import { isSequenceExpression } from '@babel/types';
-import { isWorkletizableFunctionType } from './types';
-import type { ReanimatedPluginPass, WorkletizableFunction } from './types';
-import { strict as assert } from 'assert';
+import {
+ isWorkletizableFunctionType,
+ isWorkletizableObjectType,
+} from './types';
+import type {
+ WorkletizableFunction,
+ WorkletizableObject,
+ ReanimatedPluginPass,
+} from './types';
import { processWorklet } from './workletSubstitution';
import { isGestureHandlerEventCallback } from './gestureHandlerAutoworkletization';
import { isLayoutAnimationCallback } from './layoutAnimationAutoworkletization';
+import { findReferencedWorklet } from './referencedWorklets';
+import { processWorkletizableObject } from './objectWorklets';
+
+const objectHooks = new Set([
+ 'useAnimatedGestureHandler',
+ 'useAnimatedScrollHandler',
+]);
+
+const functionHooks = new Set([
+ 'useFrameCallback',
+ 'useAnimatedStyle',
+ 'useAnimatedProps',
+ 'createAnimatedPropAdapter',
+ 'useDerivedValue',
+ 'useAnimatedScrollHandler',
+ 'useAnimatedReaction',
+ 'useWorkletCallback',
+ // animations' callbacks
+ 'withTiming',
+ 'withSpring',
+ 'withDecay',
+ 'withRepeat',
+ // scheduling functions
+ 'runOnUI',
+ 'executeOnUIRuntimeSync',
+]);
const functionArgsToWorkletize = new Map([
+ ['useAnimatedGestureHandler', [0]],
['useFrameCallback', [0]],
['useAnimatedStyle', [0]],
['useAnimatedProps', [0]],
@@ -17,21 +50,14 @@ const functionArgsToWorkletize = new Map([
['useAnimatedScrollHandler', [0]],
['useAnimatedReaction', [0, 1]],
['useWorkletCallback', [0]],
- // animations' callbacks
['withTiming', [2]],
['withSpring', [2]],
['withDecay', [1]],
['withRepeat', [3]],
- // scheduling functions
['runOnUI', [0]],
['executeOnUIRuntimeSync', [0]],
]);
-const objectHooks = new Set([
- 'useAnimatedGestureHandler',
- 'useAnimatedScrollHandler',
-]);
-
/**
*
* @returns `true` if the function was workletized, `false` otherwise.
@@ -67,65 +93,58 @@ export function processCalleesAutoworkletizableCallbacks(
return;
}
- if (objectHooks.has(name)) {
- const maybeWorklet = path.get('arguments.0');
- assert(
- !Array.isArray(maybeWorklet),
- '[Reanimated] `workletToProcess` is an array.'
- );
- if (maybeWorklet.isObjectExpression()) {
- processObjectHook(maybeWorklet, state);
- // useAnimatedScrollHandler can take a function as an argument instead of an ObjectExpression
- // but useAnimatedGestureHandler can't
- } else if (name === 'useAnimatedScrollHandler') {
- if (isWorkletizableFunctionType(maybeWorklet)) {
- processWorklet(maybeWorklet, state);
- }
- }
- } else {
- const indices = functionArgsToWorkletize.get(name);
- if (indices === undefined) {
- return;
- }
- processArguments(path, indices, state);
- }
-}
+ if (functionHooks.has(name) || objectHooks.has(name)) {
+ const acceptWorkletizableFunction = functionHooks.has(name);
+ const acceptObject = objectHooks.has(name);
+ const argIndices = functionArgsToWorkletize.get(name)!;
+ const args = path
+ .get('arguments')
+ .filter((_, index) => argIndices.includes(index));
-function processObjectHook(
- path: NodePath,
- state: ReanimatedPluginPass
-): void {
- const properties = path.get('properties');
- for (const property of properties) {
- if (property.isObjectMethod()) {
- processWorklet(property, state);
- } else if (property.isObjectProperty()) {
- const value = property.get('value');
- if (isWorkletizableFunctionType(value)) {
- processWorklet(value, state);
- }
- } else {
- throw new Error(
- `[Reanimated] '${property.type}' as to-be workletized argument is not supported for object hooks.`
- );
- }
+ processArgs(args, state, acceptWorkletizableFunction, acceptObject);
}
}
-function processArguments(
- path: NodePath,
- indices: number[],
- state: ReanimatedPluginPass
+function processArgs(
+ args: NodePath[],
+ state: ReanimatedPluginPass,
+ acceptWorkletizableFunction: boolean,
+ acceptObject: boolean
): void {
- const argumentsArray = path.get('arguments');
- indices.forEach((index) => {
- const maybeWorklet = argumentsArray[index];
+ args.forEach((arg) => {
+ const maybeWorklet = findWorklet(
+ arg,
+ acceptWorkletizableFunction,
+ acceptObject
+ );
if (!maybeWorklet) {
- // workletizable argument doesn't always have to be specified
return;
}
if (isWorkletizableFunctionType(maybeWorklet)) {
processWorklet(maybeWorklet, state);
+ } else if (isWorkletizableObjectType(maybeWorklet)) {
+ processWorkletizableObject(maybeWorklet, state);
}
});
}
+
+function findWorklet(
+ arg: NodePath,
+ acceptWorkletizableFunction: boolean,
+ acceptObject: boolean
+): NodePath | NodePath | undefined {
+ if (acceptWorkletizableFunction && isWorkletizableFunctionType(arg)) {
+ return arg;
+ }
+ if (acceptObject && isWorkletizableObjectType(arg)) {
+ return arg;
+ }
+ if (arg.isReferencedIdentifier() && arg.isIdentifier()) {
+ return findReferencedWorklet(
+ arg,
+ acceptWorkletizableFunction,
+ acceptObject
+ );
+ }
+ return undefined;
+}
diff --git a/packages/react-native-reanimated/plugin/src/jestMatchers.ts b/packages/react-native-reanimated/plugin/src/jestMatchers.ts
index caf3b52f03e..34b8dfbab1c 100644
--- a/packages/react-native-reanimated/plugin/src/jestMatchers.ts
+++ b/packages/react-native-reanimated/plugin/src/jestMatchers.ts
@@ -7,6 +7,7 @@ declare global {
toHaveWorkletData(times?: number): R;
toHaveInlineStyleWarning(times?: number): R;
toHaveLocation(location: string): R;
+ toIncludeInWorkletString(expected: string): R;
}
}
}
@@ -32,6 +33,7 @@ expect.extend({
pass: false,
};
},
+
toHaveInlineStyleWarning(received: string, expectedMatchCount = 1) {
const receivedMatchCount =
received.match(INLINE_STYLE_WARNING_REGEX)?.length || 0;
@@ -49,6 +51,7 @@ expect.extend({
pass: false,
};
},
+
toHaveLocation(received: string, expectedLocation: string) {
const expectedString = `location: "${expectedLocation}"`;
const hasLocation = received.includes(expectedLocation);
@@ -64,4 +67,27 @@ expect.extend({
pass: false,
};
},
+
+ toIncludeInWorkletString(received: string, expected: string) {
+ // Regular expression pattern to match the code field
+ const pattern = /code: "((?:[^"\\]|\\.)*)"/s;
+ const match = received.match(pattern);
+
+ // If a match was found and the match group 1 (content within quotes) includes the expected string
+ if (match && match[1].includes(expected)) {
+ // return true;
+ return {
+ message: () => `Reanimated: found ${expected} in worklet string`,
+ pass: true,
+ };
+ }
+
+ // If no match was found or the expected string is not a substring of the code field
+ // return false;
+ return {
+ message: () =>
+ `Reanimated: expected to find ${expected} in worklet string, but it's not present`,
+ pass: false,
+ };
+ },
});
diff --git a/packages/react-native-reanimated/plugin/src/objectWorklets.ts b/packages/react-native-reanimated/plugin/src/objectWorklets.ts
new file mode 100644
index 00000000000..4bb8302ba23
--- /dev/null
+++ b/packages/react-native-reanimated/plugin/src/objectWorklets.ts
@@ -0,0 +1,25 @@
+import type { NodePath } from '@babel/core';
+import { isWorkletizableFunctionType } from './types';
+import type { WorkletizableObject, ReanimatedPluginPass } from './types';
+import { processWorklet } from './workletSubstitution';
+
+export function processWorkletizableObject(
+ path: NodePath,
+ state: ReanimatedPluginPass
+): void {
+ const properties = path.get('properties');
+ for (const property of properties) {
+ if (property.isObjectMethod()) {
+ processWorklet(property, state);
+ } else if (property.isObjectProperty()) {
+ const value = property.get('value');
+ if (isWorkletizableFunctionType(value)) {
+ processWorklet(value, state);
+ }
+ } else {
+ throw new Error(
+ `[Reanimated] '${property.type}' as to-be workletized argument is not supported for object hooks.`
+ );
+ }
+ }
+}
diff --git a/packages/react-native-reanimated/plugin/src/referencedWorklets.ts b/packages/react-native-reanimated/plugin/src/referencedWorklets.ts
new file mode 100644
index 00000000000..e9d4d52ec0b
--- /dev/null
+++ b/packages/react-native-reanimated/plugin/src/referencedWorklets.ts
@@ -0,0 +1,97 @@
+import type { NodePath } from '@babel/core';
+import type { AssignmentExpression, Identifier } from '@babel/types';
+import {
+ isWorkletizableFunctionType,
+ isWorkletizableObjectType,
+} from './types';
+import type { WorkletizableFunction, WorkletizableObject } from './types';
+import type { Binding } from '@babel/traverse';
+
+export function findReferencedWorklet(
+ workletIdentifier: NodePath,
+ acceptWorkletizableFunction: boolean,
+ acceptObject: boolean
+): NodePath | NodePath | undefined {
+ const workletName = workletIdentifier.node.name;
+ const scope = workletIdentifier.scope;
+
+ const workletBinding = scope.getBinding(workletName);
+ if (!workletBinding) {
+ return undefined;
+ }
+
+ if (
+ acceptWorkletizableFunction &&
+ workletBinding.path.isFunctionDeclaration()
+ ) {
+ return workletBinding.path;
+ }
+
+ const isConstant = workletBinding.constant;
+ if (isConstant) {
+ return findReferencedWorkletFromVariableDeclarator(
+ workletBinding,
+ acceptWorkletizableFunction,
+ acceptObject
+ );
+ }
+ return findReferencedWorkletFromAssignmentExpression(
+ workletBinding,
+ acceptWorkletizableFunction,
+ acceptObject
+ );
+}
+
+function findReferencedWorkletFromVariableDeclarator(
+ workletBinding: Binding,
+ acceptWorkletizableFunction: boolean,
+ acceptObject: boolean
+): NodePath | NodePath | undefined {
+ const workletDeclaration = workletBinding.path;
+ if (!workletDeclaration.isVariableDeclarator()) {
+ return undefined;
+ }
+ const worklet = workletDeclaration.get('init');
+
+ if (acceptWorkletizableFunction && isWorkletizableFunctionType(worklet)) {
+ return worklet;
+ }
+ if (acceptObject && isWorkletizableObjectType(worklet)) {
+ return worklet;
+ }
+ return undefined;
+}
+
+function findReferencedWorkletFromAssignmentExpression(
+ workletBinding: Binding,
+ acceptWorkletizableFunction: boolean,
+ acceptObject: boolean
+): NodePath | NodePath | undefined {
+ const workletDeclaration = workletBinding.constantViolations
+ .reverse()
+ .find(
+ (constantViolation) =>
+ constantViolation.isAssignmentExpression() &&
+ ((acceptWorkletizableFunction &&
+ isWorkletizableFunctionType(constantViolation.get('right'))) ||
+ (acceptObject &&
+ isWorkletizableObjectType(constantViolation.get('right'))))
+ ) as NodePath | undefined;
+
+ if (!workletDeclaration || !workletDeclaration.isAssignmentExpression()) {
+ return undefined;
+ }
+
+ const workletDefinition = workletDeclaration.get('right');
+
+ if (
+ acceptWorkletizableFunction &&
+ isWorkletizableFunctionType(workletDefinition)
+ ) {
+ return workletDefinition;
+ }
+ if (acceptObject && isWorkletizableObjectType(workletDefinition)) {
+ return workletDefinition;
+ }
+ return undefined;
+}
diff --git a/packages/react-native-reanimated/plugin/src/types.ts b/packages/react-native-reanimated/plugin/src/types.ts
index a13442c615d..228669f1b87 100644
--- a/packages/react-native-reanimated/plugin/src/types.ts
+++ b/packages/react-native-reanimated/plugin/src/types.ts
@@ -4,6 +4,7 @@ import type {
FunctionExpression,
ObjectMethod,
ArrowFunctionExpression,
+ ObjectExpression,
} from '@babel/types';
export interface ReanimatedPluginOptions {
@@ -35,8 +36,12 @@ export type WorkletizableFunction =
export const WorkletizableFunction =
'FunctionDeclaration|FunctionExpression|ArrowFunctionExpression|ObjectMethod';
+export type WorkletizableObject = ObjectExpression;
+
+export const WorkletizableObject = 'ObjectExpression';
+
export function isWorkletizableFunctionType(
- path: NodePath
+ path: NodePath
): path is NodePath {
return (
path.isFunctionDeclaration() ||
@@ -45,3 +50,9 @@ export function isWorkletizableFunctionType(
path.isObjectMethod()
);
}
+
+export function isWorkletizableObjectType(
+ path: NodePath
+): path is NodePath {
+ return path.isObjectExpression();
+}