@vxrn/compiler 2.0.0-beta.33.1 → 2.0.0-beta.362.1

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 (115) hide show
  1. package/dist/cjs/cache.cjs +112 -109
  2. package/dist/cjs/configure.cjs +26 -28
  3. package/dist/cjs/constants.cjs +56 -60
  4. package/dist/cjs/index.cjs +428 -438
  5. package/dist/cjs/reactNativeCodegen.cjs +105 -114
  6. package/dist/cjs/reactNativeViewConfig.cjs +362 -381
  7. package/dist/cjs/refresh-runtime.cjs +390 -397
  8. package/dist/cjs/transformBabel.cjs +247 -221
  9. package/dist/cjs/transformHermesAsync.cjs +36 -47
  10. package/dist/cjs/transformHermesClasses.cjs +51 -0
  11. package/dist/cjs/transformHermesLoops.cjs +279 -281
  12. package/dist/cjs/transformSWC.cjs +183 -200
  13. package/dist/cjs/transformWorklets.cjs +107 -95
  14. package/dist/cjs/types.cjs +9 -11
  15. package/dist/cjs/workletUtils.cjs +30 -0
  16. package/dist/cjs/worklets/autoworklet.cjs +277 -222
  17. package/dist/cjs/worklets/globals.cjs +145 -60
  18. package/dist/cjs/worklets/hash.cjs +23 -27
  19. package/dist/cjs/worklets/scope.cjs +338 -359
  20. package/dist/cjs/worklets/serialize.cjs +73 -75
  21. package/dist/cjs/worklets/transform.cjs +217 -222
  22. package/dist/cjs/worklets/types.cjs +9 -11
  23. package/dist/esm/cache.mjs +96 -89
  24. package/dist/esm/cache.mjs.map +1 -1
  25. package/dist/esm/configure.mjs +11 -10
  26. package/dist/esm/configure.mjs.map +1 -1
  27. package/dist/esm/constants.mjs +41 -41
  28. package/dist/esm/constants.mjs.map +1 -1
  29. package/dist/esm/index.js +426 -420
  30. package/dist/esm/index.js.map +1 -1
  31. package/dist/esm/index.mjs +426 -420
  32. package/dist/esm/index.mjs.map +1 -1
  33. package/dist/esm/reactNativeCodegen.mjs +90 -88
  34. package/dist/esm/reactNativeCodegen.mjs.map +1 -1
  35. package/dist/esm/reactNativeViewConfig.mjs +347 -362
  36. package/dist/esm/reactNativeViewConfig.mjs.map +1 -1
  37. package/dist/esm/refresh-runtime.mjs +372 -376
  38. package/dist/esm/refresh-runtime.mjs.map +1 -1
  39. package/dist/esm/transformBabel.mjs +231 -198
  40. package/dist/esm/transformBabel.mjs.map +1 -1
  41. package/dist/esm/transformHermesAsync.mjs +20 -21
  42. package/dist/esm/transformHermesAsync.mjs.map +1 -1
  43. package/dist/esm/transformHermesClasses.mjs +25 -0
  44. package/dist/esm/transformHermesClasses.mjs.map +1 -0
  45. package/dist/esm/transformHermesLoops.mjs +264 -255
  46. package/dist/esm/transformHermesLoops.mjs.map +1 -1
  47. package/dist/esm/transformSWC.mjs +163 -171
  48. package/dist/esm/transformSWC.mjs.map +1 -1
  49. package/dist/esm/transformWorklets.mjs +89 -69
  50. package/dist/esm/transformWorklets.mjs.map +1 -1
  51. package/dist/esm/types.mjs +0 -2
  52. package/dist/esm/workletUtils.mjs +5 -0
  53. package/dist/esm/worklets/autoworklet.mjs +258 -200
  54. package/dist/esm/worklets/autoworklet.mjs.map +1 -1
  55. package/dist/esm/worklets/globals.mjs +130 -42
  56. package/dist/esm/worklets/globals.mjs.map +1 -1
  57. package/dist/esm/worklets/hash.mjs +10 -9
  58. package/dist/esm/worklets/hash.mjs.map +1 -1
  59. package/dist/esm/worklets/scope.mjs +325 -341
  60. package/dist/esm/worklets/scope.mjs.map +1 -1
  61. package/dist/esm/worklets/serialize.mjs +60 -58
  62. package/dist/esm/worklets/serialize.mjs.map +1 -1
  63. package/dist/esm/worklets/transform.mjs +201 -197
  64. package/dist/esm/worklets/transform.mjs.map +1 -1
  65. package/dist/esm/worklets/types.mjs +0 -2
  66. package/package.json +15 -6
  67. package/src/cache.ts +37 -7
  68. package/src/index.ts +44 -19
  69. package/src/reactNativeViewConfig.ts +6 -1
  70. package/src/transformBabel.test.ts +351 -17
  71. package/src/transformBabel.ts +137 -96
  72. package/src/transformHermesClasses.ts +34 -0
  73. package/src/transformSWC.ts +12 -1
  74. package/src/transformWorklets.test.ts +80 -5
  75. package/src/workletUtils.ts +4 -0
  76. package/src/worklets/autoworklet.ts +5 -2
  77. package/src/worklets/globals.ts +23 -0
  78. package/src/worklets/serialize.ts +1 -1
  79. package/types/cache.d.ts +2 -2
  80. package/types/cache.d.ts.map +1 -1
  81. package/types/index.d.ts +1 -0
  82. package/types/index.d.ts.map +1 -1
  83. package/types/reactNativeViewConfig.d.ts.map +1 -1
  84. package/types/transformBabel.d.ts +16 -1
  85. package/types/transformBabel.d.ts.map +1 -1
  86. package/types/transformHermesClasses.d.ts +5 -0
  87. package/types/transformHermesClasses.d.ts.map +1 -0
  88. package/types/transformSWC.d.ts +1 -4
  89. package/types/transformSWC.d.ts.map +1 -1
  90. package/types/workletUtils.d.ts +4 -0
  91. package/types/workletUtils.d.ts.map +1 -0
  92. package/types/worklets/autoworklet.d.ts.map +1 -1
  93. package/types/worklets/globals.d.ts.map +1 -1
  94. package/dist/cjs/transformBabel.test.cjs +0 -360
  95. package/dist/cjs/transformHermesLoops.test.cjs +0 -257
  96. package/dist/cjs/transformSWC.test.cjs +0 -117
  97. package/dist/cjs/transformWorklets.test.cjs +0 -604
  98. package/dist/esm/transformBabel.test.mjs +0 -337
  99. package/dist/esm/transformBabel.test.mjs.map +0 -1
  100. package/dist/esm/transformHermesLoops.test.mjs +0 -234
  101. package/dist/esm/transformHermesLoops.test.mjs.map +0 -1
  102. package/dist/esm/transformSWC.test.mjs +0 -118
  103. package/dist/esm/transformSWC.test.mjs.map +0 -1
  104. package/dist/esm/transformWorklets.test.mjs +0 -581
  105. package/dist/esm/transformWorklets.test.mjs.map +0 -1
  106. package/dist/esm/types.mjs.map +0 -1
  107. package/dist/esm/worklets/types.mjs.map +0 -1
  108. package/types/transformBabel.test.d.ts +0 -2
  109. package/types/transformBabel.test.d.ts.map +0 -1
  110. package/types/transformHermesLoops.test.d.ts +0 -2
  111. package/types/transformHermesLoops.test.d.ts.map +0 -1
  112. package/types/transformSWC.test.d.ts +0 -2
  113. package/types/transformSWC.test.d.ts.map +0 -1
  114. package/types/transformWorklets.test.d.ts +0 -2
  115. package/types/transformWorklets.test.d.ts.map +0 -1
@@ -1,4 +1,5 @@
1
- import { extname, relative } from 'node:path'
1
+ import { existsSync, readFileSync } from 'node:fs'
2
+ import { extname, join } from 'node:path'
2
3
  // type-only, so that importing this module does not drag babel in. every metro
3
4
  // worker loads it through the package index, and on the native transform path
4
5
  // babel is never called at all: loading it there is pure startup cost.
@@ -13,6 +14,38 @@ type Props = GetTransformProps & {
13
14
  userSetting?: GetTransformResponse
14
15
  }
15
16
 
17
+ const USER_BABEL_CONFIG_FILES = [
18
+ 'babel.config.js',
19
+ 'babel.config.cjs',
20
+ 'babel.config.mjs',
21
+ 'babel.config.json',
22
+ '.babelrc',
23
+ '.babelrc.js',
24
+ '.babelrc.json',
25
+ ] as const
26
+
27
+ // matches ONE_GENERATED_MARKER in one/src/cli/generateBundlerConfig.ts
28
+ const ONE_GENERATED_MARKER = '@one/generated bundler-config'
29
+
30
+ export function findUserBabelConfig(projectRoot?: string): string | null {
31
+ if (!projectRoot) return null
32
+ for (const name of USER_BABEL_CONFIG_FILES) {
33
+ const fullPath = join(projectRoot, name)
34
+ if (existsSync(fullPath)) {
35
+ try {
36
+ const content = readFileSync(fullPath, 'utf8')
37
+ if (content.includes(ONE_GENERATED_MARKER)) {
38
+ continue
39
+ }
40
+ } catch {
41
+ continue
42
+ }
43
+ return fullPath
44
+ }
45
+ }
46
+ return null
47
+ }
48
+
16
49
  export function getBabelOptions(props: Props): babel.TransformOptions | null {
17
50
  // unify caller contracts (the Vite plugin hands POSIX ids; the native/patches
18
51
  // path hands OS-native ids) so every path matcher below can assume forward
@@ -20,22 +53,50 @@ export function getBabelOptions(props: Props): babel.TransformOptions | null {
20
53
  // reason. without it, RN's own files aren't matched on Windows.
21
54
  props = { ...props, id: normalizePath(props.id.split('?')[0]) }
22
55
 
56
+ const isProjectFile = !props.id.includes('node_modules')
57
+ const userBabelConfig =
58
+ isProjectFile && props.projectRoot ? findUserBabelConfig(props.projectRoot) : null
59
+
23
60
  if (props.userSetting === 'babel') {
24
- return getOptions(props, true)
61
+ return getOptions(props, true, userBabelConfig)
25
62
  }
26
63
  if (
27
64
  typeof props.userSetting === 'undefined' ||
28
65
  (typeof props.userSetting === 'object' && props.userSetting.transform === 'babel')
29
66
  ) {
30
67
  if (props.userSetting?.excludeDefaultPlugins) {
31
- return props.userSetting
68
+ return {
69
+ caller: getBabelCaller(props),
70
+ ...props.userSetting,
71
+ ...(userBabelConfig ? { configFile: userBabelConfig, babelrc: true } : {}),
72
+ }
32
73
  }
33
- return getOptions(props)
74
+ return getOptions(props, false, userBabelConfig)
75
+ }
76
+ // an explicit per-file opt-out of babel survives a user config. without
77
+ // this, merely adding babel.config.js flips swc/oxc files to babel.
78
+ const userSetting = props.userSetting
79
+ if (
80
+ userSetting === 'swc' ||
81
+ userSetting === 'oxc' ||
82
+ userSetting === false ||
83
+ (typeof userSetting === 'object' &&
84
+ userSetting !== null &&
85
+ (userSetting.transform === 'swc' || userSetting.transform === 'oxc'))
86
+ ) {
87
+ return null
88
+ }
89
+ if (userBabelConfig) {
90
+ return getOptions(props, false, userBabelConfig)
34
91
  }
35
92
  return null
36
93
  }
37
94
 
38
- const getOptions = (props: Props, force = false): babel.TransformOptions | null => {
95
+ const getOptions = (
96
+ props: Props,
97
+ force = false,
98
+ userBabelConfig: string | null = null
99
+ ): babel.TransformOptions | null => {
39
100
  let plugins: babel.PluginItem[] = []
40
101
 
41
102
  if (force || shouldBabelGenerators(props)) {
@@ -56,29 +117,37 @@ const getOptions = (props: Props, force = false): babel.TransformOptions | null
56
117
  }
57
118
  }
58
119
 
59
- if (enableNativewind || shouldBabelReanimated(props)) {
60
- try {
61
- const workletsPlugin = resolvePath(
62
- 'react-native-worklets/plugin',
63
- props.projectRoot
64
- )
65
- debug?.(`Using babel worklets on file ${props.id}`)
66
- plugins.push(workletsPlugin)
67
- } catch {}
68
- }
69
-
70
120
  if (shouldBabelReactCompiler(props)) {
71
121
  debug?.(`Using babel react compiler on file`)
72
122
  plugins.push(getBabelReactCompilerPlugin(props))
73
123
  }
74
124
 
75
- if (plugins.length) {
76
- return { plugins }
125
+ if (plugins.length || userBabelConfig) {
126
+ return {
127
+ plugins,
128
+ caller: getBabelCaller(props),
129
+ ...(userBabelConfig ? { configFile: userBabelConfig, babelrc: true } : {}),
130
+ }
77
131
  }
78
132
 
79
133
  return null
80
134
  }
81
135
 
136
+ function getBabelCaller(props: Props): babel.TransformOptions['caller'] {
137
+ // babel hands every caller field to presets (babel-preset-expo reads platform
138
+ // and isDev), but its types list only the esm support flags, so the extra
139
+ // fields go through a named value rather than a checked literal.
140
+ const caller = {
141
+ name: 'vxrn',
142
+ platform:
143
+ props.environment === 'ios' || props.environment === 'android'
144
+ ? props.environment
145
+ : 'web',
146
+ isDev: props.development,
147
+ }
148
+ return caller
149
+ }
150
+
82
151
  /**
83
152
  * Run the react compiler through oxc's rust port instead of babel.
84
153
  *
@@ -91,14 +160,27 @@ const getOptions = (props: Props, force = false): babel.TransformOptions | null
91
160
  export async function transformOxcReactCompiler(
92
161
  id: string,
93
162
  code: string,
94
- target: '18' | '19',
163
+ optionsOrTarget: '18' | '19' | (Record<string, any> & { target?: '18' | '19' }) = '19',
95
164
  sourceMap = false
96
165
  ) {
97
166
  const { transform } = await import('oxc-transform-react')
167
+ const compilerOptions =
168
+ typeof optionsOrTarget === 'string'
169
+ ? { target: optionsOrTarget }
170
+ : optionsOrTarget || {}
171
+
172
+ const target = compilerOptions.target ?? '19'
98
173
  const result = await transform(id, code, {
99
174
  jsx: 'preserve',
100
175
  sourcemap: sourceMap,
101
- reactCompiler: { target },
176
+ reactCompiler: {
177
+ target,
178
+ ...compilerOptions,
179
+ environment: {
180
+ enableCustomTypeDefinitionForReanimated: true,
181
+ ...compilerOptions.environment,
182
+ },
183
+ },
102
184
  })
103
185
 
104
186
  // `errors` with fatal:false are react compiler BAILOUTS ("Cannot access refs
@@ -119,7 +201,10 @@ export async function transformOxcReactCompiler(
119
201
  )
120
202
  }
121
203
 
122
- return { code: result.code, map: sourceMap ? (result.map as any) : undefined }
204
+ return {
205
+ code: result.code,
206
+ map: sourceMap ? (result.map as any) : undefined,
207
+ }
123
208
  }
124
209
 
125
210
  /**
@@ -137,14 +222,30 @@ export async function transformBabel(
137
222
  const extension = extname(id)
138
223
  const isTSX = extension === '.tsx'
139
224
  const isTS = isTSX || extension === '.ts'
140
- const babelOptions = {
225
+ const babelOptions: babel.TransformOptions = {
141
226
  filename: id,
142
227
  compact: false,
143
- babelrc: false,
144
- configFile: false,
228
+ babelrc: options.babelrc ?? false,
229
+ configFile: options.configFile ?? false,
145
230
  sourceMaps: false,
146
231
  minified: false,
147
232
  ...options,
233
+ ...(!isTS
234
+ ? {
235
+ parserOpts: {
236
+ ...options.parserOpts,
237
+ plugins: [...(options.parserOpts?.plugins || []), 'jsx'],
238
+ },
239
+ }
240
+ : {}),
241
+ // vite and rolldown own module syntax and import.meta, so presets written for
242
+ // metro (babel-preset-expo) must keep esm instead of rewriting it for metro's runtime
243
+ caller: {
244
+ name: 'vxrn',
245
+ ...options.caller,
246
+ supportsStaticESM: true,
247
+ supportsDynamicImport: true,
248
+ },
148
249
  presets: [
149
250
  isTS
150
251
  ? [
@@ -168,7 +269,10 @@ export async function transformBabel(
168
269
  plugins: [
169
270
  ...(isTS
170
271
  ? []
171
- : [[hermesParserPlugin, { parseLangTypes: 'flow', reactRuntimeTarget: '19' }]]),
272
+ : [
273
+ [hermesParserPlugin, { parseLangTypes: 'flow', reactRuntimeTarget: '19' }],
274
+ 'babel-plugin-transform-flow-enums',
275
+ ]),
172
276
  ...(options.plugins || []),
173
277
  ...(isTS ? [] : ['@babel/plugin-transform-flow-strip-types']),
174
278
  ],
@@ -184,6 +288,14 @@ export async function transformBabel(
184
288
  })
185
289
  }
186
290
 
291
+ export async function stripFlowTypes(id: string, code: string, sourceMaps = true) {
292
+ const result = await transformBabel(id, code, { sourceMaps })
293
+ return {
294
+ code: result.code!,
295
+ map: result.map,
296
+ }
297
+ }
298
+
187
299
  const getBasePlugins = ({ development }: Props) =>
188
300
  [
189
301
  ['@babel/plugin-transform-destructuring'],
@@ -294,74 +406,3 @@ function shouldBabelGenerators({ code }: Props) {
294
406
  return asyncGeneratorRegex.test(code)
295
407
  }
296
408
  }
297
-
298
- /**
299
- * ------- reanimated --------
300
- */
301
-
302
- /**
303
- * Taken from https://github.com/software-mansion/react-native-reanimated/blob/3.15.1/packages/react-native-reanimated/plugin/src/autoworkletization.ts#L19-L59, need to check if this is up-to-date when supporting newer versions of react-native-reanimated.
304
- */
305
- const REANIMATED_AUTOWORKLETIZATION_KEYWORDS = [
306
- 'worklet',
307
- 'useAnimatedGestureHandler',
308
- 'useAnimatedScrollHandler',
309
- 'useFrameCallback',
310
- 'useAnimatedStyle',
311
- 'useAnimatedProps',
312
- 'createAnimatedPropAdapter',
313
- 'useDerivedValue',
314
- 'useAnimatedReaction',
315
- 'useWorkletCallback',
316
- 'withTiming',
317
- 'withSpring',
318
- 'withDecay',
319
- 'withRepeat',
320
- 'runOnUI',
321
- 'executeOnUIRuntimeSync',
322
- ]
323
-
324
- /**
325
- * Regex to test if a piece of code should be processed by react-native-reanimated's Babel plugin.
326
- */
327
- const REANIMATED_REGEX = new RegExp(REANIMATED_AUTOWORKLETIZATION_KEYWORDS.join('|'))
328
-
329
- // Packages to skip for reanimated babel transform
330
- // These either have false positives (mention keywords but don't use worklets)
331
- // or cause issues when transformed
332
- const REANIMATED_IGNORED_PATHS = [
333
- // Prebuilt/vendored react-native that shouldn't be transformed
334
- 'react-native-prebuilt',
335
- 'node_modules/.vxrn/react-native',
336
- // Known false positives - they mention worklet keywords in comments/strings but don't use them
337
- 'node_modules/react/',
338
- 'node_modules/react-dom/',
339
- 'node_modules/react-native/',
340
- 'node_modules/react-native-web/',
341
- ]
342
-
343
- // `id`s are normalized to forward slashes at getBabelOptions' entry, so these
344
- // plain forward-slash paths match on every OS. before that normalization the
345
- // backslash `id`s Windows hands us slipped past this list, pushing react-native's
346
- // own files through the reanimated babel pass, which has no JSX/TS parser.
347
- const REANIMATED_IGNORED_PATHS_REGEX = new RegExp(REANIMATED_IGNORED_PATHS.join('|'))
348
-
349
- function shouldBabelReanimated({ code, id }: Props) {
350
- if (!configuration.enableReanimated) {
351
- return false
352
- }
353
-
354
- // Check if path should be ignored
355
- if (REANIMATED_IGNORED_PATHS_REGEX.test(id)) {
356
- return false
357
- }
358
-
359
- // Check regex for all files (both node_modules and user code)
360
- if (REANIMATED_REGEX.test(code)) {
361
- const location = id.includes('node_modules') ? 'node_modules' : 'user-code'
362
- debug?.(` 🪄 [reanimated/${location}] ${relative(process.cwd(), id)}`)
363
- return true
364
- }
365
-
366
- return false
367
- }
@@ -0,0 +1,34 @@
1
+ // Hermes before V1 cannot parse ES6 classes. Oxc lowers fields but has an
2
+ // ES2015 target floor, so classes and lexical bindings need Babel afterward.
3
+ export async function transformHermesClasses(
4
+ code: string,
5
+ filename: string,
6
+ sourceMaps = false
7
+ ): Promise<{ code: string; map?: any } | undefined> {
8
+ if (!/\b(?:class(?:\s|\{)|(?:let|const)\s)/.test(code)) return
9
+ const [
10
+ { transformAsync },
11
+ { default: transformClasses },
12
+ { default: transformBlockScoping },
13
+ ] = await Promise.all([
14
+ import('@babel/core'),
15
+ import('@babel/plugin-transform-classes'),
16
+ import('@babel/plugin-transform-block-scoping'),
17
+ ])
18
+ const result = await transformAsync(code, {
19
+ filename,
20
+ babelrc: false,
21
+ configFile: false,
22
+ sourceMaps,
23
+ // the caller composes stage maps itself; a file's own sourceMappingURL
24
+ // composed here would give the stage map several sources
25
+ // @ts-expect-error @types/babel__core omits false, which babel accepts
26
+ inputSourceMap: false,
27
+ parserOpts: { plugins: ['jsx'] },
28
+ // Legacy Hermes hoists lexical names, including in class-free functions.
29
+ // Lower block scope so minification cannot shadow parameters or constructors.
30
+ plugins: [transformClasses, transformBlockScoping],
31
+ })
32
+ if (!result?.code) return
33
+ return { code: result.code, map: result.map }
34
+ }
@@ -22,7 +22,18 @@ export function shouldStripFlow(id: string, code: string): boolean {
22
22
  const header =
23
23
  code.match(/^(?:#![^\r\n]*(?:\r?\n|$))?(?:\s|\/\*[\s\S]*?\*\/|\/\/[^\r\n]*)*/)?.[0] ||
24
24
  ''
25
- return /@flow\b/.test(header) || /\b(?:import|export)\s+type\b/.test(code)
25
+ return (
26
+ /@flow\b/.test(header) ||
27
+ /\b(?:import|export)\s+type\b/.test(code) ||
28
+ // Some published Flow sources omit the pragma and use ordinary imports.
29
+ // Typed function returns, declarations, and class fields still need stripping.
30
+ /\(\s*(?:\.\.\.)?[\w$]+\??\s*:\s*/.test(code) ||
31
+ /\)\s*:\s*(?:[\w$]+(?:<[^;{}]*>)?(?:\[\])?\s*)(?:=>|\{)/.test(code) ||
32
+ /^\s*[\w$]+\??\s*:\s*(?:string|number|boolean|any|mixed|void|[A-Z][\w$]*)(?:[^;\r\n=]*)(?:=[^;\r\n]*)?;/m.test(
33
+ code
34
+ ) ||
35
+ /\b(?:opaque\s+)?type\s+[\w$]+(?:\s*<[^;{}]*>)?\s*=/.test(code)
36
+ )
26
37
  }
27
38
 
28
39
  export async function transformSWC(
@@ -1,6 +1,8 @@
1
1
  import fs from 'node:fs'
2
2
  import os from 'node:os'
3
+ import { createRequire } from 'node:module'
3
4
  import path from 'node:path'
5
+ import { runInNewContext } from 'node:vm'
4
6
  import { afterEach, describe, expect, it } from 'vitest'
5
7
  import { configureVXRNCompilerPlugin } from './configure'
6
8
  import { shouldTransformWorklets, transformWorklets } from './transformWorklets'
@@ -64,6 +66,11 @@ describe('transformWorklets', () => {
64
66
  expect(result.code).toContain('__initData')
65
67
  expect(result.code).toContain('__closure')
66
68
  expect(result.map).toBeUndefined()
69
+ const serialized = result.code.match(/code: ("(?:\\.|[^"\\])*")/)?.[1]
70
+ expect(serialized, 'serialized worklet code').toBeTruthy()
71
+ const worklet = (0, eval)(JSON.parse(serialized!))
72
+ expect(worklet).toBeTypeOf('function')
73
+ expect(worklet()).toBe(42)
67
74
  })
68
75
 
69
76
  it('ignores calls whose callee name collides with Object.prototype', async () => {
@@ -136,7 +143,7 @@ describe('transformWorklets', () => {
136
143
  `
137
144
  const result = await transformWorklets('/app/handler.ts', code, false)
138
145
  expect(result.code).toContain('__workletHash')
139
- expect(result.code).toMatch(/code: "function _worklet\(e\)/)
146
+ expect(result.code).toMatch(/code: "\(function _worklet\(e\)/)
140
147
  expect(result.code).toContain('var _worklet = function _worklet(e)')
141
148
  })
142
149
 
@@ -162,12 +169,38 @@ describe('transformWorklets', () => {
162
169
  const result = await transformWorklets('/app/setupLoop.ts', code, false)
163
170
 
164
171
  expect(result.code).toMatch(/setupLoop\.__closure = \{\s*\}/)
165
- const init = result.code.match(/code: "function setupLoop[^"]*"/)
172
+ const init = result.code.match(/code: "\(function setupLoop[^"]*"/)
166
173
  expect(init, 'serialized worklet code').toBeTruthy()
167
174
  expect(init![0]).not.toContain('this.__closure')
168
175
  expect(init![0]).toContain('flushQueue')
169
176
  })
170
177
 
178
+ it('keeps Reanimated UI-runtime globals out of the JavaScript closure', async () => {
179
+ const result = await transformWorklets(
180
+ '/app/valueUnpacker.ts',
181
+ `function valueUnpacker(value) { 'worklet'; return _toString(value) }
182
+ globalThis.__unpacker = valueUnpacker`,
183
+ false,
184
+ { pluginVersion: '3.19.1' }
185
+ )
186
+ const mainRuntime: any = {}
187
+ runInNewContext(result.code, mainRuntime)
188
+ expect(Object.keys(mainRuntime.__unpacker.__closure)).toEqual([])
189
+ const uiRuntime: any = { _toString: String }
190
+ const unpack = runInNewContext(mainRuntime.__unpacker.__initData.code, uiRuntime)
191
+ expect(unpack(42)).toBe('42')
192
+ })
193
+
194
+ it('keeps strict global configuration explicit for UI-runtime helpers', async () => {
195
+ const result = await transformWorklets(
196
+ '/app/strictGlobals.ts',
197
+ `function unpack(value) { 'worklet'; return _toString(value) }`,
198
+ false,
199
+ { pluginVersion: '3.19.1', strictGlobal: true }
200
+ )
201
+ expect(result.code).toMatch(/__closure = \{\s*_toString/)
202
+ })
203
+
171
204
  it('honors no-worklet-closure and limit-init-data-hoisting the way the worklets runtime needs', async () => {
172
205
  // this is the shape react-native-worklets uses for its unpacker installers.
173
206
  // the outer worklet is serialized to a string and evaluated on a worklet
@@ -190,7 +223,7 @@ describe('transformWorklets', () => {
190
223
  `
191
224
  const result = await transformWorklets('/app/installUnpacker.ts', code, false)
192
225
 
193
- const outerInit = result.code.match(/code: "function installUnpacker[^"]*"/)
226
+ const outerInit = result.code.match(/code: "\(function installUnpacker[^"]*"/)
194
227
  expect(outerInit, 'outer worklet serialized code').toBeTruthy()
195
228
  // no unpacker line: nothing is read off `this` on the worklet runtime.
196
229
  expect(outerInit![0]).not.toContain('this.__closure')
@@ -201,7 +234,7 @@ describe('transformWorklets', () => {
201
234
  // the inner worklet's init data is declared inside the outer function body,
202
235
  // not hoisted to module scope where the worklet runtime cannot see it.
203
236
  const innerVar = result.code.match(
204
- /var (_worklet_\d+_init_data) = \{\s*code: "function _worklet/
237
+ /var (_worklet_\d+_init_data) = \{\s*code: "\(function _worklet/
205
238
  )
206
239
  expect(innerVar, 'inner worklet init data').toBeTruthy()
207
240
  // only look past the serialized string, which mentions the same name inside
@@ -381,7 +414,12 @@ describe('transformWorklets', () => {
381
414
  expect(typeof scaleFn).toBe('function')
382
415
  expect(scaleFn.__workletHash).toBeDefined()
383
416
  expect(typeof scaleFn.__workletHash).toBe('number')
384
- expect(scaleFn.__pluginVersion).toBe('0.10.1')
417
+ // /app has no package.json, so the stamp comes from the worklets runtime
418
+ // this workspace actually installs
419
+ const installedWorklets = createRequire(path.resolve(process.cwd(), 'package.json'))(
420
+ 'react-native-worklets/package.json'
421
+ ).version
422
+ expect(scaleFn.__pluginVersion).toBe(installedWorklets)
385
423
  expect(scaleFn.__closure).toEqual({ multiplier: 5 })
386
424
  expect(scaleFn.__initData).toBeDefined()
387
425
  expect(scaleFn.__initData.code).toContain('__closure')
@@ -613,6 +651,43 @@ describe('transformWorklets', () => {
613
651
  expect(uiRes).toBe(7)
614
652
  })
615
653
 
654
+ it('captures the outer binding a property worklet shares its key with', async () => {
655
+ // react-native-gesture-handler's NativeProxy: the property worklet calls the
656
+ // module-level host function of the same name. naming the worklet after its
657
+ // key made it capture itself, so serializing its closure never ended.
658
+ const code = `
659
+ const updateConfig = (tag) => tag * 2
660
+ export const proxy = {
661
+ updateConfig: (tag) => {
662
+ 'worklet'
663
+ return updateConfig(tag)
664
+ },
665
+ flush: function (tag) {
666
+ 'worklet'
667
+ return updateConfig(tag) + 1
668
+ },
669
+ }
670
+ `
671
+ const result = await transformWorklets('/app/proxy.ts', code, false)
672
+
673
+ const sandbox = { global: globalThis, exports: {} as any }
674
+ const runner = new Function(
675
+ 'exports',
676
+ 'global',
677
+ result.code.replace('export const proxy', 'exports.proxy')
678
+ )
679
+ runner(sandbox.exports, sandbox.global)
680
+ const { proxy } = sandbox.exports
681
+
682
+ for (const worklet of [proxy.updateConfig, proxy.flush]) {
683
+ expect(worklet.__closure.updateConfig).not.toBe(worklet)
684
+ const reconstructed = eval(`(${worklet.__initData.code})`)
685
+ expect(reconstructed.call({ __closure: worklet.__closure }, 3)).toBe(worklet(3))
686
+ }
687
+ expect(proxy.updateConfig(3)).toBe(6)
688
+ expect(proxy.flush(3)).toBe(7)
689
+ })
690
+
616
691
  it('transforms nested worklets with bottom-up composition', async () => {
617
692
  const code = `
618
693
  export function outer() {
@@ -0,0 +1,4 @@
1
+ // compiler hosts can load capture analysis without importing Vite.
2
+ export { getClosureVariables } from './worklets/scope'
3
+ export { JS_GLOBALS } from './worklets/globals'
4
+ export { transformHermesLoops } from './transformHermesLoops'
@@ -306,7 +306,10 @@ export function findWorkletCandidates(program: any): WorkletCandidate[] {
306
306
  : fn.type === 'ArrowFunctionExpression'
307
307
  ? 'arrow_function'
308
308
  : 'function_expression',
309
- name: node.key?.name,
309
+ // only a method is named by its key, as upstream's plugin does: a
310
+ // property's function has no binding of that name, so naming it would
311
+ // shadow an outer binding it closes over with the worklet itself.
312
+ name: node.method ? node.key?.name : fn.id?.name,
310
313
  parent,
311
314
  isAutoWorklet: false,
312
315
  })
@@ -368,7 +371,7 @@ export function findWorkletCandidates(program: any): WorkletCandidate[] {
368
371
  prop.value.type === 'ArrowFunctionExpression'
369
372
  ? 'arrow_function'
370
373
  : 'function_expression',
371
- name: prop.key?.name,
374
+ name: prop.value.id?.name,
372
375
  parent: prop,
373
376
  isAutoWorklet: true,
374
377
  })
@@ -108,6 +108,29 @@ export const DEFAULT_GLOBALS = new Set([
108
108
  'clearInterval',
109
109
  'HermesInternal',
110
110
  '_WORKLET',
111
+ // Reanimated installs these in the UI runtime. Capturing them on the JS
112
+ // thread fails before its value unpacker can initialize (Reanimated 3).
113
+ 'ReanimatedError',
114
+ '__reanimatedLoggerConfig',
115
+ '_IS_FABRIC',
116
+ '_log',
117
+ '_toString',
118
+ '_scheduleHostFunctionOnJS',
119
+ '_scheduleRemoteFunctionOnJS',
120
+ '_scheduleOnRuntime',
121
+ '_makeShareableClone',
122
+ '_updatePropsPaper',
123
+ '_updatePropsFabric',
124
+ '_measurePaper',
125
+ '_measureFabric',
126
+ '_scrollToPaper',
127
+ '_dispatchCommandPaper',
128
+ '_dispatchCommandFabric',
129
+ '_setGestureState',
130
+ '_notifyAboutProgress',
131
+ '_notifyAboutEnd',
132
+ '_runOnUIQueue',
133
+ '_getAnimationTimestamp',
111
134
  ])
112
135
 
113
136
  export function createGlobalsSet(
@@ -64,7 +64,7 @@ export function serializeWorkletForUI(
64
64
 
65
65
  const transformed = transformSync('worklet.ts', fullFn)
66
66
  assertTransformed(transformed, name, 'serialized')
67
- return transformed.code.trim()
67
+ return `(${transformed.code.trim().replace(/;$/, '')})`
68
68
  }
69
69
 
70
70
  /**
package/types/cache.d.ts CHANGED
@@ -4,14 +4,14 @@ interface CacheStats {
4
4
  writes: 0;
5
5
  errors: 0;
6
6
  }
7
- export declare function getCachedTransform(filePath: string, code: string, environment: string): {
7
+ export declare function getCachedTransform(filePath: string, code: string, environment: string, userBabelConfigPath?: string | null): {
8
8
  code: string;
9
9
  map?: any;
10
10
  } | null;
11
11
  export declare function setCachedTransform(filePath: string, code: string, result: {
12
12
  code: string;
13
13
  map?: any;
14
- }, environment: string): void;
14
+ }, environment: string, userBabelConfigPath?: string | null): void;
15
15
  /** Drop every cached transform, for `react-native bundle --reset-cache`. */
16
16
  export declare function clearTransformCache(): void;
17
17
  export declare function getCacheStats(): CacheStats;
@@ -1 +1 @@
1
- {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AA4BA,UAAU,UAAU;IAClB,IAAI,EAAE,CAAC,CAAA;IACP,MAAM,EAAE,CAAC,CAAA;IACT,MAAM,EAAE,CAAC,CAAA;IACT,MAAM,EAAE,CAAC,CAAA;CACV;AAiDD,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,GAClB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,GAAG,CAAA;CAAE,GAAG,IAAI,CAoCpC;AAED,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,GAAG,CAAA;CAAE,EACnC,WAAW,EAAE,MAAM,GAClB,IAAI,CAwBN;AAED,4EAA4E;AAC5E,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C;AAED,wBAAgB,aAAa,IAAI,UAAU,CAE1C;AAED,wBAAgB,aAAa,IAAI,IAAI,CAapC"}
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AA4BA,UAAU,UAAU;IAClB,IAAI,EAAE,CAAC,CAAA;IACP,MAAM,EAAE,CAAC,CAAA;IACT,MAAM,EAAE,CAAC,CAAA;IACT,MAAM,EAAE,CAAC,CAAA;CACV;AA6ED,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,GAClC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,GAAG,CAAA;CAAE,GAAG,IAAI,CAoCpC;AAED,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,GAAG,CAAA;CAAE,EACnC,WAAW,EAAE,MAAM,EACnB,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,GAClC,IAAI,CAwBN;AAED,4EAA4E;AAC5E,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C;AAED,wBAAgB,aAAa,IAAI,UAAU,CAE1C;AAED,wBAAgB,aAAa,IAAI,IAAI,CAapC"}
package/types/index.d.ts CHANGED
@@ -12,6 +12,7 @@ export * from './transformWorklets';
12
12
  export * from './reactNativeCodegen';
13
13
  export * from './transformHermesLoops';
14
14
  export * from './transformHermesAsync';
15
+ export * from './transformHermesClasses';
15
16
  export { clearTransformCache } from './cache';
16
17
  export { JS_GLOBALS } from './worklets/globals';
17
18
  export { getClosureVariables } from './worklets/scope';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AASH,OAAO,KAAK,EAAE,YAAY,EAA8B,MAAM,MAAM,CAAA;AAcpE,OAAO,KAAK,EAAkC,OAAO,EAAE,MAAM,SAAS,CAAA;AAGtE,cAAc,aAAa,CAAA;AAC3B,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,qBAAqB,CAAA;AACnC,cAAc,sBAAsB,CAAA;AACpC,cAAc,wBAAwB,CAAA;AACtC,cAAc,wBAAwB,CAAA;AACtC,OAAO,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAA;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AACtD,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AA2R3C,wBAAsB,wBAAwB,CAC5C,SAAS,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,GAC3B,OAAO,CAAC,YAAY,EAAE,CAAC,CA6VzB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AASH,OAAO,KAAK,EAAE,YAAY,EAA8B,MAAM,MAAM,CAAA;AAcpE,OAAO,KAAK,EAAkC,OAAO,EAAE,MAAM,SAAS,CAAA;AAGtE,cAAc,aAAa,CAAA;AAC3B,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,qBAAqB,CAAA;AACnC,cAAc,sBAAsB,CAAA;AACpC,cAAc,wBAAwB,CAAA;AACtC,cAAc,wBAAwB,CAAA;AACtC,cAAc,0BAA0B,CAAA;AACxC,OAAO,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAA;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AACtD,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AA8S3C,wBAAsB,wBAAwB,CAC5C,SAAS,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,GAC3B,OAAO,CAAC,YAAY,EAAE,CAAC,CAkWzB"}
@@ -1 +1 @@
1
- {"version":3,"file":"reactNativeViewConfig.d.ts","sourceRoot":"","sources":["../src/reactNativeViewConfig.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AA4dH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,GAAG,CAuBnF;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,GAAG,GACX;IAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAsE5C;AAED,kFAAkF;AAClF,wBAAgB,sBAAsB,CAAC,MAAM,EAAE;IAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,UAEjF;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAIvD"}
1
+ {"version":3,"file":"reactNativeViewConfig.d.ts","sourceRoot":"","sources":["../src/reactNativeViewConfig.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAieH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,GAAG,CAuBnF;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,GAAG,GACX;IAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAsE5C;AAED,kFAAkF;AAClF,wBAAgB,sBAAsB,CAAC,MAAM,EAAE;IAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,UAEjF;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAIvD"}
@@ -3,6 +3,7 @@ import type { GetTransformProps, GetTransformResponse } from './types';
3
3
  type Props = GetTransformProps & {
4
4
  userSetting?: GetTransformResponse;
5
5
  };
6
+ export declare function findUserBabelConfig(projectRoot?: string): string | null;
6
7
  export declare function getBabelOptions(props: Props): babel.TransformOptions | null;
7
8
  /**
8
9
  * Run the react compiler through oxc's rust port instead of babel.
@@ -13,7 +14,9 @@ export declare function getBabelOptions(props: Props): babel.TransformOptions |
13
14
  * still applies the project's jsxImportSource and dev-mode settings, exactly
14
15
  * as it did when babel only stripped types here.
15
16
  */
16
- export declare function transformOxcReactCompiler(id: string, code: string, target: '18' | '19', sourceMap?: boolean): Promise<{
17
+ export declare function transformOxcReactCompiler(id: string, code: string, optionsOrTarget?: '18' | '19' | (Record<string, any> & {
18
+ target?: '18' | '19';
19
+ }), sourceMap?: boolean): Promise<{
17
20
  code: string;
18
21
  map: any;
19
22
  }>;
@@ -21,5 +24,17 @@ export declare function transformOxcReactCompiler(id: string, code: string, targ
21
24
  * Transform input to mostly ES5 compatible code, keep ESM syntax, and transform generators.
22
25
  */
23
26
  export declare function transformBabel(id: string, code: string, options: babel.TransformOptions): Promise<babel.BabelFileResult>;
27
+ export declare function stripFlowTypes(id: string, code: string, sourceMaps?: boolean): Promise<{
28
+ code: string;
29
+ map: {
30
+ version: number;
31
+ sources: string[];
32
+ names: string[];
33
+ sourceRoot?: string | undefined;
34
+ sourcesContent?: string[] | undefined;
35
+ mappings: string;
36
+ file: string;
37
+ } | null | undefined;
38
+ }>;
24
39
  export {};
25
40
  //# sourceMappingURL=transformBabel.d.ts.map