@vxrn/compiler 1.25.13 → 1.25.16-1788753284749

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/dist/cjs/cache.cjs +3 -1
  2. package/dist/cjs/configure.cjs +9 -1
  3. package/dist/cjs/index.cjs +82 -4
  4. package/dist/cjs/reactNativeCodegen.cjs +140 -0
  5. package/dist/cjs/reactNativeViewConfig.cjs +489 -0
  6. package/dist/cjs/transformBabel.cjs +15 -20
  7. package/dist/cjs/transformBabel.test.cjs +260 -0
  8. package/dist/cjs/transformHermesLoops.cjs +317 -0
  9. package/dist/cjs/transformHermesLoops.test.cjs +220 -0
  10. package/dist/cjs/transformSWC.cjs +102 -137
  11. package/dist/cjs/transformSWC.test.cjs +74 -0
  12. package/dist/cjs/transformWorklets.cjs +121 -0
  13. package/dist/cjs/transformWorklets.test.cjs +604 -0
  14. package/dist/cjs/worklets/autoworklet.cjs +265 -0
  15. package/dist/cjs/worklets/globals.cjs +71 -0
  16. package/dist/cjs/worklets/hash.cjs +38 -0
  17. package/dist/cjs/worklets/scope.cjs +370 -0
  18. package/dist/cjs/worklets/serialize.cjs +101 -0
  19. package/dist/cjs/worklets/transform.cjs +232 -0
  20. package/dist/cjs/worklets/types.cjs +18 -0
  21. package/dist/esm/cache.mjs +4 -2
  22. package/dist/esm/cache.mjs.map +1 -1
  23. package/dist/esm/configure.mjs +8 -1
  24. package/dist/esm/configure.mjs.map +1 -1
  25. package/dist/esm/index.js +72 -5
  26. package/dist/esm/index.js.map +1 -1
  27. package/dist/esm/index.mjs +72 -5
  28. package/dist/esm/index.mjs.map +1 -1
  29. package/dist/esm/reactNativeCodegen.mjs +104 -0
  30. package/dist/esm/reactNativeCodegen.mjs.map +1 -0
  31. package/dist/esm/reactNativeViewConfig.mjs +460 -0
  32. package/dist/esm/reactNativeViewConfig.mjs.map +1 -0
  33. package/dist/esm/transformBabel.mjs +14 -19
  34. package/dist/esm/transformBabel.mjs.map +1 -1
  35. package/dist/esm/transformBabel.test.mjs +261 -1
  36. package/dist/esm/transformBabel.test.mjs.map +1 -1
  37. package/dist/esm/transformHermesLoops.mjs +281 -0
  38. package/dist/esm/transformHermesLoops.mjs.map +1 -0
  39. package/dist/esm/transformHermesLoops.test.mjs +197 -0
  40. package/dist/esm/transformHermesLoops.test.mjs.map +1 -0
  41. package/dist/esm/transformSWC.mjs +89 -138
  42. package/dist/esm/transformSWC.mjs.map +1 -1
  43. package/dist/esm/transformSWC.test.mjs +75 -0
  44. package/dist/esm/transformSWC.test.mjs.map +1 -0
  45. package/dist/esm/transformWorklets.mjs +81 -0
  46. package/dist/esm/transformWorklets.mjs.map +1 -0
  47. package/dist/esm/transformWorklets.test.mjs +581 -0
  48. package/dist/esm/transformWorklets.test.mjs.map +1 -0
  49. package/dist/esm/worklets/autoworklet.mjs +234 -0
  50. package/dist/esm/worklets/autoworklet.mjs.map +1 -0
  51. package/dist/esm/worklets/globals.mjs +45 -0
  52. package/dist/esm/worklets/globals.mjs.map +1 -0
  53. package/dist/esm/worklets/hash.mjs +13 -0
  54. package/dist/esm/worklets/hash.mjs.map +1 -0
  55. package/dist/esm/worklets/scope.mjs +345 -0
  56. package/dist/esm/worklets/scope.mjs.map +1 -0
  57. package/dist/esm/worklets/serialize.mjs +75 -0
  58. package/dist/esm/worklets/serialize.mjs.map +1 -0
  59. package/dist/esm/worklets/transform.mjs +196 -0
  60. package/dist/esm/worklets/transform.mjs.map +1 -0
  61. package/dist/esm/worklets/types.mjs +2 -0
  62. package/dist/esm/worklets/types.mjs.map +1 -0
  63. package/package.json +10 -6
  64. package/src/cache.ts +9 -2
  65. package/src/configure.ts +12 -0
  66. package/src/index.ts +93 -13
  67. package/src/reactNativeCodegen.ts +181 -0
  68. package/src/reactNativeViewConfig.ts +593 -0
  69. package/src/transformBabel.test.ts +268 -1
  70. package/src/transformBabel.ts +21 -31
  71. package/src/transformHermesLoops.test.ts +282 -0
  72. package/src/transformHermesLoops.ts +433 -0
  73. package/src/transformSWC.test.ts +88 -0
  74. package/src/transformSWC.ts +109 -160
  75. package/src/transformWorklets.test.ts +708 -0
  76. package/src/transformWorklets.ts +160 -0
  77. package/src/types.ts +4 -4
  78. package/src/worklets/autoworklet.ts +391 -0
  79. package/src/worklets/globals.ts +125 -0
  80. package/src/worklets/hash.ts +14 -0
  81. package/src/worklets/scope.ts +401 -0
  82. package/src/worklets/serialize.ts +114 -0
  83. package/src/worklets/transform.ts +282 -0
  84. package/src/worklets/types.ts +26 -0
  85. package/types/cache.d.ts +1 -0
  86. package/types/cache.d.ts.map +1 -1
  87. package/types/configure.d.ts +2 -0
  88. package/types/configure.d.ts.map +1 -1
  89. package/types/index.d.ts +3 -0
  90. package/types/index.d.ts.map +1 -1
  91. package/types/reactNativeCodegen.d.ts +5 -0
  92. package/types/reactNativeCodegen.d.ts.map +1 -0
  93. package/types/reactNativeViewConfig.d.ts +35 -0
  94. package/types/reactNativeViewConfig.d.ts.map +1 -0
  95. package/types/transformBabel.d.ts +3 -3
  96. package/types/transformBabel.d.ts.map +1 -1
  97. package/types/transformHermesLoops.d.ts +5 -0
  98. package/types/transformHermesLoops.d.ts.map +1 -0
  99. package/types/transformHermesLoops.test.d.ts +2 -0
  100. package/types/transformHermesLoops.test.d.ts.map +1 -0
  101. package/types/transformSWC.d.ts +12 -3
  102. package/types/transformSWC.d.ts.map +1 -1
  103. package/types/transformSWC.test.d.ts +2 -0
  104. package/types/transformSWC.test.d.ts.map +1 -0
  105. package/types/transformWorklets.d.ts +22 -0
  106. package/types/transformWorklets.d.ts.map +1 -0
  107. package/types/transformWorklets.test.d.ts +2 -0
  108. package/types/transformWorklets.test.d.ts.map +1 -0
  109. package/types/types.d.ts +4 -4
  110. package/types/types.d.ts.map +1 -1
  111. package/types/worklets/autoworklet.d.ts +14 -0
  112. package/types/worklets/autoworklet.d.ts.map +1 -0
  113. package/types/worklets/globals.d.ts +3 -0
  114. package/types/worklets/globals.d.ts.map +1 -0
  115. package/types/worklets/hash.d.ts +5 -0
  116. package/types/worklets/hash.d.ts.map +1 -0
  117. package/types/worklets/scope.d.ts +7 -0
  118. package/types/worklets/scope.d.ts.map +1 -0
  119. package/types/worklets/serialize.d.ts +12 -0
  120. package/types/worklets/serialize.d.ts.map +1 -0
  121. package/types/worklets/transform.d.ts +6 -0
  122. package/types/worklets/transform.d.ts.map +1 -0
  123. package/types/worklets/types.d.ts +21 -0
  124. package/types/worklets/types.d.ts.map +1 -0
@@ -0,0 +1,160 @@
1
+ import fs from 'node:fs'
2
+ import { createRequire } from 'node:module'
3
+ import path from 'node:path'
4
+ import { configuration, isNativeWorkletsEnabled } from './configure'
5
+ import { AUTOWORKLET_FUNCTION_ARGS, GESTURE_BUILDER_METHODS } from './worklets/autoworklet'
6
+
7
+ // every callee the transform auto-workletizes has to be in this gate, or files
8
+ // whose only worklets come from gesture callbacks (`.onBegin`, `.onEnd`,
9
+ // `.onTouchesMove`) are skipped before the transform ever sees them and ship
10
+ // untransformed.
11
+ export const REANIMATED_AUTOWORKLETIZATION_KEYWORDS = [
12
+ 'worklet',
13
+ 'useAnimatedGestureHandler',
14
+ 'useAnimatedScrollHandler',
15
+ 'useFrameCallback',
16
+ 'useAnimatedStyle',
17
+ 'useAnimatedProps',
18
+ 'createAnimatedPropAdapter',
19
+ 'useDerivedValue',
20
+ 'useAnimatedReaction',
21
+ 'useWorkletCallback',
22
+ 'withTiming',
23
+ 'withSpring',
24
+ 'withDecay',
25
+ 'withRepeat',
26
+ 'runOnUI',
27
+ 'executeOnUIRuntimeSync',
28
+ ]
29
+
30
+ const REANIMATED_REGEX = new RegExp(
31
+ [
32
+ ...new Set([
33
+ ...REANIMATED_AUTOWORKLETIZATION_KEYWORDS,
34
+ ...Object.keys(AUTOWORKLET_FUNCTION_ARGS),
35
+ // gesture callbacks are only workletized on a `Gesture.<Kind>()` chain,
36
+ // but this file gate has to be permissive or a file whose only worklets
37
+ // are gesture callbacks never reaches the transform at all.
38
+ ...GESTURE_BUILDER_METHODS,
39
+ 'withCallback',
40
+ ]),
41
+ ]
42
+ .map((name) => `\\b${name}\\b`)
43
+ .join('|')
44
+ )
45
+
46
+ const REANIMATED_IGNORED_PATHS = [
47
+ 'react-native-prebuilt',
48
+ 'node_modules/.vxrn/react-native',
49
+ 'node_modules/react/',
50
+ 'node_modules/react-dom/',
51
+ 'node_modules/react-native/',
52
+ // react-native's own scoped packages are flow, and the worklets transform
53
+ // runs before flow stripping in the vite pipeline. they never hold app
54
+ // worklets, exactly like node_modules/react-native/ above.
55
+ 'node_modules/@react-native/',
56
+ 'node_modules/react-native-web/',
57
+ ]
58
+
59
+ const REANIMATED_IGNORED_PATHS_REGEX = new RegExp(REANIMATED_IGNORED_PATHS.join('|'))
60
+
61
+ export function shouldTransformWorklets({ id, code }: { id: string; code: string }) {
62
+ if (!isNativeWorkletsEnabled() || !configuration.enableReanimated) {
63
+ return false
64
+ }
65
+
66
+ const normalizedId = id.replace(/\\/g, '/')
67
+ if (REANIMATED_IGNORED_PATHS_REGEX.test(normalizedId)) {
68
+ return false
69
+ }
70
+
71
+ return REANIMATED_REGEX.test(code)
72
+ }
73
+
74
+ export function getWorkletsVersion(projectRoot?: string, filename?: string): string {
75
+ if (projectRoot) {
76
+ try {
77
+ const req = createRequire(path.resolve(projectRoot, 'package.json'))
78
+ // Look for react-native-worklets first (preferred in modern Worklets / Reanimated 4)
79
+ try {
80
+ const ver = req('react-native-worklets/package.json')?.version
81
+ if (ver) return ver
82
+ } catch {}
83
+ // Fall back to react-native-reanimated (Reanimated 3 where worklets is bundled inside)
84
+ try {
85
+ const ver = req('react-native-reanimated/package.json')?.version
86
+ if (ver) return ver
87
+ } catch {}
88
+ } catch {}
89
+
90
+ throw new Error(
91
+ `[@vxrn/compiler] Unable to resolve "react-native-worklets" or "react-native-reanimated" version from project root "${projectRoot}". ` +
92
+ `Please ensure react-native-worklets is installed in your project.`
93
+ )
94
+ }
95
+
96
+ // If no projectRoot, try resolving from filename if it exists on disk
97
+ if (filename && !filename.startsWith('\0') && fs.existsSync(filename)) {
98
+ try {
99
+ const req = createRequire(filename)
100
+ try {
101
+ const ver = req('react-native-worklets/package.json')?.version
102
+ if (ver) return ver
103
+ } catch {}
104
+ try {
105
+ const ver = req('react-native-reanimated/package.json')?.version
106
+ if (ver) return ver
107
+ } catch {}
108
+ } catch {}
109
+ }
110
+
111
+ // Fall back to process.cwd() or import.meta.url for synthetic / dev environments
112
+ const fallbacks = [
113
+ () => createRequire(path.resolve(process.cwd(), 'package.json')),
114
+ () => createRequire(import.meta.url),
115
+ ]
116
+
117
+ for (const getReq of fallbacks) {
118
+ try {
119
+ const req = getReq()
120
+ try {
121
+ const ver = req('react-native-worklets/package.json')?.version
122
+ if (ver) return ver
123
+ } catch {}
124
+ try {
125
+ const ver = req('react-native-reanimated/package.json')?.version
126
+ if (ver) return ver
127
+ } catch {}
128
+ } catch {}
129
+ }
130
+
131
+ const target = filename || process.cwd()
132
+ throw new Error(
133
+ `[@vxrn/compiler] Unable to resolve "react-native-worklets" or "react-native-reanimated" version from "${target}". ` +
134
+ `Please ensure react-native-worklets is installed in your project.`
135
+ )
136
+ }
137
+
138
+ export type TransformWorkletsOptions = {
139
+ projectRoot?: string
140
+ bundleMode?: boolean
141
+ disableInlineStylesWarning?: boolean
142
+ disableSourceMaps?: boolean
143
+ disableWorkletClasses?: boolean
144
+ globals?: string[]
145
+ relativeSourceLocation?: boolean
146
+ strictGlobal?: boolean
147
+ pluginVersion?: string
148
+ }
149
+
150
+ import { executeWorkletTransform } from './worklets/transform'
151
+
152
+ export async function transformWorklets(
153
+ id: string,
154
+ code: string,
155
+ sourceMaps = false,
156
+ options?: TransformWorkletsOptions
157
+ ): Promise<{ code: string; map?: any }> {
158
+ return executeWorkletTransform(id, code, sourceMaps, options, getWorkletsVersion)
159
+ }
160
+
package/src/types.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Options as SWCOptions } from '@swc/core'
1
+ import type { TransformOptions as OxcTransformOptions } from 'oxc-transform'
2
2
 
3
3
  export type Environment = 'ios' | 'android' | 'ssr' | 'client'
4
4
 
@@ -23,13 +23,13 @@ export type GetTransformProps = {
23
23
 
24
24
  export type GetTransform = (props: GetTransformProps) => GetTransformResponse
25
25
 
26
- export type GetTransformResponse = boolean | 'babel' | 'swc' | TransformOptions
26
+ export type GetTransformResponse = boolean | 'babel' | 'swc' | 'oxc' | TransformOptions
27
27
 
28
28
  export type TransformOptions = BabelTransformOptions | SWCTransformOptions
29
29
 
30
30
  export type SWCTransformOptions = {
31
- transform: 'swc'
32
- } & SWCOptions
31
+ transform: 'swc' | 'oxc'
32
+ } & OxcTransformOptions
33
33
 
34
34
  export type BabelTransformOptions = {
35
35
  transform: 'babel'
@@ -0,0 +1,391 @@
1
+ import type { WorkletCandidate } from './types'
2
+
3
+ // null-prototype: callee names are arbitrary source identifiers, so a plain
4
+ // object literal would resolve `constructor`, `toString` and friends off
5
+ // Object.prototype and hand back a non-iterable.
6
+ export const AUTOWORKLET_FUNCTION_ARGS: Record<string, number[]> = Object.assign(
7
+ Object.create(null),
8
+ {
9
+ useFrameCallback: [0],
10
+ useAnimatedStyle: [0],
11
+ useAnimatedProps: [0],
12
+ createAnimatedPropAdapter: [0],
13
+ useDerivedValue: [0],
14
+ useAnimatedScrollHandler: [0],
15
+ useAnimatedGestureHandler: [0],
16
+ useAnimatedReaction: [0, 1],
17
+ withTiming: [2],
18
+ withSpring: [2],
19
+ withDecay: [1],
20
+ withRepeat: [3],
21
+ runOnUI: [0],
22
+ executeOnUIRuntimeSync: [0],
23
+ scheduleOnUI: [0],
24
+ runOnUISync: [0],
25
+ runOnUIAsync: [0],
26
+ runOnRuntime: [1],
27
+ runOnRuntimeSync: [1],
28
+ runOnRuntimeAsync: [1],
29
+ scheduleOnRuntime: [1],
30
+ runOnRuntimeSyncWithId: [1],
31
+ scheduleOnRuntimeWithId: [1],
32
+ useTapGesture: [0],
33
+ usePanGesture: [0],
34
+ usePinchGesture: [0],
35
+ useRotationGesture: [0],
36
+ useFlingGesture: [0],
37
+ useLongPressGesture: [0],
38
+ useNativeGesture: [0],
39
+ useManualGesture: [0],
40
+ useHoverGesture: [0],
41
+ }
42
+ )
43
+
44
+ // gesture builder callbacks are NOT matched by name. `onChange`, `onStart` and
45
+ // `onEnd` are ordinary identifiers in plenty of unrelated code, and turning one
46
+ // of those callbacks into a worklet ships a function to the UI runtime that was
47
+ // never written for it. the callback only counts when the receiver is a
48
+ // `Gesture.<Kind>()` chain, which is what the babel plugin requires too.
49
+ export const GESTURE_BUILDER_METHODS = new Set([
50
+ 'onBegin',
51
+ 'onStart',
52
+ 'onEnd',
53
+ 'onFinalize',
54
+ 'onUpdate',
55
+ 'onChange',
56
+ 'onTouchesDown',
57
+ 'onTouchesMove',
58
+ 'onTouchesUp',
59
+ 'onTouchesCancelled',
60
+ ])
61
+
62
+ const GESTURE_KINDS = new Set([
63
+ 'Tap',
64
+ 'Pan',
65
+ 'Pinch',
66
+ 'Rotation',
67
+ 'Fling',
68
+ 'LongPress',
69
+ 'ForceTouch',
70
+ 'Native',
71
+ 'Manual',
72
+ 'Race',
73
+ 'Simultaneous',
74
+ 'Exclusive',
75
+ 'Hover',
76
+ ])
77
+
78
+ // `FadeIn.duration(300).withCallback(fn)` - same shape, same reason for gating
79
+ // on the chain root rather than on the method name.
80
+ const LAYOUT_ANIMATIONS = new Set(
81
+ `BounceIn BounceInDown BounceInLeft BounceInRight BounceInUp BounceOut BounceOutDown
82
+ BounceOutLeft BounceOutRight BounceOutUp FadeIn FadeInDown FadeInLeft FadeInRight FadeInUp
83
+ FadeOut FadeOutDown FadeOutLeft FadeOutRight FadeOutUp FlipInEasyX FlipInEasyY FlipInXDown
84
+ FlipInXUp FlipInYLeft FlipInYRight FlipOutEasyX FlipOutEasyY FlipOutXDown FlipOutXUp
85
+ FlipOutYLeft FlipOutYRight LightSpeedInLeft LightSpeedInRight LightSpeedOutLeft
86
+ LightSpeedOutRight PinwheelIn PinwheelOut RollInLeft RollInRight RollOutLeft RollOutRight
87
+ RotateInDownLeft RotateInDownRight RotateInUpLeft RotateInUpRight RotateOutDownLeft
88
+ RotateOutDownRight RotateOutUpLeft RotateOutUpRight SlideInDown SlideInLeft SlideInRight
89
+ SlideInUp SlideOutDown SlideOutLeft SlideOutRight SlideOutUp StretchInX StretchInY
90
+ StretchOutX StretchOutY ZoomIn ZoomInDown ZoomInEasyDown ZoomInEasyUp ZoomInLeft ZoomInRight
91
+ ZoomInRotate ZoomInUp ZoomOut ZoomOutDown ZoomOutEasyDown ZoomOutEasyUp ZoomOutLeft
92
+ ZoomOutRight ZoomOutRotate ZoomOutUp Layout LinearTransition SequencedTransition
93
+ FadingTransition JumpingTransition CurvedTransition EntryExitTransition`.split(/\s+/)
94
+ )
95
+
96
+ const LAYOUT_ANIMATION_CHAIN_METHODS = new Set([
97
+ 'build',
98
+ 'duration',
99
+ 'delay',
100
+ 'getDuration',
101
+ 'randomDelay',
102
+ 'getDelay',
103
+ 'getDelayFunction',
104
+ 'easing',
105
+ 'rotate',
106
+ 'springify',
107
+ 'damping',
108
+ 'mass',
109
+ 'stiffness',
110
+ 'overshootClamping',
111
+ 'energyThreshold',
112
+ 'restDisplacementThreshold',
113
+ 'restSpeedThreshold',
114
+ 'withInitialValues',
115
+ 'getAnimationAndConfig',
116
+ 'easingX',
117
+ 'easingY',
118
+ 'easingWidth',
119
+ 'easingHeight',
120
+ 'entering',
121
+ 'exiting',
122
+ 'reverse',
123
+ ])
124
+
125
+ function isGestureObject(exp: any): boolean {
126
+ return (
127
+ exp?.type === 'CallExpression' &&
128
+ exp.callee?.type === 'MemberExpression' &&
129
+ exp.callee.object?.type === 'Identifier' &&
130
+ exp.callee.object.name === 'Gesture' &&
131
+ exp.callee.property?.type === 'Identifier' &&
132
+ GESTURE_KINDS.has(exp.callee.property.name)
133
+ )
134
+ }
135
+
136
+ function containsGestureObject(exp: any): boolean {
137
+ if (isGestureObject(exp)) return true
138
+ return (
139
+ exp?.type === 'CallExpression' &&
140
+ exp.callee?.type === 'MemberExpression' &&
141
+ containsGestureObject(exp.callee.object)
142
+ )
143
+ }
144
+
145
+ function isLayoutAnimationChain(exp: any): boolean {
146
+ if (exp?.type === 'Identifier' && LAYOUT_ANIMATIONS.has(exp.name)) return true
147
+ if (
148
+ exp?.type === 'NewExpression' &&
149
+ exp.callee?.type === 'Identifier' &&
150
+ LAYOUT_ANIMATIONS.has(exp.callee.name)
151
+ ) {
152
+ return true
153
+ }
154
+ return (
155
+ exp?.type === 'CallExpression' &&
156
+ exp.callee?.type === 'MemberExpression' &&
157
+ exp.callee.property?.type === 'Identifier' &&
158
+ LAYOUT_ANIMATION_CHAIN_METHODS.has(exp.callee.property.name) &&
159
+ isLayoutAnimationChain(exp.callee.object)
160
+ )
161
+ }
162
+
163
+ // true when every argument of the call should be workletized rather than the
164
+ // indices a named hook declares.
165
+ function isChainedCallback(callee: any): boolean {
166
+ if (callee?.type !== 'MemberExpression' || callee.property?.type !== 'Identifier') {
167
+ return false
168
+ }
169
+ const name = callee.property.name
170
+ if (GESTURE_BUILDER_METHODS.has(name)) return containsGestureObject(callee.object)
171
+ if (name === 'withCallback') return isLayoutAnimationChain(callee.object)
172
+ return false
173
+ }
174
+
175
+ // the worklets directives, in the order the runtime expects to find them.
176
+ export const WORKLET_DIRECTIVES = ['worklet', 'no-worklet-closure', 'limit-init-data-hoisting']
177
+
178
+ function statementDirective(stmt: any): string | undefined {
179
+ if (!stmt || stmt.type !== 'ExpressionStatement') return undefined
180
+ if (typeof stmt.directive === 'string') return stmt.directive
181
+ if (stmt.expression?.type === 'Literal' && typeof stmt.expression.value === 'string') {
182
+ return stmt.expression.value
183
+ }
184
+ return undefined
185
+ }
186
+
187
+ export function hasDirective(fnNode: any, directive: string): boolean {
188
+ if (!fnNode || !fnNode.body) return false
189
+
190
+ // Directives array in Oxc
191
+ if (Array.isArray(fnNode.body.directives)) {
192
+ for (const d of fnNode.body.directives) {
193
+ if (d.directive === directive || d.value === directive) {
194
+ return true
195
+ }
196
+ }
197
+ }
198
+
199
+ // leading directive statements in the body
200
+ if (fnNode.body.type === 'BlockStatement' && Array.isArray(fnNode.body.body)) {
201
+ for (const stmt of fnNode.body.body) {
202
+ const found = statementDirective(stmt)
203
+ if (found === undefined) break
204
+ if (found === directive) return true
205
+ }
206
+ }
207
+
208
+ return false
209
+ }
210
+
211
+ export function hasWorkletDirective(fnNode: any): boolean {
212
+ return hasDirective(fnNode, 'worklet')
213
+ }
214
+
215
+ /**
216
+ * Source offset just past every leading worklets directive in the body, so a
217
+ * body slice drops all of them rather than only the first. Returns the offset
218
+ * of the first real statement.
219
+ */
220
+ export function bodyStartAfterDirectives(fnNode: any, code: string): number {
221
+ let start = fnNode.body.start + 1
222
+ if (fnNode.body.type !== 'BlockStatement' || !Array.isArray(fnNode.body.body)) return start
223
+ for (const stmt of fnNode.body.body) {
224
+ const found = statementDirective(stmt)
225
+ if (found === undefined || !WORKLET_DIRECTIVES.includes(found)) break
226
+ start = stmt.end
227
+ while (start < fnNode.body.end && (code[start] === ';' || /\s/.test(code[start]))) {
228
+ start++
229
+ }
230
+ }
231
+ return start
232
+ }
233
+
234
+ export function findWorkletCandidates(program: any): WorkletCandidate[] {
235
+ const candidates: WorkletCandidate[] = []
236
+ const seenStarts = new Set<number>()
237
+
238
+ function addCandidate(candidate: WorkletCandidate) {
239
+ const fnStart = candidate.fnNode.start
240
+ if (seenStarts.has(fnStart)) return
241
+ seenStarts.add(fnStart)
242
+ candidates.push(candidate)
243
+ }
244
+
245
+ function getCalleeName(callee: any): string | null {
246
+ if (!callee) return null
247
+ if (callee.type === 'Identifier') {
248
+ return callee.name
249
+ }
250
+ if (
251
+ callee.type === 'MemberExpression' &&
252
+ !callee.computed &&
253
+ callee.property?.type === 'Identifier'
254
+ ) {
255
+ return callee.property.name
256
+ }
257
+ return null
258
+ }
259
+
260
+ function walk(node: any, parent: any) {
261
+ if (!node || typeof node !== 'object') return
262
+
263
+ // Check explicit worklets
264
+ if (
265
+ node.type === 'FunctionDeclaration' ||
266
+ node.type === 'FunctionExpression' ||
267
+ node.type === 'ArrowFunctionExpression'
268
+ ) {
269
+ if (hasWorkletDirective(node)) {
270
+ let kind: WorkletCandidate['kind'] = 'function_expression'
271
+ if (node.type === 'FunctionDeclaration') kind = 'function_declaration'
272
+ else if (node.type === 'ArrowFunctionExpression') kind = 'arrow_function'
273
+
274
+ let name = node.id?.name
275
+ if (!name && parent?.type === 'VariableDeclarator' && parent.id?.name) {
276
+ name = parent.id.name
277
+ }
278
+
279
+ addCandidate({
280
+ node,
281
+ fnNode: node,
282
+ kind,
283
+ name,
284
+ parent,
285
+ isAutoWorklet: false,
286
+ })
287
+ }
288
+ } else if (
289
+ node.type === 'Property' &&
290
+ (node.method || node.value?.type === 'FunctionExpression' || node.value?.type === 'ArrowFunctionExpression')
291
+ ) {
292
+ const fn = node.value
293
+ if (hasWorkletDirective(fn)) {
294
+ addCandidate({
295
+ node: node.method ? node : fn,
296
+ fnNode: fn,
297
+ kind: node.method
298
+ ? 'object_method'
299
+ : fn.type === 'ArrowFunctionExpression'
300
+ ? 'arrow_function'
301
+ : 'function_expression',
302
+ name: node.key?.name,
303
+ parent,
304
+ isAutoWorklet: false,
305
+ })
306
+ }
307
+ }
308
+
309
+ // Check auto-workletized calls
310
+ if (node.type === 'CallExpression') {
311
+ const calleeName = getCalleeName(node.callee)
312
+ const argIndices = calleeName ? AUTOWORKLET_FUNCTION_ARGS[calleeName] : undefined
313
+ const chained = !argIndices && isChainedCallback(node.callee)
314
+ const indices =
315
+ argIndices ?? (chained ? node.arguments.map((_: unknown, i: number) => i) : undefined)
316
+ if (indices) {
317
+ for (const idx of indices) {
318
+ const arg = node.arguments[idx]
319
+ if (!arg) continue
320
+
321
+ if (
322
+ arg.type === 'ArrowFunctionExpression' ||
323
+ arg.type === 'FunctionExpression'
324
+ ) {
325
+ let name = arg.id?.name
326
+ if (!name && parent?.type === 'VariableDeclarator' && parent.id?.name) {
327
+ name = parent.id.name
328
+ }
329
+ addCandidate({
330
+ node: arg,
331
+ fnNode: arg,
332
+ kind:
333
+ arg.type === 'ArrowFunctionExpression'
334
+ ? 'arrow_function'
335
+ : 'function_expression',
336
+ name,
337
+ parent: node,
338
+ isAutoWorklet: true,
339
+ })
340
+ } else if (arg.type === 'ObjectExpression' && Array.isArray(arg.properties)) {
341
+ for (const prop of arg.properties) {
342
+ if (prop.type === 'Property') {
343
+ if (prop.method) {
344
+ addCandidate({
345
+ node: prop,
346
+ fnNode: prop.value,
347
+ kind: 'object_method',
348
+ name: prop.key?.name,
349
+ parent: arg,
350
+ isAutoWorklet: true,
351
+ })
352
+ } else if (
353
+ prop.value?.type === 'ArrowFunctionExpression' ||
354
+ prop.value?.type === 'FunctionExpression'
355
+ ) {
356
+ addCandidate({
357
+ node: prop.value,
358
+ fnNode: prop.value,
359
+ kind:
360
+ prop.value.type === 'ArrowFunctionExpression'
361
+ ? 'arrow_function'
362
+ : 'function_expression',
363
+ name: prop.key?.name,
364
+ parent: prop,
365
+ isAutoWorklet: true,
366
+ })
367
+ }
368
+ }
369
+ }
370
+ }
371
+ }
372
+ }
373
+ }
374
+
375
+ // Default traversal
376
+ for (const key of Object.keys(node)) {
377
+ if (key === 'start' || key === 'end' || key === 'loc' || key === 'range') continue
378
+ const child = node[key]
379
+ if (Array.isArray(child)) {
380
+ for (const item of child) {
381
+ walk(item, node)
382
+ }
383
+ } else if (child && typeof child === 'object') {
384
+ walk(child, node)
385
+ }
386
+ }
387
+ }
388
+
389
+ walk(program, null)
390
+ return candidates
391
+ }
@@ -0,0 +1,125 @@
1
+ export const DEFAULT_GLOBALS = new Set([
2
+ // Value properties
3
+ 'globalThis',
4
+ 'Infinity',
5
+ 'NaN',
6
+ 'undefined',
7
+ // Function properties
8
+ 'eval',
9
+ 'isFinite',
10
+ 'isNaN',
11
+ 'parseFloat',
12
+ 'parseInt',
13
+ 'decodeURI',
14
+ 'decodeURIComponent',
15
+ 'encodeURI',
16
+ 'encodeURIComponent',
17
+ 'escape',
18
+ 'unescape',
19
+ // Fundamental objects
20
+ 'Object',
21
+ 'Function',
22
+ 'Boolean',
23
+ 'Symbol',
24
+ // Error objects
25
+ 'Error',
26
+ 'AggregateError',
27
+ 'EvalError',
28
+ 'RangeError',
29
+ 'ReferenceError',
30
+ 'SyntaxError',
31
+ 'TypeError',
32
+ 'URIError',
33
+ 'InternalError',
34
+ // Numbers and dates
35
+ 'Number',
36
+ 'BigInt',
37
+ 'Math',
38
+ 'Date',
39
+ // Text processing
40
+ 'String',
41
+ 'RegExp',
42
+ // Indexed collections
43
+ 'Array',
44
+ 'Int8Array',
45
+ 'Uint8Array',
46
+ 'Uint8ClampedArray',
47
+ 'Int16Array',
48
+ 'Uint16Array',
49
+ 'Int32Array',
50
+ 'Uint32Array',
51
+ 'BigInt64Array',
52
+ 'BigUint64Array',
53
+ 'Float32Array',
54
+ 'Float64Array',
55
+ // Keyed collections
56
+ 'Map',
57
+ 'Set',
58
+ 'WeakMap',
59
+ 'WeakSet',
60
+ // Structured data
61
+ 'ArrayBuffer',
62
+ 'SharedArrayBuffer',
63
+ 'DataView',
64
+ 'Atomics',
65
+ 'JSON',
66
+ // Managing memory
67
+ 'WeakRef',
68
+ 'FinalizationRegistry',
69
+ // Control abstraction objects
70
+ 'Iterator',
71
+ 'AsyncIterator',
72
+ 'Promise',
73
+ 'GeneratorFunction',
74
+ 'AsyncGeneratorFunction',
75
+ 'Generator',
76
+ 'AsyncGenerator',
77
+ 'AsyncFunction',
78
+ // Reflection
79
+ 'Reflect',
80
+ 'Proxy',
81
+ // Internationalization
82
+ 'Intl',
83
+ // Environment / Runtime
84
+ 'null',
85
+ 'this',
86
+ 'global',
87
+ 'window',
88
+ 'self',
89
+ 'console',
90
+ 'performance',
91
+ 'arguments',
92
+ 'require',
93
+ 'fetch',
94
+ 'XMLHttpRequest',
95
+ 'WebSocket',
96
+ 'queueMicrotask',
97
+ 'requestAnimationFrame',
98
+ 'cancelAnimationFrame',
99
+ 'setTimeout',
100
+ 'clearTimeout',
101
+ 'setImmediate',
102
+ 'clearImmediate',
103
+ 'setInterval',
104
+ 'clearInterval',
105
+ 'HermesInternal',
106
+ '_WORKLET',
107
+ ])
108
+
109
+ export function createGlobalsSet(
110
+ customGlobals?: string[],
111
+ strictGlobal?: boolean
112
+ ): Set<string> {
113
+ const set = new Set<string>()
114
+ if (!strictGlobal) {
115
+ for (const g of DEFAULT_GLOBALS) {
116
+ set.add(g)
117
+ }
118
+ }
119
+ if (customGlobals) {
120
+ for (const g of customGlobals) {
121
+ set.add(g)
122
+ }
123
+ }
124
+ return set
125
+ }