@vxrn/compiler 1.25.16 → 1.26.0

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 (130) hide show
  1. package/dist/cjs/cache.cjs +4 -2
  2. package/dist/cjs/configure.cjs +9 -1
  3. package/dist/cjs/index.cjs +87 -5
  4. package/dist/cjs/reactNativeCodegen.cjs +140 -0
  5. package/dist/cjs/reactNativeViewConfig.cjs +489 -0
  6. package/dist/cjs/transformBabel.cjs +15 -20
  7. package/dist/cjs/transformBabel.test.cjs +260 -0
  8. package/dist/cjs/transformHermesAsync.cjs +61 -0
  9. package/dist/cjs/transformHermesLoops.cjs +319 -0
  10. package/dist/cjs/transformHermesLoops.test.cjs +257 -0
  11. package/dist/cjs/transformSWC.cjs +110 -138
  12. package/dist/cjs/transformSWC.test.cjs +117 -0
  13. package/dist/cjs/transformWorklets.cjs +123 -0
  14. package/dist/cjs/transformWorklets.test.cjs +604 -0
  15. package/dist/cjs/worklets/autoworklet.cjs +265 -0
  16. package/dist/cjs/worklets/globals.cjs +73 -0
  17. package/dist/cjs/worklets/hash.cjs +38 -0
  18. package/dist/cjs/worklets/scope.cjs +370 -0
  19. package/dist/cjs/worklets/serialize.cjs +101 -0
  20. package/dist/cjs/worklets/transform.cjs +266 -0
  21. package/dist/cjs/worklets/types.cjs +18 -0
  22. package/dist/esm/cache.mjs +5 -3
  23. package/dist/esm/cache.mjs.map +1 -1
  24. package/dist/esm/configure.mjs +8 -1
  25. package/dist/esm/configure.mjs.map +1 -1
  26. package/dist/esm/index.js +75 -6
  27. package/dist/esm/index.js.map +1 -1
  28. package/dist/esm/index.mjs +75 -6
  29. package/dist/esm/index.mjs.map +1 -1
  30. package/dist/esm/reactNativeCodegen.mjs +104 -0
  31. package/dist/esm/reactNativeCodegen.mjs.map +1 -0
  32. package/dist/esm/reactNativeViewConfig.mjs +460 -0
  33. package/dist/esm/reactNativeViewConfig.mjs.map +1 -0
  34. package/dist/esm/transformBabel.mjs +14 -19
  35. package/dist/esm/transformBabel.mjs.map +1 -1
  36. package/dist/esm/transformBabel.test.mjs +261 -1
  37. package/dist/esm/transformBabel.test.mjs.map +1 -1
  38. package/dist/esm/transformHermesAsync.mjs +25 -0
  39. package/dist/esm/transformHermesAsync.mjs.map +1 -0
  40. package/dist/esm/transformHermesLoops.mjs +283 -0
  41. package/dist/esm/transformHermesLoops.mjs.map +1 -0
  42. package/dist/esm/transformHermesLoops.test.mjs +234 -0
  43. package/dist/esm/transformHermesLoops.test.mjs.map +1 -0
  44. package/dist/esm/transformSWC.mjs +96 -139
  45. package/dist/esm/transformSWC.mjs.map +1 -1
  46. package/dist/esm/transformSWC.test.mjs +118 -0
  47. package/dist/esm/transformSWC.test.mjs.map +1 -0
  48. package/dist/esm/transformWorklets.mjs +82 -0
  49. package/dist/esm/transformWorklets.mjs.map +1 -0
  50. package/dist/esm/transformWorklets.test.mjs +581 -0
  51. package/dist/esm/transformWorklets.test.mjs.map +1 -0
  52. package/dist/esm/worklets/autoworklet.mjs +234 -0
  53. package/dist/esm/worklets/autoworklet.mjs.map +1 -0
  54. package/dist/esm/worklets/globals.mjs +46 -0
  55. package/dist/esm/worklets/globals.mjs.map +1 -0
  56. package/dist/esm/worklets/hash.mjs +13 -0
  57. package/dist/esm/worklets/hash.mjs.map +1 -0
  58. package/dist/esm/worklets/scope.mjs +345 -0
  59. package/dist/esm/worklets/scope.mjs.map +1 -0
  60. package/dist/esm/worklets/serialize.mjs +75 -0
  61. package/dist/esm/worklets/serialize.mjs.map +1 -0
  62. package/dist/esm/worklets/transform.mjs +229 -0
  63. package/dist/esm/worklets/transform.mjs.map +1 -0
  64. package/dist/esm/worklets/types.mjs +2 -0
  65. package/dist/esm/worklets/types.mjs.map +1 -0
  66. package/package.json +11 -6
  67. package/src/cache.ts +10 -3
  68. package/src/configure.ts +12 -0
  69. package/src/index.ts +96 -13
  70. package/src/reactNativeCodegen.ts +181 -0
  71. package/src/reactNativeViewConfig.ts +593 -0
  72. package/src/transformBabel.test.ts +268 -1
  73. package/src/transformBabel.ts +21 -31
  74. package/src/transformHermesAsync.ts +30 -0
  75. package/src/transformHermesLoops.test.ts +341 -0
  76. package/src/transformHermesLoops.ts +435 -0
  77. package/src/transformSWC.test.ts +132 -0
  78. package/src/transformSWC.ts +121 -161
  79. package/src/transformWorklets.test.ts +708 -0
  80. package/src/transformWorklets.ts +160 -0
  81. package/src/types.ts +4 -4
  82. package/src/worklets/autoworklet.ts +391 -0
  83. package/src/worklets/globals.ts +129 -0
  84. package/src/worklets/hash.ts +14 -0
  85. package/src/worklets/scope.ts +401 -0
  86. package/src/worklets/serialize.ts +114 -0
  87. package/src/worklets/transform.ts +319 -0
  88. package/src/worklets/types.ts +26 -0
  89. package/types/cache.d.ts +1 -0
  90. package/types/cache.d.ts.map +1 -1
  91. package/types/configure.d.ts +2 -0
  92. package/types/configure.d.ts.map +1 -1
  93. package/types/index.d.ts +6 -0
  94. package/types/index.d.ts.map +1 -1
  95. package/types/reactNativeCodegen.d.ts +5 -0
  96. package/types/reactNativeCodegen.d.ts.map +1 -0
  97. package/types/reactNativeViewConfig.d.ts +35 -0
  98. package/types/reactNativeViewConfig.d.ts.map +1 -0
  99. package/types/transformBabel.d.ts +3 -3
  100. package/types/transformBabel.d.ts.map +1 -1
  101. package/types/transformHermesAsync.d.ts +5 -0
  102. package/types/transformHermesAsync.d.ts.map +1 -0
  103. package/types/transformHermesLoops.d.ts +5 -0
  104. package/types/transformHermesLoops.d.ts.map +1 -0
  105. package/types/transformHermesLoops.test.d.ts +2 -0
  106. package/types/transformHermesLoops.test.d.ts.map +1 -0
  107. package/types/transformSWC.d.ts +13 -3
  108. package/types/transformSWC.d.ts.map +1 -1
  109. package/types/transformSWC.test.d.ts +2 -0
  110. package/types/transformSWC.test.d.ts.map +1 -0
  111. package/types/transformWorklets.d.ts +23 -0
  112. package/types/transformWorklets.d.ts.map +1 -0
  113. package/types/transformWorklets.test.d.ts +2 -0
  114. package/types/transformWorklets.test.d.ts.map +1 -0
  115. package/types/types.d.ts +4 -4
  116. package/types/types.d.ts.map +1 -1
  117. package/types/worklets/autoworklet.d.ts +14 -0
  118. package/types/worklets/autoworklet.d.ts.map +1 -0
  119. package/types/worklets/globals.d.ts +4 -0
  120. package/types/worklets/globals.d.ts.map +1 -0
  121. package/types/worklets/hash.d.ts +5 -0
  122. package/types/worklets/hash.d.ts.map +1 -0
  123. package/types/worklets/scope.d.ts +7 -0
  124. package/types/worklets/scope.d.ts.map +1 -0
  125. package/types/worklets/serialize.d.ts +12 -0
  126. package/types/worklets/serialize.d.ts.map +1 -0
  127. package/types/worklets/transform.d.ts +10 -0
  128. package/types/worklets/transform.d.ts.map +1 -0
  129. package/types/worklets/types.d.ts +21 -0
  130. package/types/worklets/types.d.ts.map +1 -0
@@ -0,0 +1,341 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import * as vm from 'node:vm'
3
+ import { transformHermesLoops } from './transformHermesLoops'
4
+
5
+ /**
6
+ * Hermes emits one CreateFunctionEnvironment before a loop rather than one per
7
+ * iteration, and that environment covers the loop's head AND everything its
8
+ * body declares, so every lexical binding under a loop behaves there exactly
9
+ * like `var`. Rewriting them all that way reproduces Hermes on a spec-correct
10
+ * runtime, which is what makes these assertions meaningful: every one of them
11
+ * returns the wrong answer against the untransformed source.
12
+ *
13
+ * Verified against `hermesc -dump-bytecode`, which emits a single
14
+ * CreateFunctionEnvironment before the loop and a StoreToEnvironment into the
15
+ * same slot on each iteration.
16
+ */
17
+ function runAsHermes(code: string) {
18
+ const asHermesWouldSeeIt = code.replace(/\b(?:let|const)\b/g, 'var')
19
+ const sandbox: any = { result: undefined, console }
20
+ vm.runInNewContext(asHermesWouldSeeIt, sandbox)
21
+ return sandbox.result
22
+ }
23
+
24
+ function run(source: string) {
25
+ const out = transformHermesLoops(source, 'test.js')
26
+ return runAsHermes(out ? out.code : source)
27
+ }
28
+
29
+ describe('transformHermesLoops', () => {
30
+ it.each([
31
+ ['if (false)', []],
32
+ ['if (true)', [1, 2]],
33
+ ['for (var pass = 0; pass < 2; pass++)', [1, 2, 1, 2]],
34
+ ['while (passes++ < 2)', [1, 2, 1, 2]],
35
+ ])('keeps a captured loop inside a single-statement %s body', (parent, expected) => {
36
+ expect(
37
+ run(`
38
+ var fns = []
39
+ var passes = 0
40
+ ${parent} for (const n of [1, 2]) fns.push(() => n)
41
+ result = fns.map((f) => f())
42
+ `)
43
+ ).toEqual(expected)
44
+ })
45
+
46
+ it.each([true, false])(
47
+ 'preserves if/else selection when the condition is %s',
48
+ (condition) => {
49
+ expect(
50
+ run(`
51
+ var fns = []
52
+ if (${condition}) for (const n of [1, 2]) fns.push(() => n)
53
+ else for (const n of [3, 4]) fns.push(() => n)
54
+ result = fns.map((f) => f())
55
+ `)
56
+ ).toEqual(condition ? [1, 2] : [3, 4])
57
+ }
58
+ )
59
+
60
+ it('preserves a do/while with a single-statement loop body', () => {
61
+ expect(
62
+ run(`
63
+ var fns = []
64
+ var passes = 0
65
+ do for (const n of [1, 2]) fns.push(() => n)
66
+ while (passes++ < 1)
67
+ result = fns.map((f) => f())
68
+ `)
69
+ ).toEqual([1, 2, 1, 2])
70
+ })
71
+
72
+ it('guards optional Zero query patches through nested loop transforms', () => {
73
+ expect(
74
+ run(`
75
+ var fns = []
76
+ var parts = [{}, { desiredQueriesPatches: { a: ['x', 'y'], b: ['z'] } }]
77
+ for (const part of parts) {
78
+ if (part.desiredQueriesPatches)
79
+ for (const [clientID, queriesPatch] of Object.entries(part.desiredQueriesPatches))
80
+ for (const op of queriesPatch) fns.push(() => clientID + ':' + op)
81
+ }
82
+ result = fns.map((f) => f())
83
+ `)
84
+ ).toEqual(['a:x', 'a:y', 'b:z'])
85
+ })
86
+
87
+ it('gives a C-style loop a per-iteration binding', () => {
88
+ expect(
89
+ run(`
90
+ var fns = []
91
+ for (let i = 0; i < 3; i++) fns.push(() => i)
92
+ result = fns.map((f) => f())
93
+ `)
94
+ ).toEqual([0, 1, 2])
95
+ })
96
+
97
+ it('gives for-of and for-in per-iteration bindings', () => {
98
+ expect(
99
+ run(`
100
+ var fns = []
101
+ for (const label of ['a', 'b']) fns.push(() => label)
102
+ for (const key in { x: 1, y: 2 }) fns.push(() => key)
103
+ result = fns.map((f) => f())
104
+ `)
105
+ ).toEqual(['a', 'b', 'x', 'y'])
106
+ })
107
+
108
+ it('preserves break', () => {
109
+ expect(
110
+ run(`
111
+ var fns = []
112
+ for (let i = 0; i < 10; i++) {
113
+ if (i === 3) break
114
+ fns.push(() => i)
115
+ }
116
+ result = fns.map((f) => f())
117
+ `)
118
+ ).toEqual([0, 1, 2])
119
+ })
120
+
121
+ it('preserves continue', () => {
122
+ expect(
123
+ run(`
124
+ var fns = []
125
+ for (let i = 0; i < 5; i++) {
126
+ if (i % 2 === 0) continue
127
+ fns.push(() => i)
128
+ }
129
+ result = fns.map((f) => f())
130
+ `)
131
+ ).toEqual([1, 3])
132
+ })
133
+
134
+ it('preserves return out of the enclosing function', () => {
135
+ expect(
136
+ run(`
137
+ function find() {
138
+ for (const n of [1, 2, 3]) {
139
+ const get = () => n
140
+ if (n === 2) return get()
141
+ }
142
+ return 'none'
143
+ }
144
+ result = find()
145
+ `)
146
+ ).toBe(2)
147
+ })
148
+
149
+ it('preserves a bare return', () => {
150
+ expect(
151
+ run(`
152
+ var fns = []
153
+ function go() {
154
+ for (let i = 0; i < 5; i++) {
155
+ if (i === 2) return
156
+ fns.push(() => i)
157
+ }
158
+ }
159
+ go()
160
+ result = fns.map((f) => f())
161
+ `)
162
+ ).toEqual([0, 1])
163
+ })
164
+
165
+ it('leaves break and continue belonging to a nested loop alone', () => {
166
+ expect(
167
+ run(`
168
+ var fns = []
169
+ for (let i = 0; i < 2; i++) {
170
+ for (var j = 0; j < 5; j++) {
171
+ if (j > 0) break
172
+ }
173
+ fns.push(() => i)
174
+ }
175
+ result = fns.map((f) => f())
176
+ `)
177
+ ).toEqual([0, 1])
178
+ })
179
+
180
+ it('handles nested capturing loops', () => {
181
+ expect(
182
+ run(`
183
+ var fns = []
184
+ for (let i = 0; i < 2; i++) {
185
+ for (let j = 0; j < 2; j++) {
186
+ fns.push(() => i + ':' + j)
187
+ }
188
+ }
189
+ result = fns.map((f) => f())
190
+ `)
191
+ ).toEqual(['0:0', '0:1', '1:0', '1:1'])
192
+ })
193
+
194
+ it('handles await in the loop body', async () => {
195
+ const out = transformHermesLoops(
196
+ `
197
+ var fns = []
198
+ async function go() {
199
+ for (const n of [1, 2]) {
200
+ await Promise.resolve()
201
+ fns.push(() => n)
202
+ }
203
+ return fns.map((f) => f())
204
+ }
205
+ result = go()
206
+ `,
207
+ 'test.js'
208
+ )
209
+ expect(out).not.toBeNull()
210
+ await expect(runAsHermes(out!.code)).resolves.toEqual([1, 2])
211
+ })
212
+
213
+ it('destructures a for-of binding per iteration', () => {
214
+ expect(
215
+ run(`
216
+ var fns = []
217
+ for (const { id } of [{ id: 'a' }, { id: 'b' }]) fns.push(() => id)
218
+ result = fns.map((f) => f())
219
+ `)
220
+ ).toEqual(['a', 'b'])
221
+ })
222
+
223
+ it('rewrites the esbuild interop helper that broke every multi-export module', () => {
224
+ // this exact shape is what esbuild inlines into every commonjs-interop
225
+ // module, and under Hermes it made each named export resolve to the last one
226
+ expect(
227
+ run(`
228
+ var __defProp = Object.defineProperty
229
+ var __getOwnPropNames = Object.getOwnPropertyNames
230
+ var from = { a: 1, b: 2 }
231
+ var to = {}
232
+ for (let key of __getOwnPropNames(from)) {
233
+ __defProp(to, key, { get: () => from[key], enumerable: true })
234
+ }
235
+ result = [to.a, to.b]
236
+ `)
237
+ ).toEqual([1, 2])
238
+ })
239
+
240
+ it('leaves loops that never capture their binding untouched', () => {
241
+ expect(
242
+ transformHermesLoops('for (let i = 0; i < 3; i++) sum += i', 'test.js')
243
+ ).toBeNull()
244
+ })
245
+
246
+ it('gives a binding declared in the loop body a per-iteration copy', () => {
247
+ // react-native's NativeAnimatedHelper builds one wrapper per method this
248
+ // way; sharing `methodName` made every wrapper resolve the last method.
249
+ expect(
250
+ run(`
251
+ var names = ['createAnimatedNode', 'addListener', 'removeListener']
252
+ var wrappers = {}
253
+ for (var ii = 0; ii < names.length; ii++) {
254
+ const methodName = names[ii]
255
+ wrappers[methodName] = () => methodName
256
+ }
257
+ result = names.map((n) => wrappers[n]())
258
+ `)
259
+ ).toEqual(['createAnimatedNode', 'addListener', 'removeListener'])
260
+ })
261
+
262
+ it('gives a binding declared in a nested block of the loop body a per-iteration copy', () => {
263
+ expect(
264
+ run(`
265
+ var fns = []
266
+ for (var i = 0; i < 3; i++) {
267
+ if (i >= 0) {
268
+ const doubled = i * 2
269
+ fns.push(() => doubled)
270
+ }
271
+ }
272
+ result = fns.map((f) => f())
273
+ `)
274
+ ).toEqual([0, 2, 4])
275
+ })
276
+
277
+ it('keeps `this` and `arguments` meaning what they meant outside the loop', () => {
278
+ // reanimated's PropsFilter reads `this._map` from a closure inside a
279
+ // `for (const key in props)` body, so a lifted body that rebound `this`
280
+ // crashed every animated component.
281
+ expect(
282
+ run(`
283
+ class PropsFilter {
284
+ constructor() { this._map = { a: 'A', b: 'B' } }
285
+ filter(props) {
286
+ var out = []
287
+ for (const key in props) {
288
+ const label = key
289
+ out.push(() => this._map[label] + arguments.length)
290
+ }
291
+ return out
292
+ }
293
+ }
294
+ result = new PropsFilter().filter({ a: 1, b: 2 }).map((f) => f())
295
+ `)
296
+ ).toEqual(['A1', 'B1'])
297
+ })
298
+
299
+ it('leaves a loop with no lexical binding untouched', () => {
300
+ expect(
301
+ transformHermesLoops('for (var i = 0; i < 3; i++) fns.push(() => i)', 'test.js')
302
+ ).toBeNull()
303
+ })
304
+
305
+ it('leaves the head declaration alone so it cannot collide with a sibling', () => {
306
+ // react-native-gesture-handler's Pressable declares `const gesture` in the
307
+ // same block as `for (const gesture of gestures)`. hoisting the head to
308
+ // `var` made that a redeclaration and failed the whole bundle to parse.
309
+ const out = transformHermesLoops(
310
+ `function build(gestures) {
311
+ var handlers = []
312
+ for (const gesture of gestures) { handlers.push(() => gesture) }
313
+ const gesture = gestures[0]
314
+ return gesture
315
+ }`,
316
+ 'Pressable.js'
317
+ )
318
+ // a redeclaration is a SyntaxError, so compiling is the whole assertion
319
+ expect(() => new vm.Script(out!.code)).not.toThrow()
320
+ })
321
+
322
+ it('rewrites a loop in a .js file that still contains jsx', () => {
323
+ // react-native and its dependencies ship jsx inside plain `.js`, and the
324
+ // rolldown native pipeline keeps jsx unlowered until after this transform.
325
+ const out = transformHermesLoops(
326
+ 'for (const c of items) rows.push(() => <Row key={c} />)',
327
+ 'Rows.js'
328
+ )
329
+ expect(out?.code).toContain('_hermesLoop0')
330
+ })
331
+
332
+ it('does not rewrite a loop that reassigns its own binding', () => {
333
+ // the update would be stranded inside the lifted function
334
+ expect(
335
+ transformHermesLoops(
336
+ 'for (let i = 0; i < 3; i++) { i += 1; fns.push(() => i) }',
337
+ 'test.js'
338
+ )
339
+ ).toBeNull()
340
+ })
341
+ })