vxrn 2.8.1 → 2.9.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 (54) hide show
  1. package/dist/exports/clean.mjs +128 -19
  2. package/dist/exports/clean.mjs.map +1 -1
  3. package/dist/exports/clean.native.js +147 -21
  4. package/dist/exports/clean.native.js.map +1 -1
  5. package/dist/exports/prebuild.mjs +2 -1
  6. package/dist/exports/prebuild.mjs.map +1 -1
  7. package/dist/exports/prebuild.native.js +2 -1
  8. package/dist/exports/prebuild.native.js.map +1 -1
  9. package/dist/exports/prebuildWithoutExpo.mjs +196 -7
  10. package/dist/exports/prebuildWithoutExpo.mjs.map +1 -1
  11. package/dist/exports/prebuildWithoutExpo.native.js +280 -42
  12. package/dist/exports/prebuildWithoutExpo.native.js.map +1 -1
  13. package/dist/plugins/reactNativeDevServer.mjs +43 -8
  14. package/dist/plugins/reactNativeDevServer.mjs.map +1 -1
  15. package/dist/plugins/reactNativeDevServer.native.js +45 -8
  16. package/dist/plugins/reactNativeDevServer.native.js.map +1 -1
  17. package/dist/user-interface/index.mjs +31 -24
  18. package/dist/user-interface/index.mjs.map +1 -1
  19. package/dist/user-interface/index.native.js +32 -25
  20. package/dist/user-interface/index.native.js.map +1 -1
  21. package/dist/utils/createNativeDevEngine.mjs +111 -14
  22. package/dist/utils/createNativeDevEngine.mjs.map +1 -1
  23. package/dist/utils/createNativeDevEngine.native.js +143 -14
  24. package/dist/utils/createNativeDevEngine.native.js.map +1 -1
  25. package/dist/utils/scanDepsToOptimize.mjs +10 -6
  26. package/dist/utils/scanDepsToOptimize.mjs.map +1 -1
  27. package/dist/utils/scanDepsToOptimize.native.js +12 -6
  28. package/dist/utils/scanDepsToOptimize.native.js.map +1 -1
  29. package/expo-plugin.cjs +71 -8
  30. package/package.json +14 -14
  31. package/src/exports/clean.test.ts +502 -5
  32. package/src/exports/clean.ts +176 -19
  33. package/src/exports/expoPlugin.test.mjs +44 -1
  34. package/src/exports/prebuild.ts +6 -0
  35. package/src/exports/prebuildWithoutExpo.test.ts +134 -2
  36. package/src/exports/prebuildWithoutExpo.ts +235 -10
  37. package/src/plugins/reactNativeDevServer.test.ts +75 -1
  38. package/src/plugins/reactNativeDevServer.ts +45 -8
  39. package/src/user-interface/index.ts +50 -42
  40. package/src/utils/createNativeDevEngine.test.ts +480 -1
  41. package/src/utils/createNativeDevEngine.ts +220 -17
  42. package/src/utils/imageSizePatch.test.ts +3 -3
  43. package/src/utils/scanDepsToOptimize.test.ts +35 -2
  44. package/src/utils/scanDepsToOptimize.ts +25 -8
  45. package/types/exports/clean.d.ts.map +1 -1
  46. package/types/exports/prebuild.d.ts +2 -1
  47. package/types/exports/prebuild.d.ts.map +1 -1
  48. package/types/exports/prebuildWithoutExpo.d.ts.map +1 -1
  49. package/types/plugins/reactNativeDevServer.d.ts.map +1 -1
  50. package/types/user-interface/index.d.ts.map +1 -1
  51. package/types/utils/createNativeDevEngine.d.ts +1 -1
  52. package/types/utils/createNativeDevEngine.d.ts.map +1 -1
  53. package/types/utils/scanDepsToOptimize.d.ts +25 -0
  54. package/types/utils/scanDepsToOptimize.d.ts.map +1 -1
@@ -1,5 +1,13 @@
1
1
  import { existsSync, realpathSync } from 'node:fs'
2
- import { mkdir, mkdtemp, readFile, rename, rm, symlink, writeFile } from 'node:fs/promises'
2
+ import {
3
+ mkdir,
4
+ mkdtemp,
5
+ readFile,
6
+ rename,
7
+ rm,
8
+ symlink,
9
+ writeFile,
10
+ } from 'node:fs/promises'
3
11
  import { tmpdir } from 'node:os'
4
12
  import { join } from 'node:path'
5
13
  import { createContext, runInContext, runInNewContext } from 'node:vm'
@@ -1452,6 +1460,78 @@ describe('native production import.meta lowering', () => {
1452
1460
  })
1453
1461
  })
1454
1462
 
1463
+ describe('native function and class names', () => {
1464
+ it('keeps colliding names in an unminified bundle, the way metro does', async () => {
1465
+ const testRoot = await mkdtemp(join(tmpdir(), 'vxrn-native-names-'))
1466
+ for (const name of ['a', 'b']) {
1467
+ await writeFile(
1468
+ join(testRoot, `${name}.js`),
1469
+ `export class UnsupportedFeature extends Error {}\nexport function read() {}`
1470
+ )
1471
+ }
1472
+ await writeFile(
1473
+ join(testRoot, 'entry.js'),
1474
+ `import * as a from './a.js'\nimport * as b from './b.js'\nglobalThis.names = [a.UnsupportedFeature.name, b.UnsupportedFeature.name, a.read.name, b.read.name]`
1475
+ )
1476
+
1477
+ try {
1478
+ for (const dev of [true, false]) {
1479
+ const result = await buildNativeBundle({
1480
+ root: testRoot,
1481
+ platform: 'ios',
1482
+ dev,
1483
+ minify: false,
1484
+ entryFile: 'entry.js',
1485
+ })
1486
+ const context: any = { process: { env: {} } }
1487
+ context.globalThis = context
1488
+ runInNewContext(result.code, context)
1489
+ expect(context.names).toEqual(['UnsupportedFeature', 'UnsupportedFeature', 'read', 'read'])
1490
+ }
1491
+ } finally {
1492
+ await rm(testRoot, { recursive: true, force: true })
1493
+ }
1494
+ })
1495
+
1496
+ it('keeps lowered class names in a module that also lowers async', async () => {
1497
+ const root = await mkdtemp(join(tmpdir(), 'vxrn-native-dev-names-'))
1498
+ const packageRoot = join(root, 'node_modules', 'errors-pkg')
1499
+ await mkdir(packageRoot, { recursive: true })
1500
+ await writeFile(join(packageRoot, 'package.json'), JSON.stringify({ main: './index.js' }))
1501
+ // async lowering reprints the lowered classes, renaming each inner
1502
+ // constructor that shadows its top-level export.
1503
+ await writeFile(
1504
+ join(packageRoot, 'index.js'),
1505
+ `class ErrorBase extends Error {}\nexport class UnsupportedFeature extends ErrorBase {}\nexport async function check() {}`
1506
+ )
1507
+ await writeFile(
1508
+ join(root, 'entry.js'),
1509
+ `const { UnsupportedFeature } = require('errors-pkg')\nglobalThis.names = [UnsupportedFeature.name, new UnsupportedFeature().constructor.name]`
1510
+ )
1511
+ const native = await createNativeDevEngine({
1512
+ root,
1513
+ port: 52279,
1514
+ platform: 'ios',
1515
+ plugins: [
1516
+ {
1517
+ name: 'native-names-entry',
1518
+ transform(_code, id) {
1519
+ if (id.endsWith('/__virtual-native-entry.tsx')) return `import './entry.js'`
1520
+ },
1521
+ },
1522
+ ],
1523
+ })
1524
+ try {
1525
+ const context: any = { console, setTimeout, clearTimeout }
1526
+ runInNewContext((await native.getBundle()).code, context)
1527
+ expect(context.names).toEqual(['UnsupportedFeature', 'UnsupportedFeature'])
1528
+ } finally {
1529
+ await native.close()
1530
+ await rm(root, { recursive: true, force: true })
1531
+ }
1532
+ })
1533
+ })
1534
+
1455
1535
  describe('native production react-native deep imports', () => {
1456
1536
  it('resolves react native subpaths that are missing from its exports map', async () => {
1457
1537
  const testRoot = await mkdtemp(join(tmpdir(), 'vxrn-native-rn-deep-import-'))
@@ -3032,6 +3112,74 @@ globalThis.externalDefaults = [Component, marker, Scoped, Plain, require('extern
3032
3112
  })
3033
3113
  })
3034
3114
 
3115
+ describe('native interop string literals', () => {
3116
+ it('rewrites real __toESM calls while leaving string data alone', () => {
3117
+ const literal = '__toESM(externalModule, 1)'
3118
+ const code = [
3119
+ `const stringData = ${JSON.stringify(literal)}`,
3120
+ 'const templateData = `' + literal + '`',
3121
+ `// ${literal}`,
3122
+ `/* ${literal} */`,
3123
+ 'const viaRequire = __toESM(require_helper(), 1)',
3124
+ 'const viaBinding = __toESM(externalModule, 1)',
3125
+ 'const viaLookup = __toESM(__rolldown_runtime__.loadExports("helper"), 1)',
3126
+ 'const viaRuntime = __rolldown_runtime__.__toESM(__rolldown_runtime__.loadExports("helper"), 1)',
3127
+ 'const renamed = __toESM$2(require_helper(), 1)',
3128
+ 'const spaced = __toESM( require_helper() , 1 )',
3129
+ 'const interpolated = `${__toESM(externalModule, 1)}`',
3130
+ ].join('\n')
3131
+ const normalized = normalizeNativeCommonJSInterop(code)
3132
+ expect(normalized).toContain(`const stringData = ${JSON.stringify(literal)}`)
3133
+ expect(normalized).toContain('const templateData = `' + literal + '`')
3134
+ expect(normalized).toContain(`// ${literal}`)
3135
+ expect(normalized).toContain(`/* ${literal} */`)
3136
+ expect(normalized).toContain('const viaRequire = __toESM(require_helper())')
3137
+ expect(normalized).toContain('const viaBinding = __toESM(externalModule)')
3138
+ expect(normalized).toContain(
3139
+ 'const viaLookup = __toESM(__rolldown_runtime__.loadExports("helper"))'
3140
+ )
3141
+ expect(normalized).toContain(
3142
+ 'const viaRuntime = __rolldown_runtime__.__toESM(__rolldown_runtime__.loadExports("helper"))'
3143
+ )
3144
+ expect(normalized).toContain('const renamed = __toESM$2(require_helper())')
3145
+ expect(normalized).toContain('const spaced = __toESM( require_helper() )')
3146
+ expect(normalized).toContain('const interpolated = `${__toESM(externalModule)}`')
3147
+ })
3148
+
3149
+ it.each([false, true])(
3150
+ 'keeps __toESM string data through buildNativeBundle (minify=%s)',
3151
+ async (minify) => {
3152
+ const literal = '__toESM(externalModule, 1)'
3153
+ const root = await mkdtemp(join(tmpdir(), 'vxrn-interop-literal-'))
3154
+ try {
3155
+ await writeFile(join(root, 'package.json'), JSON.stringify({ type: 'module' }))
3156
+ await writeFile(
3157
+ join(root, 'entry.js'),
3158
+ `globalThis.__interopLiteralProbe = ${JSON.stringify(literal)}\n`
3159
+ )
3160
+ const { code } = await buildNativeBundle({
3161
+ root,
3162
+ platform: 'ios',
3163
+ entryFile: 'entry.js',
3164
+ dev: true,
3165
+ minify,
3166
+ })
3167
+ expect(code).toContain(literal)
3168
+ const context = {
3169
+ clearTimeout,
3170
+ console,
3171
+ process: { env: {} },
3172
+ setTimeout,
3173
+ }
3174
+ runInNewContext(code, context)
3175
+ expect(Reflect.get(context, '__interopLiteralProbe')).toBe(literal)
3176
+ } finally {
3177
+ await rm(root, { recursive: true, force: true })
3178
+ }
3179
+ }
3180
+ )
3181
+ })
3182
+
3035
3183
  describe('native Flow sources', () => {
3036
3184
  it.each([true, false])(
3037
3185
  'preserves codegen, worklets and maps with dev=%s',
@@ -3335,6 +3483,56 @@ describe('postProcessNativeBundle', () => {
3335
3483
  })
3336
3484
  })
3337
3485
 
3486
+ describe('native dev bundle post-processing failures', () => {
3487
+ it('rejects the bundle request with the failure instead of timing out', async () => {
3488
+ const root = await mkdtemp(join(tmpdir(), 'vxrn-native-postprocess-error-'))
3489
+ await writeFile(join(root, 'package.json'), JSON.stringify({ type: 'module' }))
3490
+ await writeFile(join(root, 'entry.js'), `globalThis.__postprocessProbe = 1\n`)
3491
+ const native = await createNativeDevEngine({
3492
+ root,
3493
+ port: 0,
3494
+ platform: 'ios',
3495
+ plugins: [
3496
+ {
3497
+ name: 'postprocess-error-entry',
3498
+ transform(code, id) {
3499
+ if (id.endsWith('/__virtual-native-entry.tsx')) return `import './entry.js'`
3500
+ },
3501
+ },
3502
+ {
3503
+ name: 'postprocess-error-poison',
3504
+ renderChunk() {
3505
+ // valid javascript, so rolldown delivers it: the single-line
3506
+ // class fields inside the runtime region gain lines when
3507
+ // downleveled, which throws inside handleOutput.
3508
+ return {
3509
+ code: [
3510
+ '//#region \\0rolldown/runtime.js',
3511
+ 'class Poison { x = 1; y = 2; z = 3; }',
3512
+ '//#endregion',
3513
+ 'globalThis.__postprocessProbe = new Poison();',
3514
+ '',
3515
+ ].join('\n'),
3516
+ }
3517
+ },
3518
+ },
3519
+ ],
3520
+ })
3521
+ try {
3522
+ await expect(native.getBundle()).rejects.toThrow(
3523
+ 'Hermes runtime transform added lines'
3524
+ )
3525
+ // the failure is recorded for later callers, not just the waiter
3526
+ await expect(native.getBundle()).rejects.toThrow(
3527
+ 'Hermes runtime transform added lines'
3528
+ )
3529
+ } finally {
3530
+ await native.close()
3531
+ await rm(root, { recursive: true, force: true })
3532
+ }
3533
+ })
3534
+ })
3535
+
3338
3536
  describe('native package context identity', () => {
3339
3537
  it.each(['@tamagui/web', '@tamagui/adapt'])(
3340
3538
  'shares %s contexts with dependencies containing a second package copy',
@@ -3470,3 +3668,284 @@ describe('native zero-client edit recoverability', () => {
3470
3668
  }
3471
3669
  )
3472
3670
  })
3671
+
3672
+ describe('native mixed ESM and CommonJS exports', () => {
3673
+ it.each([false, true])(
3674
+ 'preserves replacement exports for require and import (dev=%s)',
3675
+ async (dev) => {
3676
+ const root = await mkdtemp(join(tmpdir(), 'vxrn-native-mixed-'))
3677
+ const dep = join(root, 'node_modules/mixed-native')
3678
+ await mkdir(dep, { recursive: true })
3679
+ await writeFile(
3680
+ join(dep, 'package.json'),
3681
+ JSON.stringify({ name: 'mixed-native', 'react-native': 'index.ts' })
3682
+ )
3683
+ await writeFile(
3684
+ join(dep, 'api.ts'),
3685
+ "export const value = 'native'; export const discarded = 'esm-only'"
3686
+ )
3687
+ await writeFile(
3688
+ join(dep, 'index.ts'),
3689
+ `
3690
+ import { value } from './api'
3691
+ export * from './api'
3692
+ export const install = () => value
3693
+ const api: { value: string; default?: unknown; install?: unknown } = { value }
3694
+ export default api
3695
+ module.exports = api
3696
+ module.exports.default = api
3697
+ module.exports.install = install
3698
+ `
3699
+ )
3700
+ await writeFile(
3701
+ join(root, 'entry.js'),
3702
+ `
3703
+ import api, { install, value, discarded } from 'mixed-native'
3704
+ const required = require('mixed-native')
3705
+ globalThis.mixedProbe = {
3706
+ same: api === required,
3707
+ selfDefault: required.default === required,
3708
+ installed: install(),
3709
+ value,
3710
+ discarded,
3711
+ keys: Object.keys(required).sort(),
3712
+ }
3713
+ `
3714
+ )
3715
+ try {
3716
+ const bundle = await buildNativeBundle({
3717
+ root,
3718
+ platform: 'ios',
3719
+ entryFile: 'entry.js',
3720
+ dev,
3721
+ })
3722
+ const context: any = { console }
3723
+ runInNewContext(bundle.code, context)
3724
+
3725
+ // metro's react native preset lowers all imports and exports before cjs runs.
3726
+ const { transformAsync } = await import('@babel/core')
3727
+ const metroContext: any = { console }
3728
+ const modules = new Map<string, any>()
3729
+ const sources = new Map<string, string>()
3730
+ for (const [name, file] of [
3731
+ ['mixed-native', join(dep, 'index.ts')],
3732
+ ['./api', join(dep, 'api.ts')],
3733
+ ['entry', join(root, 'entry.js')],
3734
+ ]) {
3735
+ const transformed = await transformAsync(await readFile(file!, 'utf8'), {
3736
+ filename: file,
3737
+ babelrc: false,
3738
+ configFile: false,
3739
+ presets: ['module:@react-native/babel-preset'],
3740
+ })
3741
+ sources.set(name!, transformed!.code!)
3742
+ }
3743
+ const metroRequire = (name: string): any => {
3744
+ if (modules.has(name)) return modules.get(name).exports
3745
+ const module = { exports: {} }
3746
+ modules.set(name, module)
3747
+ runInNewContext(
3748
+ '(function(module, exports, require) {' + sources.get(name) + '\n})',
3749
+ metroContext
3750
+ )(module, module.exports, metroRequire)
3751
+ return module.exports
3752
+ }
3753
+ metroRequire('entry')
3754
+ expect(context.mixedProbe).toEqual(metroContext.mixedProbe)
3755
+ expect(context.mixedProbe).toEqual({
3756
+ same: true,
3757
+ selfDefault: true,
3758
+ installed: 'native',
3759
+ value: 'native',
3760
+ discarded: undefined,
3761
+ keys: ['default', 'install', 'value'],
3762
+ })
3763
+ } finally {
3764
+ await rm(root, { recursive: true, force: true })
3765
+ }
3766
+ }
3767
+ )
3768
+ })
3769
+
3770
+ it.each([
3771
+ 'export const value = "module.exports = ignored"',
3772
+ 'export const module = { exports: 1 }; module.exports = 2',
3773
+ 'export function local(module) { module.exports = 1 }',
3774
+ 'export function local(exports) { exports.value = 1 }',
3775
+ 'module.exports = 1',
3776
+ ])('leaves non-mixed sources alone: %s', async (source) => {
3777
+ const { transformMixedNativeModule } = await import('@vxrn/compiler')
3778
+ expect(await transformMixedNativeModule(source, 'probe.js')).toBeUndefined()
3779
+ })
3780
+
3781
+ it('preserves computed CommonJS assignments and their source map', async () => {
3782
+ const { transformMixedNativeModule } = await import('@vxrn/compiler')
3783
+ const source = "export const dropped = true;\nmodule['exports'] = { value: 42 };\n"
3784
+ const result = await transformMixedNativeModule(source, 'probe.js', true)
3785
+ const module = { exports: {} }
3786
+ runInNewContext(result!.code, { module, exports: module.exports })
3787
+ expect(module.exports).toEqual({ value: 42 })
3788
+ const lines = result!.code.split('\n')
3789
+ const line = lines.findIndex((line) => line.includes('42'))
3790
+ expect(
3791
+ originalPositionFor(new TraceMap(result!.map), {
3792
+ line: line + 1,
3793
+ column: lines[line]!.indexOf('42'),
3794
+ })
3795
+ ).toMatchObject({ source: 'probe.js', line: 2 })
3796
+ })
3797
+
3798
+ it('keeps Babel export properties writable in mixed modules', async () => {
3799
+ const { transformMixedNativeModule } = await import('@vxrn/compiler')
3800
+ const result = await transformMixedNativeModule(
3801
+ 'export let value = 1; exports.value = 2; export const lexical = () => value',
3802
+ 'probe.js'
3803
+ )
3804
+ const module = { exports: {} as any }
3805
+ runInNewContext(result!.code, { module, exports: module.exports })
3806
+ expect(module.exports.value).toBe(2)
3807
+ expect(module.exports.lexical()).toBe(1)
3808
+ })
3809
+
3810
+ it.each([
3811
+ 'export const value = this; module.exports = { valueIsObject: typeof value === "object", strict: (function () { return this === undefined })() };',
3812
+ '"use strict"; export const value = this; module.exports = { valueIsObject: typeof value === "object", strict: (function () { return this === undefined })() };',
3813
+ 'export const value = 42; Object.assign(exports, { attached: value });',
3814
+ 'export const value = 42; const out = exports; out.attached = value;',
3815
+ 'export const value = 42; const out = module; out.exports.attached = value;',
3816
+ 'export let value = 1; exports.value = 10; value++;',
3817
+ 'export let value = 1; export { value as alias }; exports.value = 10; value++;',
3818
+ 'export let value = 1, next = exports.value; exports.attached = next;',
3819
+ 'export let value = 1; exports.value = 10; value ||= 2;',
3820
+ 'export default function () {}; exports.default = 42;',
3821
+ 'export default function named() {}; exports.default = 42;',
3822
+ 'export default class Named {}; exports.default = 42;',
3823
+ 'export default 1; exports.default = 42;',
3824
+ 'export let value = 1; export const nested = () => { let value = 10; value++; return value }; exports.attached = exports.nested();',
3825
+ 'export let value = 1; export const previous = value++; exports.attached = previous;',
3826
+ 'export let value = 1; ({ value } = { value: 42 }); exports.attached = value;',
3827
+ 'export let value = 1; export const increment = () => value++; exports.increment();',
3828
+ 'let value = 1; export { value as renamed }; value += 2; exports.attached = value;',
3829
+ ])('matches Metro wrapper semantics: %s', async (source) => {
3830
+ const { transformMixedNativeModule } = await import('@vxrn/compiler')
3831
+ const { transformAsync } = await import('@babel/core')
3832
+ const actual = await transformMixedNativeModule(source, 'probe.js')
3833
+ const expected = await transformAsync(source, {
3834
+ filename: 'probe.js',
3835
+ babelrc: false,
3836
+ configFile: false,
3837
+ presets: ['module:@react-native/babel-preset'],
3838
+ })
3839
+ const evaluate = (code: string) => {
3840
+ const module = { exports: {} as any }
3841
+ runInNewContext(
3842
+ '(function(module, exports) {' + code + '\n})(module, exports)',
3843
+ {
3844
+ module,
3845
+ exports: module.exports,
3846
+ }
3847
+ )
3848
+ return JSON.parse(JSON.stringify(module.exports))
3849
+ }
3850
+ expect(actual).toBeDefined()
3851
+ expect(evaluate(actual!.code)).toEqual(evaluate(expected!.code!))
3852
+ })
3853
+
3854
+ it.each([
3855
+ 'export function local(exports) { return Object.assign(exports, { value: 1 }) }',
3856
+ 'export function local(module) { return module }',
3857
+ 'export const obj = { exports: 1, module: 2 }',
3858
+ 'export function local() { module.exports = 1; var module }',
3859
+ 'export function local({ exports }) { return exports }',
3860
+ 'export function local() { try {} catch (exports) { exports.value = 1 } }',
3861
+ 'export function local() { for (const exports of []) { exports.value = 1 } }',
3862
+ ])('keeps lexical CommonJS shadows unchanged: %s', async (source) => {
3863
+ const { transformMixedNativeModule } = await import('@vxrn/compiler')
3864
+ expect(await transformMixedNativeModule(source, 'probe.js')).toBeUndefined()
3865
+ })
3866
+ it.each([
3867
+ [
3868
+ 'probe.js',
3869
+ 'export let value=1; export function increment(exports){value++;} increment({}); module.exports={published:exports.value,lexical:value};',
3870
+ ],
3871
+ [
3872
+ 'probe.js',
3873
+ 'export let value=1; for(value of [2,3]) {} module.exports={published:exports.value,lexical:value};',
3874
+ ],
3875
+ [
3876
+ 'probe.js',
3877
+ 'export let value=""; for(value in {a:1,b:2}) {} module.exports={published:exports.value,lexical:value};',
3878
+ ],
3879
+ [
3880
+ 'probe.js',
3881
+ 'export let value=1; for(value of [2,3]) if(value===2) continue; module.exports={published:exports.value,lexical:value};',
3882
+ ],
3883
+ [
3884
+ 'probe.js',
3885
+ 'export let value=1; for(const value of [2,3]) {} module.exports={published:exports.value,lexical:value};',
3886
+ ],
3887
+ [
3888
+ 'probe.js',
3889
+ 'export let value=1; export function increment(module, exports){value++;} increment({},{}); module.exports={published:exports.value,lexical:value};',
3890
+ ],
3891
+ [
3892
+ 'probe.js',
3893
+ 'export let value=1; exports={}; value++; module.exports={published:exports.value,lexical:value};',
3894
+ ],
3895
+ [
3896
+ 'probe.ts',
3897
+ 'export enum Color { Red }; exports.Color=42; module.exports={value:exports.Color};',
3898
+ ],
3899
+ [
3900
+ 'probe.ts',
3901
+ 'export namespace Color { export const Red = 1 }; exports.Color=42; module.exports={value:exports.Color};',
3902
+ ],
3903
+ [
3904
+ 'probe.ts',
3905
+ 'export const value: unknown = this; module.exports={valueIsObject:typeof value === "object",strict:(function(){return this === undefined})()};',
3906
+ ],
3907
+ [
3908
+ 'probe.tsx',
3909
+ 'export enum Color { Red }; exports.Color=42; module.exports={value:exports.Color};',
3910
+ ],
3911
+ ])(
3912
+ 'matches Metro writable binding semantics in %s: %s',
3913
+ async (filename, source) => {
3914
+ const { transformMixedNativeModule } = await import('@vxrn/compiler')
3915
+ const { transformAsync } = await import('@babel/core')
3916
+ const actual = await transformMixedNativeModule(source, filename)
3917
+ const expected = await transformAsync(source, {
3918
+ filename,
3919
+ babelrc: false,
3920
+ configFile: false,
3921
+ presets: ['module:@react-native/babel-preset'],
3922
+ })
3923
+ const evaluate = (code: string) => {
3924
+ const module = { exports: {} }
3925
+ runInNewContext(
3926
+ '(function(module, exports) {' + code + '\n})(module, exports)',
3927
+ {
3928
+ module,
3929
+ exports: module.exports,
3930
+ }
3931
+ )
3932
+ return JSON.parse(JSON.stringify(module.exports))
3933
+ }
3934
+ expect(actual).toBeDefined()
3935
+ expect(evaluate(actual!.code)).toEqual(evaluate(expected!.code!))
3936
+ }
3937
+ )
3938
+
3939
+ it('composes TypeScript runtime declaration maps to the original source', async () => {
3940
+ const { transformMixedNativeModule } = await import('@vxrn/compiler')
3941
+ const source = 'export enum Color { Red };\nexports.Color = 42;\n'
3942
+ const result = await transformMixedNativeModule(source, 'probe.ts', true)
3943
+ const lines = result!.code.split('\n')
3944
+ const line = lines.findIndex((line) => line.includes('42'))
3945
+ expect(
3946
+ originalPositionFor(new TraceMap(result!.map), {
3947
+ line: line + 1,
3948
+ column: lines[line]!.indexOf('42'),
3949
+ })
3950
+ ).toMatchObject({ source: 'probe.ts', line: 2 })
3951
+ })