Skip to content
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

release: 3.16.2 #6735

Merged
merged 6 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat: Add fallback detection methods for Worklet Classes (#6706)
Sometimes the class property

```javascript
	class Clazz {
		__workletClass = true;
	};
```

Can get stripped away by some Babel plugins. We add some additional
fallback methods so the users doesn't have to change his Babel pipeline
to use this feature.

🚀
  • Loading branch information
tjzel committed Nov 20, 2024
commit dccbede7dd3aa91045596511666780bf7c3fbdbb
4 changes: 2 additions & 2 deletions packages/react-native-reanimated/plugin/index.js

Large diffs are not rendered by default.

46 changes: 40 additions & 6 deletions packages/react-native-reanimated/plugin/src/class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,7 @@ export function processIfWorkletClass(
classPath: NodePath<ClassDeclaration>,
state: ReanimatedPluginPass
): boolean {
if (!classPath.node.id) {
// We don't support unnamed classes yet.
return false;
}

if (!hasWorkletClassMarker(classPath.node.body)) {
if (!isWorkletizableClass(classPath, state)) {
return false;
}

Expand Down Expand Up @@ -342,3 +337,42 @@ type Polyfill = {
index: number;
dependencies: Set<string>;
};

function isWorkletizableClass(
classPath: NodePath<ClassDeclaration>,
state: ReanimatedPluginPass
): boolean {
const className = classPath.node.id?.name;
const classNode = classPath.node;

// We don't support unnamed classes yet.
if (!className) {
return false;
}

// Primary method of determining if a class is workletizable. However, some
// Babel plugins might remove Class Properties.
const isMarked = hasWorkletClassMarker(classNode.body);

// Secondary method of determining if a class is workletizable. We look for the
// reference we memoized earlier. However, some plugin could've changed the reference.
const isMemoizedNode = state.classesToWorkletize.some(
(record) => record.node === classNode
);

// Fallback for the name of the class.
// We bail on non-top-level declarations.
const isTopLevelMemoizedName =
classPath.parentPath.isProgram() &&
state.classesToWorkletize.some((record) => record.name === className);

// Remove the class from the list of classes to workletize. There are some edge
// cases when leaving it as is would lead to multiple workletizations.
state.classesToWorkletize = state.classesToWorkletize.filter(
(record) => record.node !== classNode && record.name !== className
);

const result = isMarked || isMemoizedNode || isTopLevelMemoizedName;

return result;
}
39 changes: 28 additions & 11 deletions packages/react-native-reanimated/plugin/src/file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import { contextObjectMarker } from './contextObject';

export function processIfWorkletFile(
path: NodePath<Program>,
_state: ReanimatedPluginPass
state: ReanimatedPluginPass
): boolean {
if (
!path.node.directives.some(
Expand All @@ -50,18 +50,21 @@ export function processIfWorkletFile(
path.node.directives = path.node.directives.filter(
(functionDirective) => functionDirective.value.value !== 'worklet'
);
processWorkletFile(path);
processWorkletFile(path, state);

return true;
}

/** Adds a worklet directive to each viable top-level entity in the file. */
function processWorkletFile(programPath: NodePath<Program>) {
function processWorkletFile(
programPath: NodePath<Program>,
state: ReanimatedPluginPass
) {
const statements = programPath.get('body');
dehoistCommonJSExports(programPath.node);
statements.forEach((statement) => {
const candidatePath = getCandidate(statement);
processWorkletizableEntity(candidatePath);
processWorkletizableEntity(candidatePath, state);
});
}

Expand All @@ -76,7 +79,10 @@ function getCandidate(statementPath: NodePath<Statement>) {
}
}

function processWorkletizableEntity(nodePath: NodePath<unknown>) {
function processWorkletizableEntity(
nodePath: NodePath<unknown>,
state: ReanimatedPluginPass
) {
if (isWorkletizableFunctionPath(nodePath)) {
if (nodePath.isArrowFunctionExpression()) {
replaceImplicitReturnWithBlock(nodePath.node);
Expand All @@ -86,35 +92,46 @@ function processWorkletizableEntity(nodePath: NodePath<unknown>) {
if (isImplicitContextObject(nodePath)) {
appendWorkletContextObjectMarker(nodePath.node);
} else {
processWorkletAggregator(nodePath);
processWorkletAggregator(nodePath, state);
}
} else if (nodePath.isVariableDeclaration()) {
processVariableDeclaration(nodePath);
processVariableDeclaration(nodePath, state);
} else if (nodePath.isClassDeclaration()) {
appendWorkletClassMarker(nodePath.node.body);
if (nodePath.node.id?.name) {
// We don't support unnamed classes yet.
state.classesToWorkletize.push({
node: nodePath.node,
name: nodePath.node.id.name,
});
}
}
}

function processVariableDeclaration(
variableDeclarationPath: NodePath<VariableDeclaration>
variableDeclarationPath: NodePath<VariableDeclaration>,
state: ReanimatedPluginPass
) {
const declarations = variableDeclarationPath.get('declarations');
declarations.forEach((declaration) => {
const initPath = declaration.get('init');
if (initPath.isExpression()) {
processWorkletizableEntity(initPath);
processWorkletizableEntity(initPath, state);
}
});
}

function processWorkletAggregator(objectPath: NodePath<ObjectExpression>) {
function processWorkletAggregator(
objectPath: NodePath<ObjectExpression>,
state: ReanimatedPluginPass
) {
const properties = objectPath.get('properties');
properties.forEach((property) => {
if (property.isObjectMethod()) {
appendWorkletDirective(property.node.body);
} else if (property.isObjectProperty()) {
const valuePath = property.get('value');
processWorkletizableEntity(valuePath);
processWorkletizableEntity(valuePath, state);
}
});
}
Expand Down
1 change: 1 addition & 0 deletions packages/react-native-reanimated/plugin/src/globals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ const notCapturedIdentifiers_DEPRECATED = [

export function initializeState(state: ReanimatedPluginPass) {
state.workletNumber = 1;
state.classesToWorkletize = [];
initializeGlobals();
addCustomGlobals(state);
}
Expand Down
1 change: 1 addition & 0 deletions packages/react-native-reanimated/plugin/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export interface ReanimatedPluginPass {
cwd: string;
filename: string | undefined;
workletNumber: number;
classesToWorkletize: { node: BabelNode; name: string }[];
}

export type WorkletizableFunction =
Expand Down