@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.
- package/dist/cjs/cache.cjs +112 -109
- package/dist/cjs/configure.cjs +26 -28
- package/dist/cjs/constants.cjs +56 -60
- package/dist/cjs/index.cjs +420 -438
- package/dist/cjs/reactNativeCodegen.cjs +105 -114
- package/dist/cjs/reactNativeViewConfig.cjs +362 -381
- package/dist/cjs/refresh-runtime.cjs +390 -397
- package/dist/cjs/transformBabel.cjs +278 -218
- package/dist/cjs/transformHermesAsync.cjs +36 -47
- package/dist/cjs/transformHermesLoops.cjs +279 -281
- package/dist/cjs/transformSWC.cjs +183 -200
- package/dist/cjs/transformWorklets.cjs +107 -95
- package/dist/cjs/types.cjs +9 -11
- package/dist/cjs/worklets/autoworklet.cjs +277 -222
- package/dist/cjs/worklets/globals.cjs +124 -60
- package/dist/cjs/worklets/hash.cjs +23 -27
- package/dist/cjs/worklets/scope.cjs +338 -359
- package/dist/cjs/worklets/serialize.cjs +73 -75
- package/dist/cjs/worklets/transform.cjs +217 -222
- package/dist/cjs/worklets/types.cjs +9 -11
- package/dist/esm/cache.mjs +96 -89
- package/dist/esm/cache.mjs.map +1 -1
- package/dist/esm/configure.mjs +11 -10
- package/dist/esm/configure.mjs.map +1 -1
- package/dist/esm/constants.mjs +41 -41
- package/dist/esm/constants.mjs.map +1 -1
- package/dist/esm/index.js +417 -420
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/index.mjs +417 -420
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/reactNativeCodegen.mjs +90 -88
- package/dist/esm/reactNativeCodegen.mjs.map +1 -1
- package/dist/esm/reactNativeViewConfig.mjs +347 -362
- package/dist/esm/reactNativeViewConfig.mjs.map +1 -1
- package/dist/esm/refresh-runtime.mjs +372 -376
- package/dist/esm/refresh-runtime.mjs.map +1 -1
- package/dist/esm/transformBabel.mjs +261 -194
- package/dist/esm/transformBabel.mjs.map +1 -1
- package/dist/esm/transformHermesAsync.mjs +20 -21
- package/dist/esm/transformHermesAsync.mjs.map +1 -1
- package/dist/esm/transformHermesLoops.mjs +264 -255
- package/dist/esm/transformHermesLoops.mjs.map +1 -1
- package/dist/esm/transformSWC.mjs +163 -171
- package/dist/esm/transformSWC.mjs.map +1 -1
- package/dist/esm/transformWorklets.mjs +89 -69
- package/dist/esm/transformWorklets.mjs.map +1 -1
- package/dist/esm/types.mjs +0 -2
- package/dist/esm/worklets/autoworklet.mjs +258 -200
- package/dist/esm/worklets/autoworklet.mjs.map +1 -1
- package/dist/esm/worklets/globals.mjs +109 -42
- package/dist/esm/worklets/globals.mjs.map +1 -1
- package/dist/esm/worklets/hash.mjs +10 -9
- package/dist/esm/worklets/hash.mjs.map +1 -1
- package/dist/esm/worklets/scope.mjs +325 -341
- package/dist/esm/worklets/scope.mjs.map +1 -1
- package/dist/esm/worklets/serialize.mjs +60 -58
- package/dist/esm/worklets/serialize.mjs.map +1 -1
- package/dist/esm/worklets/transform.mjs +201 -197
- package/dist/esm/worklets/transform.mjs.map +1 -1
- package/dist/esm/worklets/types.mjs +0 -2
- package/package.json +7 -6
- package/src/cache.ts +37 -7
- package/src/index.ts +33 -18
- package/src/transformBabel.test.ts +311 -1
- package/src/transformBabel.ts +110 -13
- package/types/cache.d.ts +2 -2
- package/types/cache.d.ts.map +1 -1
- package/types/index.d.ts.map +1 -1
- package/types/transformBabel.d.ts +16 -1
- package/types/transformBabel.d.ts.map +1 -1
- package/types/transformSWC.d.ts +1 -4
- package/types/transformSWC.d.ts.map +1 -1
- package/dist/cjs/transformBabel.test.cjs +0 -360
- package/dist/cjs/transformHermesLoops.test.cjs +0 -257
- package/dist/cjs/transformSWC.test.cjs +0 -117
- package/dist/cjs/transformWorklets.test.cjs +0 -604
- package/dist/esm/transformBabel.test.mjs +0 -337
- package/dist/esm/transformBabel.test.mjs.map +0 -1
- package/dist/esm/transformHermesLoops.test.mjs +0 -234
- package/dist/esm/transformHermesLoops.test.mjs.map +0 -1
- package/dist/esm/transformSWC.test.mjs +0 -118
- package/dist/esm/transformSWC.test.mjs.map +0 -1
- package/dist/esm/transformWorklets.test.mjs +0 -581
- package/dist/esm/transformWorklets.test.mjs.map +0 -1
- package/dist/esm/types.mjs.map +0 -1
- package/dist/esm/worklets/types.mjs.map +0 -1
- package/types/transformBabel.test.d.ts +0 -2
- package/types/transformBabel.test.d.ts.map +0 -1
- package/types/transformHermesLoops.test.d.ts +0 -2
- package/types/transformHermesLoops.test.d.ts.map +0 -1
- package/types/transformSWC.test.d.ts +0 -2
- package/types/transformSWC.test.d.ts.map +0 -1
- package/types/transformWorklets.test.d.ts +0 -2
- 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
|
-
|
|
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
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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 (
|
|
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 (
|
|
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
|
+
})
|
package/src/transformBabel.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
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
|
|
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 = (
|
|
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 {
|
|
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
|
-
|
|
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: {
|
|
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 {
|
|
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
|
-
: [
|
|
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;
|
package/types/cache.d.ts.map
CHANGED
|
@@ -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;
|
|
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.map
CHANGED
|
@@ -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;
|
|
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"}
|