vxrn 2.8.0 → 2.9.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.
- package/dist/exports/clean.mjs +128 -19
- package/dist/exports/clean.mjs.map +1 -1
- package/dist/exports/clean.native.js +147 -21
- package/dist/exports/clean.native.js.map +1 -1
- package/dist/exports/prebuild.mjs +2 -1
- package/dist/exports/prebuild.mjs.map +1 -1
- package/dist/exports/prebuild.native.js +2 -1
- package/dist/exports/prebuild.native.js.map +1 -1
- package/dist/exports/prebuildWithoutExpo.mjs +196 -7
- package/dist/exports/prebuildWithoutExpo.mjs.map +1 -1
- package/dist/exports/prebuildWithoutExpo.native.js +280 -42
- package/dist/exports/prebuildWithoutExpo.native.js.map +1 -1
- package/dist/plugins/reactNativeDevServer.mjs +1 -1
- package/dist/plugins/reactNativeDevServer.mjs.map +1 -1
- package/dist/plugins/reactNativeDevServer.native.js +1 -1
- package/dist/plugins/reactNativeDevServer.native.js.map +1 -1
- package/dist/user-interface/index.mjs +31 -24
- package/dist/user-interface/index.mjs.map +1 -1
- package/dist/user-interface/index.native.js +32 -25
- package/dist/user-interface/index.native.js.map +1 -1
- package/dist/utils/createNativeDevEngine.mjs +111 -14
- package/dist/utils/createNativeDevEngine.mjs.map +1 -1
- package/dist/utils/createNativeDevEngine.native.js +143 -14
- package/dist/utils/createNativeDevEngine.native.js.map +1 -1
- package/dist/utils/scanDepsToOptimize.mjs +10 -6
- package/dist/utils/scanDepsToOptimize.mjs.map +1 -1
- package/dist/utils/scanDepsToOptimize.native.js +12 -6
- package/dist/utils/scanDepsToOptimize.native.js.map +1 -1
- package/expo-plugin.cjs +71 -8
- package/package.json +13 -13
- package/src/exports/clean.test.ts +502 -5
- package/src/exports/clean.ts +176 -19
- package/src/exports/expoPlugin.test.mjs +44 -1
- package/src/exports/prebuild.ts +6 -0
- package/src/exports/prebuildWithoutExpo.test.ts +134 -2
- package/src/exports/prebuildWithoutExpo.ts +235 -10
- package/src/plugins/reactNativeDevServer.ts +1 -1
- package/src/user-interface/index.ts +50 -42
- package/src/utils/createNativeDevEngine.test.ts +486 -7
- package/src/utils/createNativeDevEngine.ts +220 -17
- package/src/utils/imageSizePatch.test.ts +3 -3
- package/src/utils/scanDepsToOptimize.test.ts +35 -2
- package/src/utils/scanDepsToOptimize.ts +25 -8
- package/types/exports/clean.d.ts.map +1 -1
- package/types/exports/prebuild.d.ts +2 -1
- package/types/exports/prebuild.d.ts.map +1 -1
- package/types/exports/prebuildWithoutExpo.d.ts.map +1 -1
- package/types/user-interface/index.d.ts.map +1 -1
- package/types/utils/createNativeDevEngine.d.ts +1 -1
- package/types/utils/createNativeDevEngine.d.ts.map +1 -1
- package/types/utils/scanDepsToOptimize.d.ts +25 -0
- package/types/utils/scanDepsToOptimize.d.ts.map +1 -1
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { existsSync, realpathSync } from 'node:fs'
|
|
2
|
-
import {
|
|
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'
|
|
@@ -111,7 +119,7 @@ ${accept}`
|
|
|
111
119
|
const pending = nextUpdate()
|
|
112
120
|
if (change === 'edit') {
|
|
113
121
|
expect(context.routeEdit).toBe('index')
|
|
114
|
-
await
|
|
122
|
+
await saveFile(
|
|
115
123
|
join(root, 'app/index.tsx'),
|
|
116
124
|
"export default 'edited'; globalThis.routeEdit = 'edited'; if (import.meta.hot) import.meta.hot.accept()"
|
|
117
125
|
)
|
|
@@ -119,7 +127,7 @@ ${accept}`
|
|
|
119
127
|
expect(context.routeEdit).toBe('edited')
|
|
120
128
|
} else if (change === 'create') {
|
|
121
129
|
expect(context.routeMap['/app/[id].tsx']).toBeUndefined()
|
|
122
|
-
await
|
|
130
|
+
await saveFile(join(root, 'app/[id].tsx'), "export default 'dynamic'")
|
|
123
131
|
await apply(pending)
|
|
124
132
|
expect((await context.routeMap['/app/[id].tsx']()).default).toBe('dynamic')
|
|
125
133
|
} else {
|
|
@@ -825,7 +833,7 @@ if (import.meta.hot) import.meta.hot.accept(() => {})
|
|
|
825
833
|
|
|
826
834
|
const runtime = rn.__rolldown_runtime__
|
|
827
835
|
await native.engine.registerClient(runtime.clientId)
|
|
828
|
-
await
|
|
836
|
+
await saveFile(dependency, 'export const amount = 3')
|
|
829
837
|
const update = await updated
|
|
830
838
|
expect(update.type).toBe('hmr:update')
|
|
831
839
|
expect(runtime.applyHmrUpdate(update.code, update.changedIds, update.seq)).toBe(
|
|
@@ -1078,7 +1086,7 @@ if (import.meta.hot) import.meta.hot.accept(() => {})
|
|
|
1078
1086
|
expect(context.renderComponent()).toBe(42)
|
|
1079
1087
|
const runtime = context.__rolldown_runtime__
|
|
1080
1088
|
await native.engine.registerClient(runtime.clientId)
|
|
1081
|
-
await
|
|
1089
|
+
await saveFile(entry, source(2))
|
|
1082
1090
|
const update = await updateReceived
|
|
1083
1091
|
expect(update.type).toBe('hmr:update')
|
|
1084
1092
|
expect(update.clientId).toBe(runtime.clientId)
|
|
@@ -1275,7 +1283,7 @@ if (import.meta.hot) {
|
|
|
1275
1283
|
registeredClientId = runtime.clientId
|
|
1276
1284
|
await engine.registerClient(runtime.clientId)
|
|
1277
1285
|
|
|
1278
|
-
await
|
|
1286
|
+
await saveFile(entry, source('v2'))
|
|
1279
1287
|
const result = await Promise.race([
|
|
1280
1288
|
hmrUpdate,
|
|
1281
1289
|
new Promise((_, reject) =>
|
|
@@ -1388,7 +1396,7 @@ if (import.meta.hot) {
|
|
|
1388
1396
|
registeredClientId = runtime.clientId
|
|
1389
1397
|
await engine.registerClient(runtime.clientId)
|
|
1390
1398
|
|
|
1391
|
-
await
|
|
1399
|
+
await saveFile(leaf, `export const value = 'v2'`)
|
|
1392
1400
|
const result = await Promise.race([
|
|
1393
1401
|
hmrUpdate,
|
|
1394
1402
|
new Promise((_, reject) =>
|
|
@@ -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
|
+
})
|