@vxrn/compiler 2.0.0-beta.33.1 → 2.0.0-beta.45.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 (94) 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 +420 -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 +278 -218
  9. package/dist/cjs/transformHermesAsync.cjs +36 -47
  10. package/dist/cjs/transformHermesLoops.cjs +279 -281
  11. package/dist/cjs/transformSWC.cjs +183 -200
  12. package/dist/cjs/transformWorklets.cjs +107 -95
  13. package/dist/cjs/types.cjs +9 -11
  14. package/dist/cjs/worklets/autoworklet.cjs +277 -222
  15. package/dist/cjs/worklets/globals.cjs +124 -60
  16. package/dist/cjs/worklets/hash.cjs +23 -27
  17. package/dist/cjs/worklets/scope.cjs +338 -359
  18. package/dist/cjs/worklets/serialize.cjs +73 -75
  19. package/dist/cjs/worklets/transform.cjs +217 -222
  20. package/dist/cjs/worklets/types.cjs +9 -11
  21. package/dist/esm/cache.mjs +96 -89
  22. package/dist/esm/cache.mjs.map +1 -1
  23. package/dist/esm/configure.mjs +11 -10
  24. package/dist/esm/configure.mjs.map +1 -1
  25. package/dist/esm/constants.mjs +41 -41
  26. package/dist/esm/constants.mjs.map +1 -1
  27. package/dist/esm/index.js +417 -420
  28. package/dist/esm/index.js.map +1 -1
  29. package/dist/esm/index.mjs +417 -420
  30. package/dist/esm/index.mjs.map +1 -1
  31. package/dist/esm/reactNativeCodegen.mjs +90 -88
  32. package/dist/esm/reactNativeCodegen.mjs.map +1 -1
  33. package/dist/esm/reactNativeViewConfig.mjs +347 -362
  34. package/dist/esm/reactNativeViewConfig.mjs.map +1 -1
  35. package/dist/esm/refresh-runtime.mjs +372 -376
  36. package/dist/esm/refresh-runtime.mjs.map +1 -1
  37. package/dist/esm/transformBabel.mjs +261 -194
  38. package/dist/esm/transformBabel.mjs.map +1 -1
  39. package/dist/esm/transformHermesAsync.mjs +20 -21
  40. package/dist/esm/transformHermesAsync.mjs.map +1 -1
  41. package/dist/esm/transformHermesLoops.mjs +264 -255
  42. package/dist/esm/transformHermesLoops.mjs.map +1 -1
  43. package/dist/esm/transformSWC.mjs +163 -171
  44. package/dist/esm/transformSWC.mjs.map +1 -1
  45. package/dist/esm/transformWorklets.mjs +89 -69
  46. package/dist/esm/transformWorklets.mjs.map +1 -1
  47. package/dist/esm/types.mjs +0 -2
  48. package/dist/esm/worklets/autoworklet.mjs +258 -200
  49. package/dist/esm/worklets/autoworklet.mjs.map +1 -1
  50. package/dist/esm/worklets/globals.mjs +109 -42
  51. package/dist/esm/worklets/globals.mjs.map +1 -1
  52. package/dist/esm/worklets/hash.mjs +10 -9
  53. package/dist/esm/worklets/hash.mjs.map +1 -1
  54. package/dist/esm/worklets/scope.mjs +325 -341
  55. package/dist/esm/worklets/scope.mjs.map +1 -1
  56. package/dist/esm/worklets/serialize.mjs +60 -58
  57. package/dist/esm/worklets/serialize.mjs.map +1 -1
  58. package/dist/esm/worklets/transform.mjs +201 -197
  59. package/dist/esm/worklets/transform.mjs.map +1 -1
  60. package/dist/esm/worklets/types.mjs +0 -2
  61. package/package.json +7 -6
  62. package/src/cache.ts +37 -7
  63. package/src/index.ts +33 -18
  64. package/src/transformBabel.test.ts +311 -1
  65. package/src/transformBabel.ts +110 -13
  66. package/types/cache.d.ts +2 -2
  67. package/types/cache.d.ts.map +1 -1
  68. package/types/index.d.ts.map +1 -1
  69. package/types/transformBabel.d.ts +16 -1
  70. package/types/transformBabel.d.ts.map +1 -1
  71. package/types/transformSWC.d.ts +1 -4
  72. package/types/transformSWC.d.ts.map +1 -1
  73. package/dist/cjs/transformBabel.test.cjs +0 -360
  74. package/dist/cjs/transformHermesLoops.test.cjs +0 -257
  75. package/dist/cjs/transformSWC.test.cjs +0 -117
  76. package/dist/cjs/transformWorklets.test.cjs +0 -604
  77. package/dist/esm/transformBabel.test.mjs +0 -337
  78. package/dist/esm/transformBabel.test.mjs.map +0 -1
  79. package/dist/esm/transformHermesLoops.test.mjs +0 -234
  80. package/dist/esm/transformHermesLoops.test.mjs.map +0 -1
  81. package/dist/esm/transformSWC.test.mjs +0 -118
  82. package/dist/esm/transformSWC.test.mjs.map +0 -1
  83. package/dist/esm/transformWorklets.test.mjs +0 -581
  84. package/dist/esm/transformWorklets.test.mjs.map +0 -1
  85. package/dist/esm/types.mjs.map +0 -1
  86. package/dist/esm/worklets/types.mjs.map +0 -1
  87. package/types/transformBabel.test.d.ts +0 -2
  88. package/types/transformBabel.test.d.ts.map +0 -1
  89. package/types/transformHermesLoops.test.d.ts +0 -2
  90. package/types/transformHermesLoops.test.d.ts.map +0 -1
  91. package/types/transformSWC.test.d.ts +0 -2
  92. package/types/transformSWC.test.d.ts.map +0 -1
  93. package/types/transformWorklets.test.d.ts +0 -2
  94. package/types/transformWorklets.test.d.ts.map +0 -1
package/src/index.ts CHANGED
@@ -172,8 +172,12 @@ async function performBabelTransform({
172
172
  (x) => Array.isArray(x) && x[0] === 'babel-plugin-react-compiler'
173
173
  )
174
174
 
175
- // Check cache first
176
- const cached = getCachedTransform(id, code, environment)
175
+ // Check cache first. the user babel config feeds the transform output,
176
+ // so its identity joins the key: adding or editing babel.config.* must
177
+ // miss entries cached without that change.
178
+ const userBabelConfigPath =
179
+ typeof babelOptions.configFile === 'string' ? babelOptions.configFile : null
180
+ const cached = getCachedTransform(id, code, environment, userBabelConfigPath)
177
181
  if (cached) {
178
182
  perfStats.babel.byEnvironment[environment].transforms++
179
183
  if (
@@ -214,25 +218,32 @@ async function performBabelTransform({
214
218
  ) ?? -1
215
219
 
216
220
  if (compilerPluginIndex !== -1) {
217
- // mark callbacks before react compiler moves them into memoized bindings.
218
- if (useWorklets) {
219
- workletPreparation = prepareWorkletsForReactCompiler(
221
+ const compilerPluginConfig = babelOptions.plugins![compilerPluginIndex] as any[]
222
+ const compilerPluginOptions = compilerPluginConfig[1] || {}
223
+ const useBabelCompiler =
224
+ compilerPluginOptions.compiler === 'babel' ||
225
+ process.env.ONE_REACT_COMPILER === 'babel' ||
226
+ process.env.VXRN_REACT_COMPILER === 'babel'
227
+
228
+ if (!useBabelCompiler) {
229
+ // mark callbacks before react compiler moves them into memoized bindings.
230
+ if (useWorklets) {
231
+ workletPreparation = prepareWorkletsForReactCompiler(
232
+ id,
233
+ curCode,
234
+ shouldSourceMap()
235
+ )
236
+ if (workletPreparation) curCode = workletPreparation.code
237
+ }
238
+ compilerOut = await transformOxcReactCompiler(
220
239
  id,
221
240
  curCode,
241
+ compilerPluginOptions,
222
242
  shouldSourceMap()
223
243
  )
224
- if (workletPreparation) curCode = workletPreparation.code
244
+ if (compilerOut) curCode = compilerOut.code
245
+ babelOptions.plugins!.splice(compilerPluginIndex, 1)
225
246
  }
226
- const compilerTarget =
227
- (babelOptions.plugins![compilerPluginIndex] as any[])[1]?.target ?? '19'
228
- compilerOut = await transformOxcReactCompiler(
229
- id,
230
- curCode,
231
- compilerTarget,
232
- shouldSourceMap()
233
- )
234
- if (compilerOut) curCode = compilerOut.code
235
- babelOptions.plugins!.splice(compilerPluginIndex, 1)
236
247
  }
237
248
 
238
249
  let workletsOut: { code: string; map?: any } | null = null
@@ -251,7 +262,11 @@ async function performBabelTransform({
251
262
  const startTime = Date.now()
252
263
  let babelOut: { code?: string | null; map?: any } | null = null
253
264
 
254
- if (babelOptions.plugins?.length === 0) {
265
+ if (
266
+ babelOptions.plugins?.length === 0 &&
267
+ !babelOptions.configFile &&
268
+ !babelOptions.babelrc
269
+ ) {
255
270
  let finalMap: any = undefined
256
271
  if (shouldSourceMap()) {
257
272
  const intermediateMaps = [
@@ -311,7 +326,7 @@ async function performBabelTransform({
311
326
  const result = { code: outCode, map: babelOut.map }
312
327
 
313
328
  // Cache the result
314
- setCachedTransform(id, code, result, environment)
329
+ setCachedTransform(id, code, result, environment, userBabelConfigPath)
315
330
 
316
331
  return result
317
332
  }
@@ -4,6 +4,7 @@ import path from 'node:path'
4
4
  import { afterEach, describe, expect, it } from 'vitest'
5
5
  import { configureVXRNCompilerPlugin } from './configure'
6
6
  import {
7
+ findUserBabelConfig,
7
8
  getBabelOptions,
8
9
  transformBabel,
9
10
  transformOxcReactCompiler,
@@ -12,7 +13,6 @@ import {
12
13
  afterEach(() => {
13
14
  configureVXRNCompilerPlugin({ enableReanimated: false })
14
15
  })
15
-
16
16
  describe('getBabelOptions Worklets resolution', () => {
17
17
  it('uses the app-installed Worklets Babel plugin', () => {
18
18
  const projectRoot = fs.realpathSync(
@@ -67,6 +67,27 @@ describe('transformBabel Flow parsing', () => {
67
67
  expect(result?.code).not.toContain('HostComponent')
68
68
  })
69
69
 
70
+ it('preserves React Native Flow enums as runtime values', async () => {
71
+ const result = await transformBabel(
72
+ '/project/VirtualView.js',
73
+ `
74
+ // @flow strict-local
75
+ export enum VirtualViewRenderState {
76
+ Unknown = 0,
77
+ Rendered = 1,
78
+ None = 2,
79
+ }
80
+
81
+ export const rendered = VirtualViewRenderState.Rendered
82
+ `,
83
+ { plugins: [] }
84
+ )
85
+
86
+ expect(result?.code).toContain('const VirtualViewRenderState =')
87
+ expect(result?.code).toContain('Rendered: 1')
88
+ expect(result?.code).toContain('VirtualViewRenderState.Rendered')
89
+ })
90
+
70
91
  it('rejects a required transform error instead of returning untransformed source', async () => {
71
92
  const negativeControl = () => ({
72
93
  visitor: {
@@ -130,6 +151,29 @@ describe('transformOxcReactCompiler', () => {
130
151
 
131
152
  expect(result.code).toContain('_c(')
132
153
  })
154
+
155
+ it('supports mutating useSharedValue in useEffect via Reanimated environment defaults', async () => {
156
+ const reanimatedCode = `
157
+ import { useEffect } from 'react'
158
+ import { useSharedValue } from 'react-native-reanimated'
159
+
160
+ export function ReanimatedComponent() {
161
+ const val = useSharedValue(0)
162
+ useEffect(() => {
163
+ val.value = 1
164
+ }, [val])
165
+ return <div>{val.value}</div>
166
+ }
167
+ `
168
+ const result = await transformOxcReactCompiler(
169
+ '/project/ReanimatedComponent.tsx',
170
+ reanimatedCode,
171
+ { target: '19' },
172
+ false
173
+ )
174
+
175
+ expect(result.code).toContain('_c(')
176
+ })
133
177
  })
134
178
 
135
179
  describe('compiler plugin multi-stage source map composition', () => {
@@ -346,3 +390,269 @@ describe('shared compiler worklets backend selection', () => {
346
390
  }
347
391
  })
348
392
  })
393
+
394
+ describe('findUserBabelConfig and user Babel config respect', () => {
395
+ it('finds user babel config and ignores generated configs', () => {
396
+ const projectRoot = fs.realpathSync(
397
+ fs.mkdtempSync(path.join(os.tmpdir(), 'vxrn-babel-conf-'))
398
+ )
399
+ try {
400
+ expect(findUserBabelConfig(projectRoot)).toBeNull()
401
+
402
+ // written by `one patch` with the generated marker -> ignored
403
+ const generatedFile = path.join(projectRoot, 'babel.config.js')
404
+ fs.writeFileSync(
405
+ generatedFile,
406
+ '// @one/generated bundler-config\nmodule.exports = {}'
407
+ )
408
+ expect(findUserBabelConfig(projectRoot)).toBeNull()
409
+
410
+ // Overwritten with user config -> detected
411
+ fs.writeFileSync(generatedFile, 'module.exports = { plugins: [] }')
412
+ expect(findUserBabelConfig(projectRoot)).toBe(generatedFile)
413
+ } finally {
414
+ fs.rmSync(projectRoot, { recursive: true, force: true })
415
+ }
416
+ })
417
+
418
+ it('includes user configFile and babelrc in getBabelOptions for project files', () => {
419
+ const projectRoot = fs.realpathSync(
420
+ fs.mkdtempSync(path.join(os.tmpdir(), 'vxrn-babel-conf-'))
421
+ )
422
+ const userConfig = path.join(projectRoot, 'babel.config.json')
423
+ fs.writeFileSync(userConfig, '{"plugins": []}')
424
+ try {
425
+ // For project file:
426
+ const projectFileOptions = getBabelOptions({
427
+ id: path.join(projectRoot, 'src', 'index.tsx'),
428
+ code: `export const hello = () => 123`,
429
+ projectRoot,
430
+ development: true,
431
+ environment: 'client',
432
+ reactForRNVersion: '19',
433
+ })
434
+ expect(projectFileOptions?.configFile).toBe(userConfig)
435
+ expect(projectFileOptions?.babelrc).toBe(true)
436
+
437
+ // For node_modules file: should not attach user configFile
438
+ const nodeModulesOptions = getBabelOptions({
439
+ id: path.join(projectRoot, 'node_modules', 'foo', 'index.js'),
440
+ code: `export const foo = 1`,
441
+ projectRoot,
442
+ development: true,
443
+ environment: 'client',
444
+ reactForRNVersion: '19',
445
+ })
446
+ expect(nodeModulesOptions).toBeNull()
447
+ } finally {
448
+ fs.rmSync(projectRoot, { recursive: true, force: true })
449
+ }
450
+ })
451
+
452
+ it('runs user babel config transforms via transformBabel', async () => {
453
+ const projectRoot = fs.realpathSync(
454
+ fs.mkdtempSync(path.join(os.tmpdir(), 'vxrn-babel-conf-'))
455
+ )
456
+ const userConfig = path.join(projectRoot, 'babel.config.json')
457
+ // A babel config that injects a banner/plugin or standard babel syntax
458
+ fs.writeFileSync(
459
+ userConfig,
460
+ JSON.stringify({
461
+ comments: false,
462
+ })
463
+ )
464
+ try {
465
+ const code = '/* remove me */ export const x = 1'
466
+ const res = await transformBabel(path.join(projectRoot, 'src', 'index.ts'), code, {
467
+ configFile: userConfig,
468
+ babelrc: true,
469
+ })
470
+ expect(res.code).not.toContain('remove me')
471
+ expect(res.code).toContain('export const x = 1')
472
+ } finally {
473
+ fs.rmSync(projectRoot, { recursive: true, force: true })
474
+ }
475
+ })
476
+
477
+ it('tells user babel config it runs in a bundler that keeps static esm', async () => {
478
+ const projectRoot = fs.realpathSync(
479
+ fs.mkdtempSync(path.join(os.tmpdir(), 'vxrn-babel-conf-'))
480
+ )
481
+ const userConfig = path.join(projectRoot, 'babel.config.js')
482
+ // presets such as babel-preset-expo read this caller to decide whether to
483
+ // rewrite esm to commonjs and import.meta to a metro runtime global
484
+ fs.writeFileSync(
485
+ userConfig,
486
+ `module.exports = (api) => ({
487
+ comments: !api.caller((c) => c?.name === 'vxrn' && c?.supportsStaticESM === true),
488
+ })`
489
+ )
490
+ try {
491
+ const res = await transformBabel(
492
+ path.join(projectRoot, 'src', 'index.ts'),
493
+ '/* remove me */ export const x = 1',
494
+ { configFile: userConfig, babelrc: true }
495
+ )
496
+ expect(res.code).not.toContain('remove me')
497
+ } finally {
498
+ fs.rmSync(projectRoot, { recursive: true, force: true })
499
+ }
500
+ })
501
+ })
502
+ describe('explicit swc/oxc per-file choice with a user babel config', () => {
503
+ it('returns null for swc/oxc string and object forms', () => {
504
+ const projectRoot = fs.realpathSync(
505
+ fs.mkdtempSync(path.join(os.tmpdir(), 'vxrn-babel-conf-'))
506
+ )
507
+ const userConfig = path.join(projectRoot, 'babel.config.js')
508
+ fs.writeFileSync(userConfig, 'module.exports = { plugins: [] }')
509
+ try {
510
+ const base = {
511
+ id: path.join(projectRoot, 'src', 'index.tsx'),
512
+ code: `export const x = 1`,
513
+ projectRoot,
514
+ development: true,
515
+ environment: 'client' as const,
516
+ reactForRNVersion: '19' as const,
517
+ }
518
+ // merely adding babel.config.js must not flip an explicit non-babel choice
519
+ expect(getBabelOptions({ ...base, userSetting: 'swc' })).toBeNull()
520
+ expect(getBabelOptions({ ...base, userSetting: 'oxc' })).toBeNull()
521
+ expect(getBabelOptions({ ...base, userSetting: { transform: 'swc' } })).toBeNull()
522
+ expect(getBabelOptions({ ...base, userSetting: { transform: 'oxc' } })).toBeNull()
523
+ // controls: babel choices still resolve through the user config
524
+ expect(getBabelOptions({ ...base, userSetting: 'babel' })?.configFile).toBe(
525
+ userConfig
526
+ )
527
+ expect(
528
+ getBabelOptions({ ...base, userSetting: { transform: 'babel' } })?.configFile
529
+ ).toBe(userConfig)
530
+ expect(getBabelOptions(base)?.configFile).toBe(userConfig)
531
+ } finally {
532
+ fs.rmSync(projectRoot, { recursive: true, force: true })
533
+ }
534
+ })
535
+
536
+ it('skips the transform end-to-end for object-form swc', async () => {
537
+ const { createVXRNCompilerPlugin } = await import('./index')
538
+ const projectRoot = fs.realpathSync(
539
+ fs.mkdtempSync(path.join(os.tmpdir(), 'vxrn-babel-conf-'))
540
+ )
541
+ const srcDir = path.join(projectRoot, 'src')
542
+ fs.mkdirSync(srcDir, { recursive: true })
543
+ const file = path.join(srcDir, 'index.ts')
544
+ const code = `export const x = 1`
545
+ fs.writeFileSync(file, code)
546
+ fs.writeFileSync(
547
+ path.join(projectRoot, 'babel.config.js'),
548
+ 'module.exports = { plugins: [] }'
549
+ )
550
+ configureVXRNCompilerPlugin({
551
+ enableCompiler: false,
552
+ enableReanimated: false,
553
+ })
554
+ try {
555
+ const plugins = await createVXRNCompilerPlugin({
556
+ transform: () => ({ transform: 'swc' }) as any,
557
+ })
558
+ const plugin = plugins.find((p: any) => p.name === 'one:compiler') as any
559
+ await plugin.configResolved({ root: projectRoot, build: {} })
560
+ const hook = plugin.transform.handler || plugin.transform
561
+ const result = await hook.call({ environment: { name: 'client' } }, code, file)
562
+ expect(result == null).toBe(true)
563
+ } finally {
564
+ configureVXRNCompilerPlugin({
565
+ enableCompiler: false,
566
+ enableReanimated: false,
567
+ })
568
+ fs.rmSync(projectRoot, { recursive: true, force: true })
569
+ }
570
+ })
571
+ })
572
+
573
+ describe('user babel config end-to-end through the compiler plugin', () => {
574
+ it('runs the user config when default plugins are empty', async () => {
575
+ const { createVXRNCompilerPlugin } = await import('./index')
576
+ const projectRoot = fs.realpathSync(
577
+ fs.mkdtempSync(path.join(os.tmpdir(), 'vxrn-babel-e2e-'))
578
+ )
579
+ const srcDir = path.join(projectRoot, 'src')
580
+ fs.mkdirSync(srcDir, { recursive: true })
581
+ const file = path.join(srcDir, 'index.ts')
582
+ const marker = 'babel-e2e-probe'
583
+ const code = `/* ${marker} */ export const x = 1`
584
+ fs.writeFileSync(file, code)
585
+ fs.writeFileSync(
586
+ path.join(projectRoot, 'babel.config.json'),
587
+ JSON.stringify({ comments: false })
588
+ )
589
+ configureVXRNCompilerPlugin({
590
+ enableCompiler: false,
591
+ enableReanimated: false,
592
+ })
593
+ try {
594
+ const plugins = await createVXRNCompilerPlugin()
595
+ const plugin = plugins.find((p: any) => p.name === 'one:compiler') as any
596
+ await plugin.configResolved({ root: projectRoot, build: {} })
597
+ const hook = plugin.transform.handler || plugin.transform
598
+ const result = await hook.call({ environment: { name: 'client' } }, code, file)
599
+ // default plugins are empty here, but the user configFile must still
600
+ // route through transformBabel instead of taking the skip fast-path
601
+ expect(result).toBeDefined()
602
+ expect(result.code).toContain('export const x = 1')
603
+ expect(result.code).not.toContain(marker)
604
+ } finally {
605
+ configureVXRNCompilerPlugin({
606
+ enableCompiler: false,
607
+ enableReanimated: false,
608
+ })
609
+ fs.rmSync(projectRoot, { recursive: true, force: true })
610
+ }
611
+ })
612
+
613
+ it('invalidates the cache when the user babel config is added or edited', async () => {
614
+ const { createVXRNCompilerPlugin } = await import('./index')
615
+ const projectRoot = fs.realpathSync(
616
+ fs.mkdtempSync(path.join(os.tmpdir(), 'vxrn-babel-cache-'))
617
+ )
618
+ const srcDir = path.join(projectRoot, 'src')
619
+ fs.mkdirSync(srcDir, { recursive: true })
620
+ const file = path.join(srcDir, 'index.ts')
621
+ const marker = 'babel-cache-probe'
622
+ const code = `/* ${marker} */ export const x = 1`
623
+ fs.writeFileSync(file, code)
624
+ const userConfig = path.join(projectRoot, 'babel.config.json')
625
+ configureVXRNCompilerPlugin({
626
+ enableCompiler: false,
627
+ enableReanimated: false,
628
+ })
629
+ try {
630
+ const plugins = await createVXRNCompilerPlugin({
631
+ transform: () => 'babel' as const,
632
+ })
633
+ const plugin = plugins.find((p: any) => p.name === 'one:compiler') as any
634
+ await plugin.configResolved({ root: projectRoot, build: {} })
635
+ const hook = plugin.transform.handler || plugin.transform
636
+ const context = { environment: { name: 'client' } }
637
+
638
+ const res1 = await hook.call(context, code, file)
639
+ expect(res1.code).toContain(marker)
640
+
641
+ // adding a config without touching the source must miss the cache
642
+ fs.writeFileSync(userConfig, JSON.stringify({ comments: false }))
643
+ const res2 = await hook.call(context, code, file)
644
+ expect(res2.code).not.toContain(marker)
645
+
646
+ // editing the config contents must miss again
647
+ fs.writeFileSync(userConfig, JSON.stringify({ comments: true }))
648
+ const res3 = await hook.call(context, code, file)
649
+ expect(res3.code).toContain(marker)
650
+ } finally {
651
+ configureVXRNCompilerPlugin({
652
+ enableCompiler: false,
653
+ enableReanimated: false,
654
+ })
655
+ fs.rmSync(projectRoot, { recursive: true, force: true })
656
+ }
657
+ })
658
+ })
@@ -1,4 +1,5 @@
1
- import { extname, relative } from 'node:path'
1
+ import { existsSync, readFileSync } from 'node:fs'
2
+ import { extname, join, relative } 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,49 @@ 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
+ ...props.userSetting,
70
+ ...(userBabelConfig ? { configFile: userBabelConfig, babelrc: true } : {}),
71
+ }
32
72
  }
33
- return getOptions(props)
73
+ return getOptions(props, false, userBabelConfig)
74
+ }
75
+ // an explicit per-file opt-out of babel survives a user config. without
76
+ // this, merely adding babel.config.js flips swc/oxc files to babel.
77
+ const userSetting = props.userSetting
78
+ if (
79
+ userSetting === 'swc' ||
80
+ userSetting === 'oxc' ||
81
+ userSetting === false ||
82
+ (typeof userSetting === 'object' &&
83
+ userSetting !== null &&
84
+ (userSetting.transform === 'swc' || userSetting.transform === 'oxc'))
85
+ ) {
86
+ return null
87
+ }
88
+ if (userBabelConfig) {
89
+ return getOptions(props, false, userBabelConfig)
34
90
  }
35
91
  return null
36
92
  }
37
93
 
38
- const getOptions = (props: Props, force = false): babel.TransformOptions | null => {
94
+ const getOptions = (
95
+ props: Props,
96
+ force = false,
97
+ userBabelConfig: string | null = null
98
+ ): babel.TransformOptions | null => {
39
99
  let plugins: babel.PluginItem[] = []
40
100
 
41
101
  if (force || shouldBabelGenerators(props)) {
@@ -72,8 +132,11 @@ const getOptions = (props: Props, force = false): babel.TransformOptions | null
72
132
  plugins.push(getBabelReactCompilerPlugin(props))
73
133
  }
74
134
 
75
- if (plugins.length) {
76
- return { plugins }
135
+ if (plugins.length || userBabelConfig) {
136
+ return {
137
+ plugins,
138
+ ...(userBabelConfig ? { configFile: userBabelConfig, babelrc: true } : {}),
139
+ }
77
140
  }
78
141
 
79
142
  return null
@@ -91,14 +154,27 @@ const getOptions = (props: Props, force = false): babel.TransformOptions | null
91
154
  export async function transformOxcReactCompiler(
92
155
  id: string,
93
156
  code: string,
94
- target: '18' | '19',
157
+ optionsOrTarget: '18' | '19' | (Record<string, any> & { target?: '18' | '19' }) = '19',
95
158
  sourceMap = false
96
159
  ) {
97
160
  const { transform } = await import('oxc-transform-react')
161
+ const compilerOptions =
162
+ typeof optionsOrTarget === 'string'
163
+ ? { target: optionsOrTarget }
164
+ : optionsOrTarget || {}
165
+
166
+ const target = compilerOptions.target ?? '19'
98
167
  const result = await transform(id, code, {
99
168
  jsx: 'preserve',
100
169
  sourcemap: sourceMap,
101
- reactCompiler: { target },
170
+ reactCompiler: {
171
+ target,
172
+ ...compilerOptions,
173
+ environment: {
174
+ enableCustomTypeDefinitionForReanimated: true,
175
+ ...compilerOptions.environment,
176
+ },
177
+ },
102
178
  })
103
179
 
104
180
  // `errors` with fatal:false are react compiler BAILOUTS ("Cannot access refs
@@ -119,7 +195,10 @@ export async function transformOxcReactCompiler(
119
195
  )
120
196
  }
121
197
 
122
- return { code: result.code, map: sourceMap ? (result.map as any) : undefined }
198
+ return {
199
+ code: result.code,
200
+ map: sourceMap ? (result.map as any) : undefined,
201
+ }
123
202
  }
124
203
 
125
204
  /**
@@ -140,11 +219,18 @@ export async function transformBabel(
140
219
  const babelOptions = {
141
220
  filename: id,
142
221
  compact: false,
143
- babelrc: false,
144
- configFile: false,
222
+ babelrc: options.babelrc ?? false,
223
+ configFile: options.configFile ?? false,
145
224
  sourceMaps: false,
146
225
  minified: false,
147
226
  ...options,
227
+ // vite and rolldown own module syntax and import.meta, so presets written for
228
+ // metro (babel-preset-expo) must keep esm instead of rewriting it for metro's runtime
229
+ caller: {
230
+ name: 'vxrn',
231
+ supportsStaticESM: true,
232
+ supportsDynamicImport: true,
233
+ },
148
234
  presets: [
149
235
  isTS
150
236
  ? [
@@ -168,7 +254,10 @@ export async function transformBabel(
168
254
  plugins: [
169
255
  ...(isTS
170
256
  ? []
171
- : [[hermesParserPlugin, { parseLangTypes: 'flow', reactRuntimeTarget: '19' }]]),
257
+ : [
258
+ [hermesParserPlugin, { parseLangTypes: 'flow', reactRuntimeTarget: '19' }],
259
+ 'babel-plugin-transform-flow-enums',
260
+ ]),
172
261
  ...(options.plugins || []),
173
262
  ...(isTS ? [] : ['@babel/plugin-transform-flow-strip-types']),
174
263
  ],
@@ -184,6 +273,14 @@ export async function transformBabel(
184
273
  })
185
274
  }
186
275
 
276
+ export async function stripFlowTypes(id: string, code: string, sourceMaps = true) {
277
+ const result = await transformBabel(id, code, { sourceMaps })
278
+ return {
279
+ code: result.code!,
280
+ map: result.map,
281
+ }
282
+ }
283
+
187
284
  const getBasePlugins = ({ development }: Props) =>
188
285
  [
189
286
  ['@babel/plugin-transform-destructuring'],
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"}
@@ -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,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;AA0S3C,wBAAsB,wBAAwB,CAC5C,SAAS,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,GAC3B,OAAO,CAAC,YAAY,EAAE,CAAC,CA6VzB"}