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

Extend plugin's documentation #6227

Merged
merged 8 commits into from
Jul 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"packages": [
"packages/react-native-reanimated",
"packages/eslint-plugin-reanimated",
"packages/react-native-reanimated/plugin",
"apps/*"
]
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"label": "Reanimated Babel plugin",
"position": 11,
"link": {
"type": "generated-index"
}
}
248 changes: 248 additions & 0 deletions packages/docs-reanimated/docs/reanimated-babel-plugin/about.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
---
id: about
title: 'About'
sidebar_label: 'About'
---

# Reanimated Babel Plugin

## What is Reanimated Babel Plugin?

The Reanimated Babel Plugin transforms your code so that it can run on the [UI thread](/docs/fundamentals/glossary#ui-thread). It looks for functions marked with a `'worklet';` directive and converts them into serializable objects. We call this process [workletization](/docs/fundamentals/glossary#to-workletize).

A worklet is:

- A function that contains a `'worklet'` directive at its very top, i.e.:

```ts
function foo() {
'worklet';
console.log('Hello from worklet');
}
```

- A function that is _autoworkletizable_, i.e.:

```ts
useAnimatedStyle(() => {
// This function will be ran on the UI thread,
// hence it's in a workletizable context and will be
// autoworkletized. You don't need to add the 'worklet' directive here.
return {
width: 100,
};
});
```

## What can be a worklet?

Currently, Reanimated Babel Plugin supports the following constructs as worklets:

- Function Declarations

```ts
function foo() {
'worklet';
console.log('Hello from FunctionDeclaration');
}
```

- Function Expressions

```ts
const foo = function () {
'worklet';
console.log('Hello from FunctionExpression');
};
```

- Arrow Function Expressions

```ts
const foo = () => {
'worklet';
console.log('Hello from ArrowFunctionExpression');
};
```

- Object Methods

```ts
const obj = {
foo() {
'worklet';
console.log('Hello from ObjectMethod');
},
};
```

## Autoworkletization

To reduce boilerplate code and provide a safer API, Reanimated Babel Plugin detects automatically whether a function should be workletized. Thanks to that, you don't need to add the `'worklet'` directive to your callbacks:

```ts
const style = useAnimatedStyle(() => {
// You don't need to add the 'worklet' directive here,
// since plugin detects this callback as autoworkletizable.
return {
width: 100,
};
});
```

This isn't limited to `useAnimatedStyle` hook - Reanimated Babel Plugin autoworkletizes all callbacks for the API of Reanimated. The whole list can be found in the [plugin source code](https://github.com/software-mansion/react-native-reanimated/blob/main/packages/react-native-reanimated/plugin/src/autoworkletization.ts).

Keep in mind that in more advanced use cases, you might still need to manually mark a function as a worklet.

### Referencing worklets

You can define worklets **before** they are used and the plugin will autoworkletize them too:

```ts
function foo() {
// You don't need to add
// the 'worklet' directive here.
return { width: 100 };
}

// You don't need to define an inline function here,
// a reference is enough.
const style = useAnimatedStyle(foo);
```

### Objects aggregating worklets

In some APIs, like `useAnimatedScrollHandler` you can pass an object that contains worklets instead of a function:

```ts
const handlerObject = {
// You don't need to mark these methods as worklets.
onBeginDrag() {
console.log('Dragging...');
},
onScroll() {
console.log('Scrolling...');
},
};

const handler = useAnimatedScrollHandler(handlerObject);
```

### Workletizing whole files (experimental)

You can mark a file as a workletizable file by adding the `'worklet'` directive to the top of the file.

This will make all _top-level_

- Functions
- Objects aggregating worklets

workletized automatically. This can come in handy for files that contain multiple worklets.

```ts
// file.ts
'worklet';

function foo() {
// Function 'foo' will be autoworkletized.
return { width: 100 };
}

function bar() {
// Function 'bar' will be autoworkletized.
function foobar() {
// Function 'foobar' won't since it's not defined in top-level scope.
console.log("I'm not a worklet");
}
return { width: 100 };
}
```

## Limits of autoworkletization

The plugin cannot infer whether a function is autoworkletizable or not in some contexts.

### Imports

When importing a function from another file or a module and using it as a worklet, you must manually add the `'worklet'` directive to the function:

```ts
// foo.ts
import { bar } from './bar';
// ...
const style = useAnimatedStyle(bar);

// bar.ts
export function bar() {
'worklet'; // Won't work without it.
return {
width: 100,
};
}
```

### Custom hooks

Currently Reanimated hasn't exposed APIs that would allow you to register your custom hooks for callback workletization. This however, might change in the future.

### Expressions

A function won't get automatically workletized when it's a result of an expression. You have to add the `"worklet";` directive to make it work:

```ts
const foo = someCondition
? () => {
'worklet'; // Won't work without it.
return { width: 100 };
}
: () => {
'worklet'; // Won't work without it.
return { width: 200 };
};

const style = useAnimatedStyle(foo);
```

In such cases we recommend either handling the conditional logic in the worklet itself or refactoring your code to eliminate the need for conditional worklets.

## Pitfalls

There are some patterns that won't work with the plugin.

### Hoisting worklets

Worklets aren't hoisted. This means that you can't use worklets before they are defined:

```ts
// The following line crashes,
// even though 'foo' is marked as a worklet.
const style = useAnimatedStyle(foo);

function foo() {
'worklet';
return { width: 100 };
}
```

### Classes

You can't use classes on the UI thread:

```ts
class Clazz {
foo() {
'worklet';
return { width: 100 };
}
}

const clazz = new Clazz();

// The following line crashes,
// even though 'foo' is marked as a worklet.
const style = useAnimatedStyle(clazz.foo());
```

## Notes

Babel is a powerful tool that can be explored to implement numerous useful features. If you feel like Reanimated Babel plugin could make use of some new functionality or that its pitfalls are too severe, feel free to let us know on [GitHub](https://github.com/software-mansion/react-native-reanimated/), via an issue or a discussion thread - and as always, PRs are welcome!
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
id: plugin-options
title: 'Options for Reanimated Babel Plugin'
sidebar_label: 'Options for Reanimated Babel Plugin'
title: 'Options'
sidebar_label: 'Options'
---

# Options for Reanimated Babel Plugin
Expand Down
6 changes: 6 additions & 0 deletions packages/react-native-reanimated/plugin/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// This file is needed for manual tests of the plugin.
const reanimatedPlugin = require('./index.js');

module.exports = {
plugins: [reanimatedPlugin],
};
5 changes: 5 additions & 0 deletions packages/react-native-reanimated/plugin/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
{
"devDependencies": {
"@babel/cli": "^7.20.0",
"@babel/core": "^7.20.0",
"@babel/plugin-transform-unicode-regex": "^7.24.7",
"@babel/traverse": "^7.20.0",
"@babel/types": "^7.20.0",
"@react-native/eslint-config": "^0.72.1",
"@types/node": "^18.15.11",
"@typescript-eslint/eslint-plugin": "^7.0.2",
Expand Down
Loading