-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Rewrite plugin to TypeScript #4096
Merged
Merged
Changes from 58 commits
Commits
Show all changes
73 commits
Select commit
Hold shift + click to select a range
9712156
working build
tjzel 50b68fe
changed require to import
tjzel fd81dd1
changed ReferencedIdentifier to Identifier (I guess it is gonna break…
tjzel d7e7527
probable fix
tjzel 059ffbb
fixed hash error
tjzel 600d850
preparing for string worklet function
tjzel 0a8a85f
added some comments to not lose things that need more thought
tjzel d7104b3
more changes
tjzel 70ca4e8
almost fixed buildWorkletString
tjzel 7b0ab43
working so far
tjzel dc1b045
need to rebuild the project
tjzel 484170c
wrong ting in newFun?
tjzel 431ec78
changes to makeWorklet
tjzel 3f8803f
before major changes in processWorklets
tjzel e90bb52
almost ready for first draft
tjzel 74dede5
first draft
tjzel 0241d78
migrated to PluginPass
tjzel c4ff4fe
restore deleted file
tjzel deb4782
newline
tjzel a361911
typos & index.js update
tjzel 2b265ff
prettied index.js
tjzel 392378e
recompiled index.js
tjzel 8772841
added tsconfig
tjzel 014ed46
amended error messages
tjzel 40e7ecc
Merge branch '@tjzel/ts-plugin-refactor' into @tjzel/ts-plugin-refact…
tjzel 02bf855
will remove comments now
tjzel d328572
cleaner build with tsconfig
tjzel 42efa28
Merge branch main into @tjzel/ts-plugin-refactor
tjzel f9043d4
included #4062 plugin changes
tjzel 225c956
included #3970 plugin changes
tjzel 0f8e340
included #4104 plugin changes
tjzel 5d31a45
compiled index.js
tjzel 4ef9d8c
apply formatting
tjzel 21dfdff
add eslint ignore for commit ability
tjzel 42ff15b
actually .eslintignore is not necessary
tjzel d3f3f3f
.eslintignore didnt get removed for some reason
tjzel 3db07e8
formatting for easier diff on github
tjzel fcc30ed
reverted some naming changes for clarity
tjzel b41f25a
with new index.js
tjzel e841d93
added README file
tjzel 6e027ee
added source map
tjzel 4a08fc5
follow up
tjzel 26f7725
amendments in regard to review
tjzel 9b9fc13
improved some typechecking and errors
tjzel 64eec77
added building scripts for plugin to package.json
tjzel e43cb1d
ES6 in tsconfig and removed unnecessary interface
tjzel f56bf06
changes from ES6 as target
tjzel e72fe0d
added plugins own package.json
tjzel 7acac08
added dependencies
tjzel 6a57c94
added dev dependencies
tjzel 578acc2
import 'path'
tjzel 1ecf504
guessing at this point
tjzel 832eebb
force typing
tjzel 0baa675
more commits
tjzel d766190
hope it works on windows now
tjzel a32406e
...
tjzel 32dc5e9
added yarn.lock
tjzel d700a9e
added readme
tjzel 39d9292
tomekzaw review
tjzel a084988
typo fix
tjzel 5086fce
added plugin type:check to global package.json
tjzel fb00ed9
no more postinstall
tjzel f9daad2
added CI
tjzel 0227710
added yarn in root for CI
tjzel 7a30818
weird formatting bug
tjzel da0bf77
/..
tjzel bc2758b
m
tjzel 6b5bf1c
removed sourcemap dependency
tjzel 0fad557
Update .github/workflows/validate-plugin.yml
tjzel f3cacdf
Update plugin/README.md
tjzel 23ea6d3
Apply suggestions from code review
tjzel 5ad95b4
readme amendments
tjzel 08e425b
Merge branch 'main' into @tjzel/ts-plugin-refactor
tjzel File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
### ReanimatedPlugin is now in TypeScript. | ||
tjzel marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
To compile it, either use `yarn` or explicitly use `yarn plugin` in the root directory or `yarn` in `plugin/`. | ||
|
||
# Why do we need this plugin? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use h2 or h3 for subsection titles instead ( |
||
|
||
Reanimated is all about executing the code directly on the UI thread whenever possible to avoid expensive and troublesome communication between those two threads. Since UI and JS (React-Native) contexts are separate, we somehow need to pass the functions (and their arguments) from the JS thread to the UI thread. That's why we need **worklets**. If you haven't yet, we strongly recommend reading [the official documentation on worklets](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/worklets/) first. | ||
|
||
# What is a worklet? | ||
|
||
Worklets are **Arrow Function Expressions**, **Function Declarations**, **Function Expressions**, or **Object Methods** (for more information, refer to [official ECMAScript](https://262.ecma-international.org/), [types in Babel](https://babeljs.io/docs/babel-types), [AST Explorer](https://astexplorer.net/) - with @babel/parser) that contain a `worklet` directive at their very top. And that's it. It might be quite disappointing, but all the fun begins once we make such a worklet. It's a function that can be called on JS and UI threads. That's why Reanimated is able to produce smooth and responsive animations - functions that control those animations are not executed on JS thread, from where their results would need to be transported to UI thread and then applied. | ||
|
||
# How does a worklet work? | ||
|
||
The sole principle is (seemingly) simple. Once the Babel parser has transformed the code of the worklet function it appends some information to the function (since functions are objects in JavaScript). Once `runOnUI` has been called with our worklet, this extra information, which mostly is just JavaScript code, is injected into UI thread. Then UI thread calls `eval`, on this code and is happily able to run it autonomously. If it's just `eval` you might ask: | ||
|
||
# Why transform? | ||
|
||
Well, we have to remember that UI thread is a completely different context. It does not have access to the scope we called the function from, nor anything present on the JS thread. Obviously, we could just copy the entire JS thread and inject it into the UI, but that's in direct conflict with one of Reanimated's principles - _speed_. We want to inject _as little data as possible_ - that's why we don't copy the context and we don't copy all the function closures. We only copy the variables we need (that are used in the worklet) and pass _only them_. For that, we need some transforming and transforming only for the parts that we will need to inject. But we can do better. One of Reanimated's strong points is transparency. We can just inject some code into the UI thread, why not, but once something crashes, we'd like to know what. That's why we also append some debugging information - reference to the original code - using simple source maps. That allows us to get information about what failed in the worklet even though it was executed on another thread. Pretty neat, right? | ||
|
||
To sum up, the process of worklet transformation appends to the JS function: | ||
|
||
- its UI code (as a string), | ||
- closure with only required variables, | ||
- debugging information (source maps). | ||
|
||
# Is that it? | ||
|
||
Of course not. Reanimated Plugin does a lot more than that. We also perform automatic _workletization_. It's a functionality that allows the developers to have less boilerplate code. When you use `useAnimatedStyle` or `useAnimatedGestureHandler` we **cannot** give them non-worklet functions. So, simply put, Plugin detects if it should workletize a non-worklet function and then workletizes it. Thanks to it, we can type: | ||
|
||
```TypeScript | ||
const scrollHandler = useAnimatedScrollHandler({ | ||
onScroll: (e) => { | ||
position.value = e.contentOffset.x; | ||
}, | ||
onEndDrag: (e) => { | ||
scrollToNearestItem(e.contentOffset.x); | ||
}, | ||
onMomentumEnd: (e) => { | ||
scrollToNearestItem(e.contentOffset.x); | ||
}, | ||
}); | ||
``` | ||
|
||
instead of | ||
|
||
```TypeScript | ||
const scrollHandler = useAnimatedScrollHandler({ | ||
onScroll: (e) => { | ||
`worklet`; | ||
tjzel marked this conversation as resolved.
Show resolved
Hide resolved
|
||
position.value = e.contentOffset.x; | ||
}, | ||
onEndDrag: (e) => { | ||
`worklet`; | ||
scrollToNearestItem(e.contentOffset.x); | ||
}, | ||
onMomentumEnd: (e) => { | ||
`worklet`; | ||
scrollToNearestItem(e.contentOffset.x); | ||
}, | ||
}); | ||
``` | ||
|
||
This might not seem to be a lot but it really helps with development and allows us to delegate some tedious tasks to babel. | ||
|
||
# Something doesn't work in Reanimated Plugin! | ||
|
||
It's certainly possible. It's being created ad-hoc from our experience and needs, not pre-planned in general: | ||
|
||
_We had a need to transform something -> we looked up how it is structured in AST explorer -> we added certain functionality to Plugin._ | ||
|
||
Some use cases might've been overlooked and on the other hand - some might work but were not designed (considered) to be transformed. That's why we strongly suggest that before you do something unusual with Reanimated you should check if it's conforming to Plugin. If it's not and you think it would be useful - you are more than welcome to contribute and submit a pull request on our [repo](https://www.github.com/software-mansion/react-native-reanimated). | ||
|
||
# Plugin's applications | ||
|
||
To get some more information about certain edge cases or use cases not listed here, try looking them up in our [tests](https://github.com/software-mansion/react-native-reanimated/blob/main/__tests__/plugin.test.js). | ||
|
||
### What can be a worklet? | ||
|
||
As stated in [this paragraph](#what-is-a-worklet), a worklet is supposed to be one of those: | ||
|
||
- Arrow Function Expression: | ||
|
||
```TypeScript | ||
const foo = () => { | ||
`worklet`; | ||
console.log('Hello from ArrowFunctionExpression'); | ||
} | ||
``` | ||
|
||
- Function Declaration: | ||
|
||
```TypeScript | ||
function foo (){ | ||
`worklet`; | ||
console.log('Hello from FunctionDeclaration'); | ||
} | ||
``` | ||
|
||
- Function Expression: | ||
|
||
```TypeScript | ||
const foo = function () { | ||
`worklet`; | ||
console.log('Hello from FunctionExpression'); | ||
}; | ||
``` | ||
|
||
- Object Method: | ||
|
||
```TypeScript | ||
const obj = { | ||
foo() { | ||
`worklet`; | ||
console.log('Hello from ObjectMethod'); | ||
}, | ||
}; | ||
``` | ||
|
||
In addition, workletization will work with: | ||
|
||
- Sequence Expression (only last element): | ||
|
||
```TypeScript | ||
function foo() { | ||
(0, bar, foobar)({ // only foobar will get workletized! | ||
barfoo() { | ||
`worklet`; | ||
console.log('Hello from Sequence Expression'); | ||
}, | ||
}); | ||
} | ||
``` | ||
|
||
### Inline styles support | ||
|
||
For more information read [official docs](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/animations/#animations-in-inline-styles). | ||
|
||
### How to debug Reanimated Babel plugin? | ||
|
||
It's simple. After compilation we have generated `.js.map` file. We strongly recommend using **Visual Studio Code** and **JavaScript Debug Terminal**. Just open a new debugging session, type in your terminal (in project's root directory) `npx babel <filename>` and voilà. Add some breakpoints in `plugin/index.ts` or just use step-by-step tools. Some knowledge of JavaScript's AST and babel will be required to understand what exactly is happening during code transformation. |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.