@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.
- 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 +428 -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 +247 -221
- package/dist/cjs/transformHermesAsync.cjs +36 -47
- package/dist/cjs/transformHermesClasses.cjs +51 -0
- 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/workletUtils.cjs +30 -0
- package/dist/cjs/worklets/autoworklet.cjs +277 -222
- package/dist/cjs/worklets/globals.cjs +145 -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 +426 -420
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/index.mjs +426 -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 +231 -198
- 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/transformHermesClasses.mjs +25 -0
- package/dist/esm/transformHermesClasses.mjs.map +1 -0
- 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/workletUtils.mjs +5 -0
- package/dist/esm/worklets/autoworklet.mjs +258 -200
- package/dist/esm/worklets/autoworklet.mjs.map +1 -1
- package/dist/esm/worklets/globals.mjs +130 -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 +15 -6
- package/src/cache.ts +37 -7
- package/src/index.ts +44 -19
- package/src/reactNativeViewConfig.ts +6 -1
- package/src/transformBabel.test.ts +351 -17
- package/src/transformBabel.ts +137 -96
- package/src/transformHermesClasses.ts +34 -0
- package/src/transformSWC.ts +12 -1
- package/src/transformWorklets.test.ts +80 -5
- package/src/workletUtils.ts +4 -0
- package/src/worklets/autoworklet.ts +5 -2
- package/src/worklets/globals.ts +23 -0
- package/src/worklets/serialize.ts +1 -1
- package/types/cache.d.ts +2 -2
- package/types/cache.d.ts.map +1 -1
- package/types/index.d.ts +1 -0
- package/types/index.d.ts.map +1 -1
- package/types/reactNativeViewConfig.d.ts.map +1 -1
- package/types/transformBabel.d.ts +16 -1
- package/types/transformBabel.d.ts.map +1 -1
- package/types/transformHermesClasses.d.ts +5 -0
- package/types/transformHermesClasses.d.ts.map +1 -0
- package/types/transformSWC.d.ts +1 -4
- package/types/transformSWC.d.ts.map +1 -1
- package/types/workletUtils.d.ts +4 -0
- package/types/workletUtils.d.ts.map +1 -0
- package/types/worklets/autoworklet.d.ts.map +1 -1
- package/types/worklets/globals.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
|
@@ -35,6 +35,7 @@ export * from './transformWorklets'
|
|
|
35
35
|
export * from './reactNativeCodegen'
|
|
36
36
|
export * from './transformHermesLoops'
|
|
37
37
|
export * from './transformHermesAsync'
|
|
38
|
+
export * from './transformHermesClasses'
|
|
38
39
|
export { clearTransformCache } from './cache'
|
|
39
40
|
export { JS_GLOBALS } from './worklets/globals'
|
|
40
41
|
export { getClosureVariables } from './worklets/scope'
|
|
@@ -162,18 +163,26 @@ async function performBabelTransform({
|
|
|
162
163
|
}
|
|
163
164
|
|
|
164
165
|
if (userTransform !== 'swc' && userTransform !== 'oxc') {
|
|
165
|
-
|
|
166
|
+
let babelOptions = getBabelOptions({
|
|
166
167
|
...transformProps,
|
|
167
168
|
userSetting: userTransform,
|
|
168
169
|
})
|
|
170
|
+
// worklets run without requesting a babel pass.
|
|
171
|
+
if (!babelOptions && shouldTransformWorklets({ id, code })) {
|
|
172
|
+
babelOptions = { plugins: [] }
|
|
173
|
+
}
|
|
169
174
|
|
|
170
175
|
if (babelOptions) {
|
|
171
176
|
const hasCompilerPlugin = babelOptions.plugins?.some(
|
|
172
177
|
(x) => Array.isArray(x) && x[0] === 'babel-plugin-react-compiler'
|
|
173
178
|
)
|
|
174
179
|
|
|
175
|
-
// Check cache first
|
|
176
|
-
|
|
180
|
+
// Check cache first. the user babel config feeds the transform output,
|
|
181
|
+
// so its identity joins the key: adding or editing babel.config.* must
|
|
182
|
+
// miss entries cached without that change.
|
|
183
|
+
const userBabelConfigPath =
|
|
184
|
+
typeof babelOptions.configFile === 'string' ? babelOptions.configFile : null
|
|
185
|
+
const cached = getCachedTransform(id, code, environment, userBabelConfigPath)
|
|
177
186
|
if (cached) {
|
|
178
187
|
perfStats.babel.byEnvironment[environment].transforms++
|
|
179
188
|
if (
|
|
@@ -214,25 +223,32 @@ async function performBabelTransform({
|
|
|
214
223
|
) ?? -1
|
|
215
224
|
|
|
216
225
|
if (compilerPluginIndex !== -1) {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
226
|
+
const compilerPluginConfig = babelOptions.plugins![compilerPluginIndex] as any[]
|
|
227
|
+
const compilerPluginOptions = compilerPluginConfig[1] || {}
|
|
228
|
+
const useBabelCompiler =
|
|
229
|
+
compilerPluginOptions.compiler === 'babel' ||
|
|
230
|
+
process.env.ONE_REACT_COMPILER === 'babel' ||
|
|
231
|
+
process.env.VXRN_REACT_COMPILER === 'babel'
|
|
232
|
+
|
|
233
|
+
if (!useBabelCompiler) {
|
|
234
|
+
// mark callbacks before react compiler moves them into memoized bindings.
|
|
235
|
+
if (useWorklets) {
|
|
236
|
+
workletPreparation = prepareWorkletsForReactCompiler(
|
|
237
|
+
id,
|
|
238
|
+
curCode,
|
|
239
|
+
shouldSourceMap()
|
|
240
|
+
)
|
|
241
|
+
if (workletPreparation) curCode = workletPreparation.code
|
|
242
|
+
}
|
|
243
|
+
compilerOut = await transformOxcReactCompiler(
|
|
220
244
|
id,
|
|
221
245
|
curCode,
|
|
246
|
+
compilerPluginOptions,
|
|
222
247
|
shouldSourceMap()
|
|
223
248
|
)
|
|
224
|
-
if (
|
|
249
|
+
if (compilerOut) curCode = compilerOut.code
|
|
250
|
+
babelOptions.plugins!.splice(compilerPluginIndex, 1)
|
|
225
251
|
}
|
|
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
252
|
}
|
|
237
253
|
|
|
238
254
|
let workletsOut: { code: string; map?: any } | null = null
|
|
@@ -251,7 +267,11 @@ async function performBabelTransform({
|
|
|
251
267
|
const startTime = Date.now()
|
|
252
268
|
let babelOut: { code?: string | null; map?: any } | null = null
|
|
253
269
|
|
|
254
|
-
if (
|
|
270
|
+
if (
|
|
271
|
+
babelOptions.plugins?.length === 0 &&
|
|
272
|
+
!babelOptions.configFile &&
|
|
273
|
+
!babelOptions.babelrc
|
|
274
|
+
) {
|
|
255
275
|
let finalMap: any = undefined
|
|
256
276
|
if (shouldSourceMap()) {
|
|
257
277
|
const intermediateMaps = [
|
|
@@ -311,7 +331,7 @@ async function performBabelTransform({
|
|
|
311
331
|
const result = { code: outCode, map: babelOut.map }
|
|
312
332
|
|
|
313
333
|
// Cache the result
|
|
314
|
-
setCachedTransform(id, code, result, environment)
|
|
334
|
+
setCachedTransform(id, code, result, environment, userBabelConfigPath)
|
|
315
335
|
|
|
316
336
|
return result
|
|
317
337
|
}
|
|
@@ -628,6 +648,11 @@ ${rootJS.code}
|
|
|
628
648
|
transform(code, _id) {
|
|
629
649
|
if (this.environment.name !== 'client') return
|
|
630
650
|
if (code.includes(runtimePublicPath)) return // already wrapped
|
|
651
|
+
// vite's special queries (the same pattern as its SPECIAL_QUERY_RE) make
|
|
652
|
+
// a string, url, or worker wrapper module, never the component source.
|
|
653
|
+
// wrapping one imports the refresh runtime, which writes to `window` and
|
|
654
|
+
// so throws when a worker imports `Foo.tsx?raw`.
|
|
655
|
+
if (/[?&](?:worker|sharedworker|raw|url)\b/.test(_id)) return
|
|
631
656
|
|
|
632
657
|
const id = _id.split('?')[0]
|
|
633
658
|
if (id.includes('node_modules')) return
|
|
@@ -389,7 +389,12 @@ function readCommands(call: any, decls: Declarations): Command[] {
|
|
|
389
389
|
for (const m of commandMembers) {
|
|
390
390
|
const name = m.key?.name ?? m.key?.value
|
|
391
391
|
if (!name) continue
|
|
392
|
-
|
|
392
|
+
// `setPage(ref, index): void` carries its params on the member itself;
|
|
393
|
+
// `setPage: (ref, index) => void` and flow members carry a function type
|
|
394
|
+
const fn =
|
|
395
|
+
m.type === 'TSMethodSignature'
|
|
396
|
+
? m
|
|
397
|
+
: (m.typeAnnotation?.typeAnnotation ?? m.value ?? m.typeAnnotation)
|
|
393
398
|
const params = (fn?.params ?? fn?.parameters ?? []).slice(1)
|
|
394
399
|
byName.set(
|
|
395
400
|
name,
|
|
@@ -4,7 +4,9 @@ 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,
|
|
9
|
+
stripFlowTypes,
|
|
8
10
|
transformBabel,
|
|
9
11
|
transformOxcReactCompiler,
|
|
10
12
|
} from './transformBabel'
|
|
@@ -12,9 +14,8 @@ import {
|
|
|
12
14
|
afterEach(() => {
|
|
13
15
|
configureVXRNCompilerPlugin({ enableReanimated: false })
|
|
14
16
|
})
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
it('uses the app-installed Worklets Babel plugin', () => {
|
|
17
|
+
describe('getBabelOptions worklets ownership', () => {
|
|
18
|
+
it('does not add an automatic Worklets Babel pass', () => {
|
|
18
19
|
const projectRoot = fs.realpathSync(
|
|
19
20
|
fs.mkdtempSync(path.join(os.tmpdir(), 'vxrn-worklets-'))
|
|
20
21
|
)
|
|
@@ -40,7 +41,7 @@ describe('getBabelOptions Worklets resolution', () => {
|
|
|
40
41
|
projectRoot,
|
|
41
42
|
})
|
|
42
43
|
|
|
43
|
-
expect(options
|
|
44
|
+
expect(options).toBeNull()
|
|
44
45
|
} finally {
|
|
45
46
|
fs.rmSync(projectRoot, { recursive: true, force: true })
|
|
46
47
|
}
|
|
@@ -67,6 +68,27 @@ describe('transformBabel Flow parsing', () => {
|
|
|
67
68
|
expect(result?.code).not.toContain('HostComponent')
|
|
68
69
|
})
|
|
69
70
|
|
|
71
|
+
it('preserves React Native Flow enums as runtime values', async () => {
|
|
72
|
+
const result = await transformBabel(
|
|
73
|
+
'/project/VirtualView.js',
|
|
74
|
+
`
|
|
75
|
+
// @flow strict-local
|
|
76
|
+
export enum VirtualViewRenderState {
|
|
77
|
+
Unknown = 0,
|
|
78
|
+
Rendered = 1,
|
|
79
|
+
None = 2,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export const rendered = VirtualViewRenderState.Rendered
|
|
83
|
+
`,
|
|
84
|
+
{ plugins: [] }
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
expect(result?.code).toContain('const VirtualViewRenderState =')
|
|
88
|
+
expect(result?.code).toContain('Rendered: 1')
|
|
89
|
+
expect(result?.code).toContain('VirtualViewRenderState.Rendered')
|
|
90
|
+
})
|
|
91
|
+
|
|
70
92
|
it('rejects a required transform error instead of returning untransformed source', async () => {
|
|
71
93
|
const negativeControl = () => ({
|
|
72
94
|
visitor: {
|
|
@@ -130,6 +152,29 @@ describe('transformOxcReactCompiler', () => {
|
|
|
130
152
|
|
|
131
153
|
expect(result.code).toContain('_c(')
|
|
132
154
|
})
|
|
155
|
+
|
|
156
|
+
it('supports mutating useSharedValue in useEffect via Reanimated environment defaults', async () => {
|
|
157
|
+
const reanimatedCode = `
|
|
158
|
+
import { useEffect } from 'react'
|
|
159
|
+
import { useSharedValue } from 'react-native-reanimated'
|
|
160
|
+
|
|
161
|
+
export function ReanimatedComponent() {
|
|
162
|
+
const val = useSharedValue(0)
|
|
163
|
+
useEffect(() => {
|
|
164
|
+
val.value = 1
|
|
165
|
+
}, [val])
|
|
166
|
+
return <div>{val.value}</div>
|
|
167
|
+
}
|
|
168
|
+
`
|
|
169
|
+
const result = await transformOxcReactCompiler(
|
|
170
|
+
'/project/ReanimatedComponent.tsx',
|
|
171
|
+
reanimatedCode,
|
|
172
|
+
{ target: '19' },
|
|
173
|
+
false
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
expect(result.code).toContain('_c(')
|
|
177
|
+
})
|
|
133
178
|
})
|
|
134
179
|
|
|
135
180
|
describe('compiler plugin multi-stage source map composition', () => {
|
|
@@ -203,7 +248,7 @@ export async function* generatorProbe() {
|
|
|
203
248
|
})
|
|
204
249
|
|
|
205
250
|
describe('shared compiler worklets backend selection', () => {
|
|
206
|
-
it('
|
|
251
|
+
it('does not fall back to Babel when worklet compilation is disabled', async () => {
|
|
207
252
|
const { vi } = await import('vitest')
|
|
208
253
|
const { createVXRNCompilerPlugin } = await import('./index')
|
|
209
254
|
const workletsModule = await import('./transformWorklets')
|
|
@@ -225,8 +270,8 @@ describe('shared compiler worklets backend selection', () => {
|
|
|
225
270
|
const hook = plugin.transform.handler || plugin.transform
|
|
226
271
|
const context = { environment: { name: 'ios' } }
|
|
227
272
|
const result = await hook.call(context, inputCode, tempFile)
|
|
228
|
-
expect(result).
|
|
229
|
-
//
|
|
273
|
+
expect(result).toBeNull()
|
|
274
|
+
// disabling the transform does not select a second backend.
|
|
230
275
|
expect(workletSpy).not.toHaveBeenCalled()
|
|
231
276
|
} finally {
|
|
232
277
|
workletSpy.mockRestore()
|
|
@@ -238,7 +283,7 @@ describe('shared compiler worklets backend selection', () => {
|
|
|
238
283
|
}
|
|
239
284
|
})
|
|
240
285
|
|
|
241
|
-
it('
|
|
286
|
+
it('compiles worklets with One when no Babel pass is requested', async () => {
|
|
242
287
|
const compiler = await import('./index')
|
|
243
288
|
const { vi } = await import('vitest')
|
|
244
289
|
const workletsModule = await import('./transformWorklets')
|
|
@@ -261,7 +306,8 @@ describe('shared compiler worklets backend selection', () => {
|
|
|
261
306
|
const context = { environment: { name: 'ios' } }
|
|
262
307
|
const result = await hook.call(context, inputCode, tempFile)
|
|
263
308
|
expect(result).toBeDefined()
|
|
264
|
-
|
|
309
|
+
expect(result.code).toContain('__workletHash')
|
|
310
|
+
// the worklet-only file still reaches the One transform.
|
|
265
311
|
expect(workletSpy).toHaveBeenCalled()
|
|
266
312
|
} finally {
|
|
267
313
|
workletSpy.mockRestore()
|
|
@@ -284,7 +330,7 @@ describe('shared compiler worklets backend selection', () => {
|
|
|
284
330
|
fs.writeFileSync(tempFile, inputCode)
|
|
285
331
|
|
|
286
332
|
try {
|
|
287
|
-
//
|
|
333
|
+
// begin with worklet compilation disabled.
|
|
288
334
|
configureVXRNCompilerPlugin({
|
|
289
335
|
enableReanimated: true,
|
|
290
336
|
enableNativeWorklets: false,
|
|
@@ -297,10 +343,10 @@ describe('shared compiler worklets backend selection', () => {
|
|
|
297
343
|
const context = { environment: { name: 'ios' } }
|
|
298
344
|
|
|
299
345
|
const res1 = await hook1.call(context, inputCode, tempFile)
|
|
300
|
-
expect(res1).
|
|
346
|
+
expect(res1).toBeNull()
|
|
301
347
|
expect(workletSpy).toHaveBeenCalledTimes(0)
|
|
302
348
|
|
|
303
|
-
//
|
|
349
|
+
// enable the transform on the same unchanged file.
|
|
304
350
|
configureVXRNCompilerPlugin({
|
|
305
351
|
enableReanimated: true,
|
|
306
352
|
enableNativeWorklets: true,
|
|
@@ -313,15 +359,16 @@ describe('shared compiler worklets backend selection', () => {
|
|
|
313
359
|
|
|
314
360
|
const res2 = await hook2.call(context, inputCode, tempFile)
|
|
315
361
|
expect(res2).toBeDefined()
|
|
316
|
-
|
|
362
|
+
expect(res2.code).toContain('__workletHash')
|
|
363
|
+
// the cache must not serve the untransformed entry.
|
|
317
364
|
expect(workletSpy).toHaveBeenCalledTimes(1)
|
|
318
365
|
|
|
319
|
-
//
|
|
366
|
+
// reuse the transformed cache entry.
|
|
320
367
|
const res3 = await hook2.call(context, inputCode, tempFile)
|
|
321
368
|
expect(res3).toBeDefined()
|
|
322
369
|
expect(workletSpy).toHaveBeenCalledTimes(1)
|
|
323
370
|
|
|
324
|
-
//
|
|
371
|
+
// disable the transform; the cache must not serve a transformed entry.
|
|
325
372
|
configureVXRNCompilerPlugin({
|
|
326
373
|
enableReanimated: true,
|
|
327
374
|
enableNativeWorklets: false,
|
|
@@ -333,8 +380,8 @@ describe('shared compiler worklets backend selection', () => {
|
|
|
333
380
|
const hook3 = plugin3.transform.handler || plugin3.transform
|
|
334
381
|
|
|
335
382
|
const res4 = await hook3.call(context, inputCode, tempFile)
|
|
336
|
-
expect(res4).
|
|
337
|
-
//
|
|
383
|
+
expect(res4).toBeNull()
|
|
384
|
+
// no second transform was requested.
|
|
338
385
|
expect(workletSpy).toHaveBeenCalledTimes(1)
|
|
339
386
|
} finally {
|
|
340
387
|
workletSpy.mockRestore()
|
|
@@ -346,3 +393,290 @@ describe('shared compiler worklets backend selection', () => {
|
|
|
346
393
|
}
|
|
347
394
|
})
|
|
348
395
|
})
|
|
396
|
+
|
|
397
|
+
describe('findUserBabelConfig and user Babel config respect', () => {
|
|
398
|
+
it('finds user babel config and ignores generated configs', () => {
|
|
399
|
+
const projectRoot = fs.realpathSync(
|
|
400
|
+
fs.mkdtempSync(path.join(os.tmpdir(), 'vxrn-babel-conf-'))
|
|
401
|
+
)
|
|
402
|
+
try {
|
|
403
|
+
expect(findUserBabelConfig(projectRoot)).toBeNull()
|
|
404
|
+
|
|
405
|
+
// written by `one patch` with the generated marker -> ignored
|
|
406
|
+
const generatedFile = path.join(projectRoot, 'babel.config.js')
|
|
407
|
+
fs.writeFileSync(
|
|
408
|
+
generatedFile,
|
|
409
|
+
'// @one/generated bundler-config\nmodule.exports = {}'
|
|
410
|
+
)
|
|
411
|
+
expect(findUserBabelConfig(projectRoot)).toBeNull()
|
|
412
|
+
|
|
413
|
+
// Overwritten with user config -> detected
|
|
414
|
+
fs.writeFileSync(generatedFile, 'module.exports = { plugins: [] }')
|
|
415
|
+
expect(findUserBabelConfig(projectRoot)).toBe(generatedFile)
|
|
416
|
+
} finally {
|
|
417
|
+
fs.rmSync(projectRoot, { recursive: true, force: true })
|
|
418
|
+
}
|
|
419
|
+
})
|
|
420
|
+
|
|
421
|
+
it('includes user configFile and babelrc in getBabelOptions for project files', () => {
|
|
422
|
+
const projectRoot = fs.realpathSync(
|
|
423
|
+
fs.mkdtempSync(path.join(os.tmpdir(), 'vxrn-babel-conf-'))
|
|
424
|
+
)
|
|
425
|
+
const userConfig = path.join(projectRoot, 'babel.config.json')
|
|
426
|
+
fs.writeFileSync(userConfig, '{"plugins": []}')
|
|
427
|
+
try {
|
|
428
|
+
// For project file:
|
|
429
|
+
const projectFileOptions = getBabelOptions({
|
|
430
|
+
id: path.join(projectRoot, 'src', 'index.tsx'),
|
|
431
|
+
code: `export const hello = () => 123`,
|
|
432
|
+
projectRoot,
|
|
433
|
+
development: true,
|
|
434
|
+
environment: 'client',
|
|
435
|
+
reactForRNVersion: '19',
|
|
436
|
+
})
|
|
437
|
+
expect(projectFileOptions?.configFile).toBe(userConfig)
|
|
438
|
+
expect(projectFileOptions?.babelrc).toBe(true)
|
|
439
|
+
|
|
440
|
+
// For node_modules file: should not attach user configFile
|
|
441
|
+
const nodeModulesOptions = getBabelOptions({
|
|
442
|
+
id: path.join(projectRoot, 'node_modules', 'foo', 'index.js'),
|
|
443
|
+
code: `export const foo = 1`,
|
|
444
|
+
projectRoot,
|
|
445
|
+
development: true,
|
|
446
|
+
environment: 'client',
|
|
447
|
+
reactForRNVersion: '19',
|
|
448
|
+
})
|
|
449
|
+
expect(nodeModulesOptions).toBeNull()
|
|
450
|
+
} finally {
|
|
451
|
+
fs.rmSync(projectRoot, { recursive: true, force: true })
|
|
452
|
+
}
|
|
453
|
+
})
|
|
454
|
+
|
|
455
|
+
it('runs user babel config transforms via transformBabel', async () => {
|
|
456
|
+
const projectRoot = fs.realpathSync(
|
|
457
|
+
fs.mkdtempSync(path.join(os.tmpdir(), 'vxrn-babel-conf-'))
|
|
458
|
+
)
|
|
459
|
+
const userConfig = path.join(projectRoot, 'babel.config.json')
|
|
460
|
+
// A babel config that injects a banner/plugin or standard babel syntax
|
|
461
|
+
fs.writeFileSync(
|
|
462
|
+
userConfig,
|
|
463
|
+
JSON.stringify({
|
|
464
|
+
comments: false,
|
|
465
|
+
})
|
|
466
|
+
)
|
|
467
|
+
try {
|
|
468
|
+
const code = '/* remove me */ export const x = 1'
|
|
469
|
+
const res = await transformBabel(path.join(projectRoot, 'src', 'index.ts'), code, {
|
|
470
|
+
configFile: userConfig,
|
|
471
|
+
babelrc: true,
|
|
472
|
+
})
|
|
473
|
+
expect(res.code).not.toContain('remove me')
|
|
474
|
+
expect(res.code).toContain('export const x = 1')
|
|
475
|
+
} finally {
|
|
476
|
+
fs.rmSync(projectRoot, { recursive: true, force: true })
|
|
477
|
+
}
|
|
478
|
+
})
|
|
479
|
+
|
|
480
|
+
it.each(['ios', 'android', 'client', 'ssr'] as const)(
|
|
481
|
+
'tells user babel config the %s platform and that the bundler keeps static esm',
|
|
482
|
+
async (environment) => {
|
|
483
|
+
const projectRoot = fs.realpathSync(
|
|
484
|
+
fs.mkdtempSync(path.join(os.tmpdir(), 'vxrn-babel-conf-'))
|
|
485
|
+
)
|
|
486
|
+
const userConfig = path.join(projectRoot, 'babel.config.js')
|
|
487
|
+
// presets such as babel-preset-expo read this caller to decide whether to
|
|
488
|
+
// rewrite esm to commonjs and import.meta to a metro runtime global
|
|
489
|
+
fs.writeFileSync(
|
|
490
|
+
userConfig,
|
|
491
|
+
`module.exports = (api) => ({
|
|
492
|
+
comments: !api.caller((c) => c?.name === 'vxrn' && c?.supportsStaticESM === true && c?.platform === '${environment === 'ios' || environment === 'android' ? environment : 'web'}'),
|
|
493
|
+
})`
|
|
494
|
+
)
|
|
495
|
+
try {
|
|
496
|
+
const id = path.join(projectRoot, 'src', 'index.ts')
|
|
497
|
+
const code = '/* remove me */ export const x = 1'
|
|
498
|
+
const options = getBabelOptions({
|
|
499
|
+
id,
|
|
500
|
+
code,
|
|
501
|
+
projectRoot,
|
|
502
|
+
environment,
|
|
503
|
+
development: false,
|
|
504
|
+
reactForRNVersion: '19',
|
|
505
|
+
})
|
|
506
|
+
expect(options).not.toBeNull()
|
|
507
|
+
const res = await transformBabel(id, code, options!)
|
|
508
|
+
expect(res.code).not.toContain('remove me')
|
|
509
|
+
} finally {
|
|
510
|
+
fs.rmSync(projectRoot, { recursive: true, force: true })
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
)
|
|
514
|
+
})
|
|
515
|
+
describe('explicit swc/oxc per-file choice with a user babel config', () => {
|
|
516
|
+
it('returns null for swc/oxc string and object forms', () => {
|
|
517
|
+
const projectRoot = fs.realpathSync(
|
|
518
|
+
fs.mkdtempSync(path.join(os.tmpdir(), 'vxrn-babel-conf-'))
|
|
519
|
+
)
|
|
520
|
+
const userConfig = path.join(projectRoot, 'babel.config.js')
|
|
521
|
+
fs.writeFileSync(userConfig, 'module.exports = { plugins: [] }')
|
|
522
|
+
try {
|
|
523
|
+
const base = {
|
|
524
|
+
id: path.join(projectRoot, 'src', 'index.tsx'),
|
|
525
|
+
code: `export const x = 1`,
|
|
526
|
+
projectRoot,
|
|
527
|
+
development: true,
|
|
528
|
+
environment: 'client' as const,
|
|
529
|
+
reactForRNVersion: '19' as const,
|
|
530
|
+
}
|
|
531
|
+
// merely adding babel.config.js must not flip an explicit non-babel choice
|
|
532
|
+
expect(getBabelOptions({ ...base, userSetting: 'swc' })).toBeNull()
|
|
533
|
+
expect(getBabelOptions({ ...base, userSetting: 'oxc' })).toBeNull()
|
|
534
|
+
expect(getBabelOptions({ ...base, userSetting: { transform: 'swc' } })).toBeNull()
|
|
535
|
+
expect(getBabelOptions({ ...base, userSetting: { transform: 'oxc' } })).toBeNull()
|
|
536
|
+
// controls: babel choices still resolve through the user config
|
|
537
|
+
expect(getBabelOptions({ ...base, userSetting: 'babel' })?.configFile).toBe(
|
|
538
|
+
userConfig
|
|
539
|
+
)
|
|
540
|
+
expect(
|
|
541
|
+
getBabelOptions({ ...base, userSetting: { transform: 'babel' } })?.configFile
|
|
542
|
+
).toBe(userConfig)
|
|
543
|
+
expect(getBabelOptions(base)?.configFile).toBe(userConfig)
|
|
544
|
+
} finally {
|
|
545
|
+
fs.rmSync(projectRoot, { recursive: true, force: true })
|
|
546
|
+
}
|
|
547
|
+
})
|
|
548
|
+
|
|
549
|
+
it('skips the transform end-to-end for object-form swc', async () => {
|
|
550
|
+
const { createVXRNCompilerPlugin } = await import('./index')
|
|
551
|
+
const projectRoot = fs.realpathSync(
|
|
552
|
+
fs.mkdtempSync(path.join(os.tmpdir(), 'vxrn-babel-conf-'))
|
|
553
|
+
)
|
|
554
|
+
const srcDir = path.join(projectRoot, 'src')
|
|
555
|
+
fs.mkdirSync(srcDir, { recursive: true })
|
|
556
|
+
const file = path.join(srcDir, 'index.ts')
|
|
557
|
+
const code = `export const x = 1`
|
|
558
|
+
fs.writeFileSync(file, code)
|
|
559
|
+
fs.writeFileSync(
|
|
560
|
+
path.join(projectRoot, 'babel.config.js'),
|
|
561
|
+
'module.exports = { plugins: [] }'
|
|
562
|
+
)
|
|
563
|
+
configureVXRNCompilerPlugin({
|
|
564
|
+
enableCompiler: false,
|
|
565
|
+
enableReanimated: false,
|
|
566
|
+
})
|
|
567
|
+
try {
|
|
568
|
+
const plugins = await createVXRNCompilerPlugin({
|
|
569
|
+
transform: () => ({ transform: 'swc' }) as any,
|
|
570
|
+
})
|
|
571
|
+
const plugin = plugins.find((p: any) => p.name === 'one:compiler') as any
|
|
572
|
+
await plugin.configResolved({ root: projectRoot, build: {} })
|
|
573
|
+
const hook = plugin.transform.handler || plugin.transform
|
|
574
|
+
const result = await hook.call({ environment: { name: 'client' } }, code, file)
|
|
575
|
+
expect(result == null).toBe(true)
|
|
576
|
+
} finally {
|
|
577
|
+
configureVXRNCompilerPlugin({
|
|
578
|
+
enableCompiler: false,
|
|
579
|
+
enableReanimated: false,
|
|
580
|
+
})
|
|
581
|
+
fs.rmSync(projectRoot, { recursive: true, force: true })
|
|
582
|
+
}
|
|
583
|
+
})
|
|
584
|
+
})
|
|
585
|
+
|
|
586
|
+
describe('user babel config end-to-end through the compiler plugin', () => {
|
|
587
|
+
it('runs the user config when default plugins are empty', async () => {
|
|
588
|
+
const { createVXRNCompilerPlugin } = await import('./index')
|
|
589
|
+
const projectRoot = fs.realpathSync(
|
|
590
|
+
fs.mkdtempSync(path.join(os.tmpdir(), 'vxrn-babel-e2e-'))
|
|
591
|
+
)
|
|
592
|
+
const srcDir = path.join(projectRoot, 'src')
|
|
593
|
+
fs.mkdirSync(srcDir, { recursive: true })
|
|
594
|
+
const file = path.join(srcDir, 'index.ts')
|
|
595
|
+
const marker = 'babel-e2e-probe'
|
|
596
|
+
const code = `/* ${marker} */ export const x = 1`
|
|
597
|
+
fs.writeFileSync(file, code)
|
|
598
|
+
fs.writeFileSync(
|
|
599
|
+
path.join(projectRoot, 'babel.config.json'),
|
|
600
|
+
JSON.stringify({ comments: false })
|
|
601
|
+
)
|
|
602
|
+
configureVXRNCompilerPlugin({
|
|
603
|
+
enableCompiler: false,
|
|
604
|
+
enableReanimated: false,
|
|
605
|
+
})
|
|
606
|
+
try {
|
|
607
|
+
const plugins = await createVXRNCompilerPlugin()
|
|
608
|
+
const plugin = plugins.find((p: any) => p.name === 'one:compiler') as any
|
|
609
|
+
await plugin.configResolved({ root: projectRoot, build: {} })
|
|
610
|
+
const hook = plugin.transform.handler || plugin.transform
|
|
611
|
+
const result = await hook.call({ environment: { name: 'client' } }, code, file)
|
|
612
|
+
// default plugins are empty here, but the user configFile must still
|
|
613
|
+
// route through transformBabel instead of taking the skip fast-path
|
|
614
|
+
expect(result).toBeDefined()
|
|
615
|
+
expect(result.code).toContain('export const x = 1')
|
|
616
|
+
expect(result.code).not.toContain(marker)
|
|
617
|
+
} finally {
|
|
618
|
+
configureVXRNCompilerPlugin({
|
|
619
|
+
enableCompiler: false,
|
|
620
|
+
enableReanimated: false,
|
|
621
|
+
})
|
|
622
|
+
fs.rmSync(projectRoot, { recursive: true, force: true })
|
|
623
|
+
}
|
|
624
|
+
})
|
|
625
|
+
|
|
626
|
+
it('invalidates the cache when the user babel config is added or edited', async () => {
|
|
627
|
+
const { createVXRNCompilerPlugin } = await import('./index')
|
|
628
|
+
const projectRoot = fs.realpathSync(
|
|
629
|
+
fs.mkdtempSync(path.join(os.tmpdir(), 'vxrn-babel-cache-'))
|
|
630
|
+
)
|
|
631
|
+
const srcDir = path.join(projectRoot, 'src')
|
|
632
|
+
fs.mkdirSync(srcDir, { recursive: true })
|
|
633
|
+
const file = path.join(srcDir, 'index.ts')
|
|
634
|
+
const marker = 'babel-cache-probe'
|
|
635
|
+
const code = `/* ${marker} */ export const x = 1`
|
|
636
|
+
fs.writeFileSync(file, code)
|
|
637
|
+
const userConfig = path.join(projectRoot, 'babel.config.json')
|
|
638
|
+
configureVXRNCompilerPlugin({
|
|
639
|
+
enableCompiler: false,
|
|
640
|
+
enableReanimated: false,
|
|
641
|
+
})
|
|
642
|
+
try {
|
|
643
|
+
const plugins = await createVXRNCompilerPlugin({
|
|
644
|
+
transform: () => 'babel' as const,
|
|
645
|
+
})
|
|
646
|
+
const plugin = plugins.find((p: any) => p.name === 'one:compiler') as any
|
|
647
|
+
await plugin.configResolved({ root: projectRoot, build: {} })
|
|
648
|
+
const hook = plugin.transform.handler || plugin.transform
|
|
649
|
+
const context = { environment: { name: 'client' } }
|
|
650
|
+
|
|
651
|
+
const res1 = await hook.call(context, code, file)
|
|
652
|
+
expect(res1.code).toContain(marker)
|
|
653
|
+
|
|
654
|
+
// adding a config without touching the source must miss the cache
|
|
655
|
+
fs.writeFileSync(userConfig, JSON.stringify({ comments: false }))
|
|
656
|
+
const res2 = await hook.call(context, code, file)
|
|
657
|
+
expect(res2.code).not.toContain(marker)
|
|
658
|
+
|
|
659
|
+
// editing the config contents must miss again
|
|
660
|
+
fs.writeFileSync(userConfig, JSON.stringify({ comments: true }))
|
|
661
|
+
const res3 = await hook.call(context, code, file)
|
|
662
|
+
expect(res3.code).toContain(marker)
|
|
663
|
+
} finally {
|
|
664
|
+
configureVXRNCompilerPlugin({
|
|
665
|
+
enableCompiler: false,
|
|
666
|
+
enableReanimated: false,
|
|
667
|
+
})
|
|
668
|
+
fs.rmSync(projectRoot, { recursive: true, force: true })
|
|
669
|
+
}
|
|
670
|
+
})
|
|
671
|
+
})
|
|
672
|
+
|
|
673
|
+
describe('Flow and JSX parsing', () => {
|
|
674
|
+
it('strips Flow types while retaining JSX in published JavaScript', async () => {
|
|
675
|
+
const result = await stripFlowTypes(
|
|
676
|
+
'/app/VideoView.js',
|
|
677
|
+
`export function View(props: {value: string}) { return <NativeView {...props} /> }`
|
|
678
|
+
)
|
|
679
|
+
expect(result.code).not.toContain('value: string')
|
|
680
|
+
expect(result.code).toContain('<NativeView')
|
|
681
|
+
})
|
|
682
|
+
})
|