vxrn 2.0.0-beta.99.1 → 2.6.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/backgroundComputation.mjs +145 -0
- package/dist/backgroundComputation.mjs.map +1 -0
- package/dist/backgroundComputation.native.js +163 -0
- package/dist/backgroundComputation.native.js.map +1 -0
- package/dist/backgroundComputationNative.mjs +16 -0
- package/dist/backgroundComputationNative.mjs.map +1 -0
- package/dist/backgroundComputationNative.native.js +18 -0
- package/dist/backgroundComputationNative.native.js.map +1 -0
- package/dist/config/getBaseViteConfigOnly.mjs +6 -10
- package/dist/config/getBaseViteConfigOnly.mjs.map +1 -1
- package/dist/config/getBaseViteConfigOnly.native.js +6 -10
- package/dist/config/getBaseViteConfigOnly.native.js.map +1 -1
- package/dist/config/getOptimizeDeps.mjs +2 -1
- package/dist/config/getOptimizeDeps.mjs.map +1 -1
- package/dist/config/getOptimizeDeps.native.js +2 -1
- package/dist/config/getOptimizeDeps.native.js.map +1 -1
- package/dist/config/getViteServerConfig.mjs +13 -0
- package/dist/config/getViteServerConfig.mjs.map +1 -1
- package/dist/config/getViteServerConfig.native.js +15 -1
- package/dist/config/getViteServerConfig.native.js.map +1 -1
- package/dist/constants/defaults.mjs +4 -0
- package/dist/constants/defaults.mjs.map +1 -1
- package/dist/constants/defaults.native.js +4 -0
- package/dist/constants/defaults.native.js.map +1 -1
- package/dist/exports/build.native.js +1 -1
- package/dist/exports/build.native.js.map +1 -1
- package/dist/exports/clean.native.js +1 -1
- package/dist/exports/clean.native.js.map +1 -1
- package/dist/exports/createServer.native.js +2 -2
- package/dist/exports/createServer.native.js.map +1 -1
- package/dist/exports/dev.mjs +3 -0
- package/dist/exports/dev.mjs.map +1 -1
- package/dist/exports/dev.native.js +6 -3
- package/dist/exports/dev.native.js.map +1 -1
- package/dist/exports/loadEnv.native.js +1 -1
- package/dist/exports/loadEnv.native.js.map +1 -1
- package/dist/exports/patch.native.js +1 -1
- package/dist/exports/patch.native.js.map +1 -1
- package/dist/exports/prebuild.mjs +0 -22
- package/dist/exports/prebuild.mjs.map +1 -1
- package/dist/exports/prebuild.native.js +1 -23
- package/dist/exports/prebuild.native.js.map +1 -1
- package/dist/exports/prebuildWithoutExpo.mjs +1116 -178
- package/dist/exports/prebuildWithoutExpo.mjs.map +1 -1
- package/dist/exports/prebuildWithoutExpo.native.js +1592 -243
- package/dist/exports/prebuildWithoutExpo.native.js.map +1 -1
- package/dist/exports/runAndroid.native.js +1 -1
- package/dist/exports/runAndroid.native.js.map +1 -1
- package/dist/exports/runIos.native.js +1 -1
- package/dist/exports/runIos.native.js.map +1 -1
- package/dist/exports/serve.native.js +1 -1
- package/dist/exports/serve.native.js.map +1 -1
- package/dist/exports/serveStaticAssets.native.js +1 -1
- package/dist/exports/serveStaticAssets.native.js.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.mjs +4 -1
- package/dist/index.native.js +4 -1
- package/dist/native-source-contract.mjs +3 -0
- package/dist/native-source-contract.native.js +3 -0
- package/dist/patches/builtInDepPatches.mjs +0 -13
- package/dist/patches/builtInDepPatches.mjs.map +1 -1
- package/dist/patches/builtInDepPatches.native.js +0 -13
- package/dist/patches/builtInDepPatches.native.js.map +1 -1
- package/dist/plugins/autoDepOptimizePlugin.mjs +3 -2
- package/dist/plugins/autoDepOptimizePlugin.mjs.map +1 -1
- package/dist/plugins/autoDepOptimizePlugin.native.js +6 -3
- package/dist/plugins/autoDepOptimizePlugin.native.js.map +1 -1
- package/dist/plugins/backgroundComputationPlugin.mjs +27 -0
- package/dist/plugins/backgroundComputationPlugin.mjs.map +1 -0
- package/dist/plugins/backgroundComputationPlugin.native.js +32 -0
- package/dist/plugins/backgroundComputationPlugin.native.js.map +1 -0
- package/dist/plugins/reactNativeCommonJsPlugin.native.js +3 -3
- package/dist/plugins/reactNativeCommonJsPlugin.native.js.map +1 -1
- package/dist/plugins/reactNativeDevAssetPlugin.mjs +2 -1
- package/dist/plugins/reactNativeDevAssetPlugin.mjs.map +1 -1
- package/dist/plugins/reactNativeDevAssetPlugin.native.js +3 -2
- package/dist/plugins/reactNativeDevAssetPlugin.native.js.map +1 -1
- package/dist/plugins/reactNativeDevServer.mjs +87 -24
- package/dist/plugins/reactNativeDevServer.mjs.map +1 -1
- package/dist/plugins/reactNativeDevServer.native.js +162 -45
- package/dist/plugins/reactNativeDevServer.native.js.map +1 -1
- package/dist/plugins/workletImportsPlugin.mjs +9 -30
- package/dist/plugins/workletImportsPlugin.mjs.map +1 -1
- package/dist/plugins/workletImportsPlugin.native.js +10 -35
- package/dist/plugins/workletImportsPlugin.native.js.map +1 -1
- package/dist/rn-commands/bundle/buildBundle.mjs +3 -3
- package/dist/rn-commands/bundle/buildBundle.mjs.map +1 -1
- package/dist/rn-commands/bundle/buildBundle.native.js +3 -3
- package/dist/rn-commands/bundle/buildBundle.native.js.map +1 -1
- package/dist/runtime/hmr-client.native.js +3 -7
- package/dist/runtime/hmr-client.native.js.map +1 -1
- package/dist/runtime/hmr-runtime.native.js +32 -40
- package/dist/runtime/hmr-runtime.native.js.map +1 -1
- package/dist/serve/node.native.js +2 -2
- package/dist/serve/node.native.js.map +1 -1
- package/dist/swift-package-id.mjs +3 -0
- package/dist/swift-package-id.native.js +3 -0
- package/dist/user-interface/index.native.js +6 -6
- package/dist/user-interface/index.native.js.map +1 -1
- package/dist/utils/compileNativeDevBytecode.mjs +93 -0
- package/dist/utils/compileNativeDevBytecode.mjs.map +1 -0
- package/dist/utils/compileNativeDevBytecode.native.js +98 -0
- package/dist/utils/compileNativeDevBytecode.native.js.map +1 -0
- package/dist/utils/createNativeDevEngine.mjs +227 -20
- package/dist/utils/createNativeDevEngine.mjs.map +1 -1
- package/dist/utils/createNativeDevEngine.native.js +249 -37
- package/dist/utils/createNativeDevEngine.native.js.map +1 -1
- package/dist/utils/execSync.native.js +1 -1
- package/dist/utils/execSync.native.js.map +1 -1
- package/dist/utils/getServerEntry.native.js +2 -2
- package/dist/utils/getServerEntry.native.js.map +1 -1
- package/dist/utils/nativeRun.mjs +32 -16
- package/dist/utils/nativeRun.mjs.map +1 -1
- package/dist/utils/nativeRun.native.js +43 -18
- package/dist/utils/nativeRun.native.js.map +1 -1
- package/dist/utils/nativeSourceContract.mjs +1823 -0
- package/dist/utils/nativeSourceContract.mjs.map +1 -0
- package/dist/utils/nativeSourceContract.native.js +2425 -0
- package/dist/utils/nativeSourceContract.native.js.map +1 -0
- package/dist/utils/patches.native.js +15 -24
- package/dist/utils/patches.native.js.map +1 -1
- package/dist/utils/printServerUrls.native.js +1 -1
- package/dist/utils/printServerUrls.native.js.map +1 -1
- package/dist/utils/scanDepsToOptimize.mjs +7 -1
- package/dist/utils/scanDepsToOptimize.mjs.map +1 -1
- package/dist/utils/scanDepsToOptimize.native.js +27 -2
- package/dist/utils/scanDepsToOptimize.native.js.map +1 -1
- package/dist/utils/swiftPackageId.mjs +31 -2
- package/dist/utils/swiftPackageId.mjs.map +1 -1
- package/dist/utils/swiftPackageId.native.js +50 -2
- package/dist/utils/swiftPackageId.native.js.map +1 -1
- package/dist/vxrn-vite-plugin.native.js +1 -1
- package/dist/vxrn-vite-plugin.native.js.map +1 -1
- package/expo-plugin.cjs +792 -2
- package/native-project-patches.cjs +201 -10
- package/package.json +34 -15
- package/src/backgroundComputation.ts +220 -0
- package/src/backgroundComputationNative.ts +23 -0
- package/src/config/getBaseViteConfigOnly.ts +8 -9
- package/src/config/getOptimizeDeps.ts +3 -1
- package/src/config/getViteServerConfig.ts +16 -0
- package/src/config/glbAssets.test.ts +228 -0
- package/src/constants/defaults.ts +6 -0
- package/src/exports/dev.ts +5 -1
- package/src/exports/expoPlugin.test.mjs +337 -15
- package/src/exports/prebuild.ts +0 -31
- package/src/exports/prebuildWithoutExpo.test.ts +1588 -48
- package/src/exports/prebuildWithoutExpo.ts +1573 -213
- package/src/index.ts +8 -0
- package/src/native-source-contract.ts +16 -0
- package/src/patches/builtInDepPatches.ts +0 -21
- package/src/plugins/autoDepOptimizePlugin.ts +7 -2
- package/src/plugins/backgroundComputationPlugin.test.ts +77 -0
- package/src/plugins/backgroundComputationPlugin.ts +29 -0
- package/src/plugins/reactNativeDevAssetPlugin.ts +5 -1
- package/src/plugins/reactNativeDevServer.test.ts +74 -0
- package/src/plugins/reactNativeDevServer.ts +145 -28
- package/src/plugins/rnCodegenPlugin.test.ts +30 -0
- package/src/plugins/workletImportsPlugin.ts +19 -37
- package/src/rn-commands/bundle/buildBundle.ts +5 -3
- package/src/swift-package-id.ts +1 -0
- package/src/utils/compileNativeDevBytecode.test.ts +168 -0
- package/src/utils/compileNativeDevBytecode.ts +117 -0
- package/src/utils/createNativeDevEngine.test.ts +643 -18
- package/src/utils/createNativeDevEngine.ts +278 -48
- package/src/utils/nativeRun.test.ts +125 -23
- package/src/utils/nativeRun.ts +51 -23
- package/src/utils/nativeSourceArtifacts.test.ts +39 -0
- package/src/utils/nativeSourceContract.test.ts +600 -0
- package/src/utils/nativeSourceContract.ts +1897 -0
- package/src/utils/scanDepsToOptimize.test.ts +80 -0
- package/src/utils/scanDepsToOptimize.ts +18 -7
- package/src/utils/swiftPackageId.ts +25 -1
- package/types/backgroundComputation.d.ts +28 -0
- package/types/backgroundComputation.d.ts.map +1 -0
- package/types/backgroundComputationNative.d.ts +2 -0
- package/types/backgroundComputationNative.d.ts.map +1 -0
- package/types/config/getBaseViteConfigOnly.d.ts.map +1 -1
- package/types/config/getOptimizeDeps.d.ts.map +1 -1
- package/types/config/getViteServerConfig.d.ts.map +1 -1
- package/types/constants/defaults.d.ts.map +1 -1
- package/types/exports/dev.d.ts.map +1 -1
- package/types/exports/prebuild.d.ts.map +1 -1
- package/types/exports/prebuildWithoutExpo.d.ts +6 -0
- package/types/exports/prebuildWithoutExpo.d.ts.map +1 -1
- package/types/index.d.ts +2 -0
- package/types/index.d.ts.map +1 -1
- package/types/native-source-contract.d.ts +2 -0
- package/types/native-source-contract.d.ts.map +1 -0
- package/types/patches/builtInDepPatches.d.ts.map +1 -1
- package/types/plugins/autoDepOptimizePlugin.d.ts.map +1 -1
- package/types/plugins/backgroundComputationPlugin.d.ts +3 -0
- package/types/plugins/backgroundComputationPlugin.d.ts.map +1 -0
- package/types/plugins/reactNativeDevAssetPlugin.d.ts.map +1 -1
- package/types/plugins/reactNativeDevServer.d.ts.map +1 -1
- package/types/plugins/workletImportsPlugin.d.ts.map +1 -1
- package/types/rn-commands/bundle/buildBundle.d.ts.map +1 -1
- package/types/swift-package-id.d.ts +2 -0
- package/types/swift-package-id.d.ts.map +1 -0
- package/types/utils/compileNativeDevBytecode.d.ts +3 -0
- package/types/utils/compileNativeDevBytecode.d.ts.map +1 -0
- package/types/utils/createNativeDevEngine.d.ts +7 -22
- package/types/utils/createNativeDevEngine.d.ts.map +1 -1
- package/types/utils/nativeRun.d.ts.map +1 -1
- package/types/utils/nativeSourceContract.d.ts +121 -0
- package/types/utils/nativeSourceContract.d.ts.map +1 -0
- package/types/utils/scanDepsToOptimize.d.ts +1 -0
- package/types/utils/scanDepsToOptimize.d.ts.map +1 -1
- package/types/utils/swiftPackageId.d.ts +1 -0
- package/types/utils/swiftPackageId.d.ts.map +1 -1
package/src/index.ts
CHANGED
|
@@ -27,5 +27,13 @@ export * from './plugins/rollupRemoveUnusedImports'
|
|
|
27
27
|
export * from './plugins/autoDepOptimizePlugin'
|
|
28
28
|
export * from './plugins/rnCodegenPlugin'
|
|
29
29
|
export * from './plugins/workletImportsPlugin'
|
|
30
|
+
export * from './plugins/backgroundComputationPlugin'
|
|
30
31
|
|
|
31
32
|
export * from './types'
|
|
33
|
+
|
|
34
|
+
export {
|
|
35
|
+
transformBackgroundComputations,
|
|
36
|
+
loadBackgroundComputationModule,
|
|
37
|
+
createBackgroundWorkletModule,
|
|
38
|
+
assertBackgroundDependency,
|
|
39
|
+
} from './backgroundComputation'
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export {
|
|
2
|
+
nativeSourceContract,
|
|
3
|
+
writeNativeSourceDeclaration,
|
|
4
|
+
writeNativeSourceDeclarations,
|
|
5
|
+
renderSwiftSourceGlue,
|
|
6
|
+
kotlinSourceId,
|
|
7
|
+
renderKotlinSourceGlue,
|
|
8
|
+
swiftPodManifest,
|
|
9
|
+
writeSwiftPackageArtifacts,
|
|
10
|
+
type NativeSourceContract,
|
|
11
|
+
type NativeSourceMethod,
|
|
12
|
+
type NativeSourceModule,
|
|
13
|
+
type NativeSourceView,
|
|
14
|
+
type NativeSourceViewProp,
|
|
15
|
+
type SwiftPackageArtifacts,
|
|
16
|
+
} from './utils/nativeSourceContract'
|
|
@@ -469,25 +469,4 @@ ${contents}
|
|
|
469
469
|
},
|
|
470
470
|
},
|
|
471
471
|
},
|
|
472
|
-
// react native 0.87 made ReactContextBaseJavaModule a Kotlin class, so the
|
|
473
|
-
// bare `currentActivity` property in @react-navigation/native's MaterialSymbolModule
|
|
474
|
-
// no longer resolves and the Android build fails to compile. upstream alpha.45
|
|
475
|
-
// changed exactly this line; the monorepo stays on alpha.44 because alpha.45
|
|
476
|
-
// also pulls a @react-navigation/core alpha that renames Tabs APIs.
|
|
477
|
-
{
|
|
478
|
-
module: '@react-navigation/native',
|
|
479
|
-
patchFiles: {
|
|
480
|
-
version: '8.0.0-alpha.44',
|
|
481
|
-
'android/src/main/java/org/reactnavigation/MaterialSymbolModule.kt': (contents) => {
|
|
482
|
-
assertString(contents)
|
|
483
|
-
bailIfExists(contents, 'reactApplicationContext.currentActivity')
|
|
484
|
-
const patched = contents.replace(
|
|
485
|
-
'colorValue, currentActivity ?: reactApplicationContext',
|
|
486
|
-
'colorValue, reactApplicationContext.currentActivity ?: reactApplicationContext'
|
|
487
|
-
)
|
|
488
|
-
bailIfUnchanged(patched, contents)
|
|
489
|
-
return patched
|
|
490
|
-
},
|
|
491
|
-
},
|
|
492
|
-
},
|
|
493
472
|
]
|
|
@@ -89,7 +89,10 @@ export async function getScannedOptimizeDepsConfig(props: FindDepsOptionsByMode)
|
|
|
89
89
|
include: filteredDeps,
|
|
90
90
|
exclude: EXCLUDE_LIST,
|
|
91
91
|
},
|
|
92
|
-
noExternal:
|
|
92
|
+
noExternal: [
|
|
93
|
+
...filteredDeps,
|
|
94
|
+
...result.noExternalDeps.filter((dep) => !excludeSet.has(dep)),
|
|
95
|
+
],
|
|
93
96
|
},
|
|
94
97
|
}
|
|
95
98
|
}
|
|
@@ -119,6 +122,7 @@ export async function findDepsToOptimize({
|
|
|
119
122
|
'yarn.lock',
|
|
120
123
|
'package-lock.json',
|
|
121
124
|
'pnpm-lock.yaml',
|
|
125
|
+
'bun.lock',
|
|
122
126
|
'bun.lockb',
|
|
123
127
|
])
|
|
124
128
|
|
|
@@ -136,7 +140,8 @@ export async function findDepsToOptimize({
|
|
|
136
140
|
if (
|
|
137
141
|
lockFileHash === cachedLockFileHash &&
|
|
138
142
|
!!cachedDepsToPreBundle &&
|
|
139
|
-
'prebundleDeps' in cachedDepsToPreBundle
|
|
143
|
+
'prebundleDeps' in cachedDepsToPreBundle &&
|
|
144
|
+
Array.isArray(cachedDepsToPreBundle.noExternalDeps)
|
|
140
145
|
) {
|
|
141
146
|
value = cachedDepsToPreBundle
|
|
142
147
|
debug?.(`Using cached scan results from ${cacheFilePath}`)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { expect, test } from 'vitest'
|
|
2
|
+
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
|
3
|
+
import { tmpdir } from 'node:os'
|
|
4
|
+
import { join } from 'node:path'
|
|
5
|
+
import { runInNewContext } from 'node:vm'
|
|
6
|
+
import { rolldown } from 'rolldown'
|
|
7
|
+
import { backgroundComputationPlugin } from './backgroundComputationPlugin'
|
|
8
|
+
import { workletImportsPlugin } from './workletImportsPlugin'
|
|
9
|
+
|
|
10
|
+
async function bundle(source: string) {
|
|
11
|
+
const root = mkdtempSync(join(tmpdir(), 'one-background-definition-'))
|
|
12
|
+
writeFileSync(join(root, 'entry.ts'), source)
|
|
13
|
+
writeFileSync(
|
|
14
|
+
join(root, 'calculate.ts'),
|
|
15
|
+
`import { multiplier } from './helper'; export function calculate(input: number) { return input * multiplier }`
|
|
16
|
+
)
|
|
17
|
+
writeFileSync(join(root, 'helper.ts'), 'export const multiplier = 3')
|
|
18
|
+
let build: Awaited<ReturnType<typeof rolldown>> | undefined
|
|
19
|
+
try {
|
|
20
|
+
build = await rolldown({
|
|
21
|
+
input: join(root, 'entry.ts'),
|
|
22
|
+
plugins: [
|
|
23
|
+
backgroundComputationPlugin('native'),
|
|
24
|
+
workletImportsPlugin({}),
|
|
25
|
+
{
|
|
26
|
+
name: 'definition-contract',
|
|
27
|
+
resolveId(id) {
|
|
28
|
+
if (id === 'one/background') return '\0definition-contract'
|
|
29
|
+
},
|
|
30
|
+
load(id) {
|
|
31
|
+
if (id === '\0definition-contract')
|
|
32
|
+
return 'export function defineBackgroundComputation(calculate) { return calculate }'
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
})
|
|
37
|
+
const result = await build.generate({ format: 'cjs' })
|
|
38
|
+
const chunk = result.output[0]
|
|
39
|
+
if (chunk.type !== 'chunk') throw new Error('expected executable output')
|
|
40
|
+
const exports: Record<string, unknown> = {}
|
|
41
|
+
runInNewContext(chunk.code, { exports })
|
|
42
|
+
return exports
|
|
43
|
+
} finally {
|
|
44
|
+
await build?.close()
|
|
45
|
+
rmSync(root, { recursive: true, force: true })
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
test('bundles an imported pure graph into the selected native calculation', async () => {
|
|
50
|
+
const result = await bundle(
|
|
51
|
+
`import { defineBackgroundComputation as define } from 'one/background'; import { calculate } from './calculate'; const task = define(calculate); export const answer = task(7);`
|
|
52
|
+
)
|
|
53
|
+
expect(result.answer).toBe(21)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test('unused definition imports do not reject a module', async () => {
|
|
57
|
+
const result = await bundle(
|
|
58
|
+
`import { defineBackgroundComputation } from 'one/background'; export const answer = 21;`
|
|
59
|
+
)
|
|
60
|
+
expect(result.answer).toBe(21)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test('rejects component definitions even beside a valid definition', async () => {
|
|
64
|
+
await expect(
|
|
65
|
+
bundle(
|
|
66
|
+
`import { defineBackgroundComputation } from 'one/background'; import { calculate } from './calculate'; export const good = defineBackgroundComputation(calculate); export function Component() { return defineBackgroundComputation(calculate) }`
|
|
67
|
+
)
|
|
68
|
+
).rejects.toThrow('definitions must be module-scope variables')
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
test('rejects inline calculations that can capture component state', async () => {
|
|
72
|
+
await expect(
|
|
73
|
+
bundle(
|
|
74
|
+
`import { defineBackgroundComputation } from 'one/background'; export const bad = defineBackgroundComputation((input) => input);`
|
|
75
|
+
)
|
|
76
|
+
).rejects.toThrow('one named import from a relative pure module')
|
|
77
|
+
})
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { dirname, resolve, relative } from 'node:path'
|
|
2
|
+
import {
|
|
3
|
+
transformBackgroundComputations,
|
|
4
|
+
loadBackgroundComputationModule,
|
|
5
|
+
} from '../backgroundComputation'
|
|
6
|
+
import type { Plugin } from 'rolldown'
|
|
7
|
+
|
|
8
|
+
export function backgroundComputationPlugin(platform: 'web' | 'native'): Plugin {
|
|
9
|
+
return {
|
|
10
|
+
name: `one:background-computation:${platform}`,
|
|
11
|
+
transform: {
|
|
12
|
+
order: 'pre',
|
|
13
|
+
handler(code, id) {
|
|
14
|
+
return transformBackgroundComputations(code, id, {
|
|
15
|
+
platform,
|
|
16
|
+
resolve: (source, importer) => resolve(dirname(importer), source),
|
|
17
|
+
workerFactory: (module) =>
|
|
18
|
+
`() => new Worker(new URL(${JSON.stringify(`./${relative(dirname(id), module.id).replaceAll('\\', '/')}`)}, import.meta.url), { type: 'module' })`,
|
|
19
|
+
})
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
resolveId(source) {
|
|
23
|
+
if (platform === 'web' && loadBackgroundComputationModule(source)) return source
|
|
24
|
+
},
|
|
25
|
+
load(id) {
|
|
26
|
+
if (platform === 'web') return loadBackgroundComputationModule(id)?.source
|
|
27
|
+
},
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from 'node:path'
|
|
2
2
|
import FSExtra from 'fs-extra'
|
|
3
|
+
import { getMimeType } from 'hono/utils/mime'
|
|
3
4
|
|
|
4
5
|
import type { Plugin, ResolvedConfig } from 'vite'
|
|
5
6
|
import colors from 'picocolors'
|
|
@@ -138,7 +139,10 @@ export default asset;
|
|
|
138
139
|
try {
|
|
139
140
|
const asset = await FSExtra.readFile(assetPath)
|
|
140
141
|
|
|
141
|
-
res.setHeader(
|
|
142
|
+
res.setHeader(
|
|
143
|
+
'content-type',
|
|
144
|
+
getMimeType(assetPath) || 'application/octet-stream'
|
|
145
|
+
)
|
|
142
146
|
res.write(asset)
|
|
143
147
|
res.end()
|
|
144
148
|
} catch (e) {
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises'
|
|
2
|
+
import { tmpdir } from 'node:os'
|
|
3
|
+
import { join } from 'node:path'
|
|
4
|
+
import { createServer } from 'vite'
|
|
5
|
+
import { expect, it } from 'vitest'
|
|
6
|
+
import { createReactNativeDevServerPlugin } from './reactNativeDevServer'
|
|
7
|
+
|
|
8
|
+
it('serves native media with byte ranges and preview content types', async () => {
|
|
9
|
+
const root = await mkdtemp(join(tmpdir(), 'native-media-http-'))
|
|
10
|
+
const bytes = Buffer.from([0, 1, 2, 3, 4, 5, 6, 7])
|
|
11
|
+
await mkdir(join(root, 'assets'))
|
|
12
|
+
await writeFile(join(root, 'assets/clip.mp4'), bytes)
|
|
13
|
+
await writeFile(join(root, 'assets/preview.txt'), 'native preview')
|
|
14
|
+
const server = await createServer({
|
|
15
|
+
root,
|
|
16
|
+
configFile: false,
|
|
17
|
+
plugins: [createReactNativeDevServerPlugin()],
|
|
18
|
+
server: { host: '127.0.0.1', port: 0 },
|
|
19
|
+
})
|
|
20
|
+
try {
|
|
21
|
+
await server.listen()
|
|
22
|
+
const { port } = server.httpServer!.address() as { port: number }
|
|
23
|
+
const origin = `http://127.0.0.1:${port}/assets/assets`
|
|
24
|
+
const full = await fetch(`${origin}/clip.mp4?platform=ios`)
|
|
25
|
+
expect(full.status).toBe(200)
|
|
26
|
+
expect(full.headers.get('content-type')).toBe('video/mp4')
|
|
27
|
+
expect(full.headers.get('accept-ranges')).toBe('bytes')
|
|
28
|
+
expect(Buffer.from(await full.arrayBuffer())).toEqual(bytes)
|
|
29
|
+
for (const [range, start, end] of [
|
|
30
|
+
['bytes=0-1', 0, 1],
|
|
31
|
+
['bytes=3-', 3, 7],
|
|
32
|
+
['bytes=-2', 6, 7],
|
|
33
|
+
['bytes=5-99', 5, 7],
|
|
34
|
+
['bytes=-99', 0, 7],
|
|
35
|
+
] as const) {
|
|
36
|
+
const response = await fetch(`${origin}/clip.mp4`, { headers: { range } })
|
|
37
|
+
expect(response.status).toBe(206)
|
|
38
|
+
expect(response.headers.get('content-range')).toBe(`bytes ${start}-${end}/8`)
|
|
39
|
+
expect(response.headers.get('content-length')).toBe(String(end - start + 1))
|
|
40
|
+
expect(Buffer.from(await response.arrayBuffer())).toEqual(
|
|
41
|
+
bytes.subarray(start, end + 1)
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
for (const range of ['bytes=8-', 'bytes=4-2', 'bytes=-0']) {
|
|
45
|
+
const response = await fetch(`${origin}/clip.mp4`, { headers: { range } })
|
|
46
|
+
expect(response.status).toBe(416)
|
|
47
|
+
expect(response.headers.get('content-range')).toBe('bytes */8')
|
|
48
|
+
expect(await response.text()).toBe('')
|
|
49
|
+
}
|
|
50
|
+
const fullResponseHeaders: Record<string, string>[] = [
|
|
51
|
+
{ range: 'items=0-1' },
|
|
52
|
+
{ range: 'bytes=0-1,4-5' },
|
|
53
|
+
{ range: 'bytes=0-1', 'if-range': '"old"' },
|
|
54
|
+
]
|
|
55
|
+
for (const headers of fullResponseHeaders) {
|
|
56
|
+
const response = await fetch(`${origin}/clip.mp4`, { headers })
|
|
57
|
+
expect(response.status).toBe(200)
|
|
58
|
+
expect(Buffer.from(await response.arrayBuffer())).toEqual(bytes)
|
|
59
|
+
}
|
|
60
|
+
const head = await fetch(`${origin}/clip.mp4`, {
|
|
61
|
+
method: 'HEAD',
|
|
62
|
+
headers: { range: 'bytes=0-1' },
|
|
63
|
+
})
|
|
64
|
+
expect(head.status).toBe(200)
|
|
65
|
+
expect(head.headers.get('content-length')).toBe('8')
|
|
66
|
+
expect(await head.text()).toBe('')
|
|
67
|
+
const preview = await fetch(`${origin}/preview.txt`)
|
|
68
|
+
expect(preview.headers.get('content-type')).toBe('text/plain')
|
|
69
|
+
expect(await preview.text()).toBe('native preview')
|
|
70
|
+
} finally {
|
|
71
|
+
await server.close()
|
|
72
|
+
await rm(root, { recursive: true, force: true })
|
|
73
|
+
}
|
|
74
|
+
})
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { Connect, Plugin, ViteDevServer } from 'vite'
|
|
2
2
|
import { WebSocketServer, type WebSocket } from 'ws'
|
|
3
3
|
import { createMessageSocket } from '@vxrn/utils'
|
|
4
|
+
import {
|
|
5
|
+
expoClientFromNativeApp,
|
|
6
|
+
type NativeAppManifest,
|
|
7
|
+
} from '@vxrn/utils/nativeAppManifest'
|
|
4
8
|
import {
|
|
5
9
|
addConnectedNativeClient,
|
|
6
10
|
removeConnectedNativeClient,
|
|
@@ -8,11 +12,13 @@ import {
|
|
|
8
12
|
import type { VXRNOptionsFilled } from '../config/getOptionsFilled'
|
|
9
13
|
import { URL } from 'node:url'
|
|
10
14
|
import { createRequire } from 'node:module'
|
|
11
|
-
import { join } from 'node:path'
|
|
15
|
+
import { extname, join, resolve, sep } from 'node:path'
|
|
12
16
|
import { randomUUID } from 'node:crypto'
|
|
17
|
+
import { existsSync, readFileSync, statSync } from 'node:fs'
|
|
13
18
|
import { readFile } from 'node:fs/promises'
|
|
14
19
|
import { createDevMiddleware } from '@react-native/dev-middleware'
|
|
15
20
|
import { createNativeDevEngine } from '../utils/createNativeDevEngine'
|
|
21
|
+
import { nativeDevBytecodeCompiler } from '../utils/compileNativeDevBytecode'
|
|
16
22
|
import { getBoundPort } from '../utils/getBoundPort'
|
|
17
23
|
import {
|
|
18
24
|
getNativeFramePlatform,
|
|
@@ -32,17 +38,35 @@ type NativeHmrSocket = WebSocket & {
|
|
|
32
38
|
vxrnPlatform: 'ios' | 'android'
|
|
33
39
|
}
|
|
34
40
|
|
|
41
|
+
// metro's asset server answers `/assets/<path>` for any asset file inside the
|
|
42
|
+
// project root, registered by the bundle or not.
|
|
43
|
+
function getProjectAssetFile(
|
|
44
|
+
root: string,
|
|
45
|
+
pathname: string
|
|
46
|
+
): { filePath: string; type: string } | undefined {
|
|
47
|
+
const filePath = resolve(root, pathname.slice('/assets/'.length))
|
|
48
|
+
if (!filePath.startsWith(resolve(root) + sep)) return
|
|
49
|
+
const type = extname(filePath).slice(1)
|
|
50
|
+
if (getNativeAssetContentType(type) === 'application/octet-stream') return
|
|
51
|
+
if (!existsSync(filePath) || !statSync(filePath).isFile()) return
|
|
52
|
+
return { filePath, type }
|
|
53
|
+
}
|
|
54
|
+
|
|
35
55
|
export function getNativeAssetContentType(type: string): string {
|
|
36
56
|
switch (type.toLowerCase()) {
|
|
37
57
|
case 'bmp':
|
|
38
58
|
return 'image/bmp'
|
|
39
59
|
case 'gif':
|
|
40
60
|
return 'image/gif'
|
|
61
|
+
case 'glb':
|
|
62
|
+
return 'model/gltf-binary'
|
|
41
63
|
case 'jpeg':
|
|
42
64
|
case 'jpg':
|
|
43
65
|
return 'image/jpeg'
|
|
44
66
|
case 'json':
|
|
45
67
|
return 'application/json'
|
|
68
|
+
case 'mp4':
|
|
69
|
+
return 'video/mp4'
|
|
46
70
|
case 'otf':
|
|
47
71
|
return 'font/otf'
|
|
48
72
|
case 'png':
|
|
@@ -51,6 +75,8 @@ export function getNativeAssetContentType(type: string): string {
|
|
|
51
75
|
return 'image/svg+xml'
|
|
52
76
|
case 'ttf':
|
|
53
77
|
return 'font/ttf'
|
|
78
|
+
case 'txt':
|
|
79
|
+
return 'text/plain'
|
|
54
80
|
case 'webp':
|
|
55
81
|
return 'image/webp'
|
|
56
82
|
case 'woff':
|
|
@@ -87,6 +113,7 @@ export function createReactNativeDevServerPlugin(
|
|
|
87
113
|
const devEngineCreating: Record<string, Promise<unknown> | null> = {}
|
|
88
114
|
const warnedProdBundleRequest = new Set<string>()
|
|
89
115
|
const pendingReloadPlatforms = new Set<'ios' | 'android'>()
|
|
116
|
+
let compileBytecode: ReturnType<typeof nativeDevBytecodeCompiler> | undefined
|
|
90
117
|
|
|
91
118
|
const devToolsSocketEndpoints = ['/inspector/device', '/inspector/debug']
|
|
92
119
|
const reactNativeDevToolsUrl = `http://${host}:${getBoundPort(server)}`
|
|
@@ -101,14 +128,32 @@ export function createReactNativeDevServerPlugin(
|
|
|
101
128
|
// an expo project's clients (dev launcher, expo-updates, peach) ask `/`,
|
|
102
129
|
// `/manifest` or `/index.exp` for the evaluated app config with an
|
|
103
130
|
// `expo-platform` header or `?platform=`. answer with the expo updates
|
|
104
|
-
// manifest expo cli's ExpoGoManifestHandlerMiddleware serves.
|
|
105
|
-
//
|
|
131
|
+
// manifest expo cli's ExpoGoManifestHandlerMiddleware serves. only apps
|
|
132
|
+
// that declare expo use expo prebuild; a hoisted @expo/config alone does
|
|
133
|
+
// not make a one native app an expo app.
|
|
106
134
|
const projectRequire = createRequire(join(root, 'package.json'))
|
|
135
|
+
const packageJsonPath = join(root, 'package.json')
|
|
136
|
+
const packageJson = existsSync(packageJsonPath)
|
|
137
|
+
? JSON.parse(readFileSync(packageJsonPath, 'utf8'))
|
|
138
|
+
: {}
|
|
139
|
+
const hasExpo =
|
|
140
|
+
Object.hasOwn(packageJson.dependencies ?? {}, 'expo') ||
|
|
141
|
+
Object.hasOwn(packageJson.devDependencies ?? {}, 'expo')
|
|
142
|
+
// a one app that declares no expo answers from its native.app manifest,
|
|
143
|
+
// mapped to the same expo config shape.
|
|
144
|
+
const nativeApp: NativeAppManifest | undefined = hasExpo
|
|
145
|
+
? undefined
|
|
146
|
+
: (globalThis as any).__vxrnNativeEntryConfig?.app
|
|
107
147
|
const anonymousScopeId = randomUUID()
|
|
108
148
|
server.middlewares.use(async (req, res, next) => {
|
|
149
|
+
if (!hasExpo && !nativeApp) return next()
|
|
109
150
|
if (req.method !== 'GET' && req.method !== 'HEAD') return next()
|
|
110
151
|
const url = new URL(req.url || '/', `http://${req.headers.host || 'localhost'}`)
|
|
111
|
-
if (
|
|
152
|
+
if (
|
|
153
|
+
url.pathname !== '/' &&
|
|
154
|
+
url.pathname !== '/manifest' &&
|
|
155
|
+
url.pathname !== '/index.exp'
|
|
156
|
+
) {
|
|
112
157
|
return next()
|
|
113
158
|
}
|
|
114
159
|
const header = req.headers['expo-platform'] || req.headers['exponent-platform']
|
|
@@ -116,26 +161,47 @@ export function createReactNativeDevServerPlugin(
|
|
|
116
161
|
validPlatforms[url.searchParams.get('platform') || String(header || '')]
|
|
117
162
|
if (!platform) return next()
|
|
118
163
|
|
|
119
|
-
let expoConfigPath: string
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
164
|
+
let expoConfigPath: string | undefined
|
|
165
|
+
if (!nativeApp) {
|
|
166
|
+
try {
|
|
167
|
+
expoConfigPath = projectRequire.resolve('@expo/config')
|
|
168
|
+
} catch {
|
|
169
|
+
return next()
|
|
170
|
+
}
|
|
124
171
|
}
|
|
125
|
-
const expoRequire = createRequire(expoConfigPath)
|
|
126
|
-
const { getConfig } = expoRequire('@expo/config')
|
|
127
|
-
const { resolveRelativeEntryPoint } = expoRequire('@expo/config/paths')
|
|
128
|
-
const { Updates } = expoRequire('@expo/config-plugins')
|
|
129
172
|
|
|
130
173
|
try {
|
|
131
|
-
const { exp, pkg } = getConfig(root)
|
|
132
174
|
const hostUri = req.headers.host || `localhost:${getBoundPort(server)}`
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
175
|
+
const { exp, mainModuleName, runtimeVersion } = nativeApp
|
|
176
|
+
? {
|
|
177
|
+
exp: expoClientFromNativeApp(nativeApp),
|
|
178
|
+
// the vite native server answers any `.bundle` path with the
|
|
179
|
+
// app entry, so `index` is what a one app's native build loads.
|
|
180
|
+
mainModuleName: 'index',
|
|
181
|
+
runtimeVersion: nativeApp.updates?.runtimeVersion ?? null,
|
|
182
|
+
}
|
|
183
|
+
: await (async () => {
|
|
184
|
+
const expoRequire = createRequire(expoConfigPath!)
|
|
185
|
+
const { getConfig } = expoRequire('@expo/config')
|
|
186
|
+
const { resolveRelativeEntryPoint } = expoRequire('@expo/config/paths')
|
|
187
|
+
const { Updates } = expoRequire('@expo/config-plugins')
|
|
188
|
+
const { exp, pkg } = getConfig(root)
|
|
189
|
+
return {
|
|
190
|
+
exp,
|
|
191
|
+
mainModuleName: resolveRelativeEntryPoint(root, {
|
|
192
|
+
platform,
|
|
193
|
+
pkg,
|
|
194
|
+
}) as string,
|
|
195
|
+
runtimeVersion: await Updates.getRuntimeVersionAsync(
|
|
196
|
+
root,
|
|
197
|
+
{
|
|
198
|
+
...exp,
|
|
199
|
+
runtimeVersion: exp.runtimeVersion ?? { policy: 'sdkVersion' },
|
|
200
|
+
},
|
|
201
|
+
platform
|
|
202
|
+
),
|
|
203
|
+
}
|
|
204
|
+
})()
|
|
139
205
|
const manifest = JSON.stringify({
|
|
140
206
|
id: randomUUID(),
|
|
141
207
|
createdAt: new Date().toISOString(),
|
|
@@ -146,7 +212,7 @@ export function createReactNativeDevServerPlugin(
|
|
|
146
212
|
url: `http://${hostUri}/${encodeURI(mainModuleName.replace(/^\/+/, ''))}.bundle?platform=${platform}&dev=true&hot=false`,
|
|
147
213
|
},
|
|
148
214
|
assets: [],
|
|
149
|
-
metadata: {},
|
|
215
|
+
metadata: nativeApp ? { oneNativeApp: true } : {},
|
|
150
216
|
extra: {
|
|
151
217
|
eas: { projectId: exp.extra?.eas?.projectId ?? undefined },
|
|
152
218
|
expoClient: { ...exp, hostUri },
|
|
@@ -191,8 +257,11 @@ export function createReactNativeDevServerPlugin(
|
|
|
191
257
|
})
|
|
192
258
|
|
|
193
259
|
// Native AssetSourceResolver requests the URL registered in the Rolldown
|
|
194
|
-
// bundle.
|
|
195
|
-
//
|
|
260
|
+
// bundle. Like Metro, any other project file is served at
|
|
261
|
+
// `/assets/<project-relative path>`: a splash image or font that only the
|
|
262
|
+
// app config names is never in the bundle. Install this before React
|
|
263
|
+
// Native's generic middleware, which otherwise terminates unknown
|
|
264
|
+
// /assets requests with an HTML 404.
|
|
196
265
|
server.middlewares.use(async (req, res, next) => {
|
|
197
266
|
if (req.method !== 'GET' && req.method !== 'HEAD') return next()
|
|
198
267
|
|
|
@@ -204,7 +273,6 @@ export function createReactNativeDevServerPlugin(
|
|
|
204
273
|
|
|
205
274
|
const platform = validPlatforms[url.searchParams.get('platform') || '']
|
|
206
275
|
const engine = platform ? devEngines[platform] : undefined
|
|
207
|
-
if (!engine) return next()
|
|
208
276
|
|
|
209
277
|
let pathname: string
|
|
210
278
|
try {
|
|
@@ -215,8 +283,11 @@ export function createReactNativeDevServerPlugin(
|
|
|
215
283
|
return
|
|
216
284
|
}
|
|
217
285
|
|
|
218
|
-
const asset =
|
|
286
|
+
const asset =
|
|
287
|
+
engine?.getAsset(pathname, url.searchParams.get('hash') || undefined) ??
|
|
288
|
+
getProjectAssetFile(root, pathname)
|
|
219
289
|
if (!asset) {
|
|
290
|
+
if (!engine) return next()
|
|
220
291
|
res.writeHead(404, { 'Content-Type': 'text/plain' })
|
|
221
292
|
res.end('Native asset not found')
|
|
222
293
|
return
|
|
@@ -224,11 +295,49 @@ export function createReactNativeDevServerPlugin(
|
|
|
224
295
|
|
|
225
296
|
try {
|
|
226
297
|
const contents = await readFile(asset.filePath)
|
|
227
|
-
|
|
298
|
+
const headers = {
|
|
228
299
|
'Cache-Control': 'no-cache',
|
|
300
|
+
'Accept-Ranges': 'bytes',
|
|
229
301
|
'Content-Length': String(contents.byteLength),
|
|
230
302
|
'Content-Type': getNativeAssetContentType(asset.type),
|
|
231
|
-
}
|
|
303
|
+
}
|
|
304
|
+
// avfoundation probes remote media with byte ranges before loading it.
|
|
305
|
+
// ignore unsupported range units, multipart requests and if-range without
|
|
306
|
+
// a matching validator; these receive the complete representation.
|
|
307
|
+
const range =
|
|
308
|
+
req.method === 'GET' &&
|
|
309
|
+
!req.headers['if-range'] &&
|
|
310
|
+
/^bytes=(\d*)-(\d*)$/.exec(req.headers.range || '')
|
|
311
|
+
if (range && (range[1] || range[2])) {
|
|
312
|
+
const size = contents.byteLength
|
|
313
|
+
const start = range[1]
|
|
314
|
+
? Number(range[1])
|
|
315
|
+
: Math.max(0, size - Number(range[2]))
|
|
316
|
+
const end =
|
|
317
|
+
range[1] && range[2] ? Math.min(Number(range[2]), size - 1) : size - 1
|
|
318
|
+
if (
|
|
319
|
+
!Number.isSafeInteger(start) ||
|
|
320
|
+
!Number.isSafeInteger(end) ||
|
|
321
|
+
start > end ||
|
|
322
|
+
start >= size
|
|
323
|
+
) {
|
|
324
|
+
res.writeHead(416, {
|
|
325
|
+
...headers,
|
|
326
|
+
'Content-Length': '0',
|
|
327
|
+
'Content-Range': `bytes */${size}`,
|
|
328
|
+
})
|
|
329
|
+
res.end()
|
|
330
|
+
return
|
|
331
|
+
}
|
|
332
|
+
res.writeHead(206, {
|
|
333
|
+
...headers,
|
|
334
|
+
'Content-Length': String(end - start + 1),
|
|
335
|
+
'Content-Range': `bytes ${start}-${end}/${size}`,
|
|
336
|
+
})
|
|
337
|
+
res.end(contents.subarray(start, end + 1))
|
|
338
|
+
return
|
|
339
|
+
}
|
|
340
|
+
res.writeHead(200, headers)
|
|
232
341
|
res.end(req.method === 'HEAD' ? undefined : contents)
|
|
233
342
|
} catch (error) {
|
|
234
343
|
console.error(
|
|
@@ -448,6 +557,14 @@ export function createReactNativeDevServerPlugin(
|
|
|
448
557
|
|
|
449
558
|
try {
|
|
450
559
|
const bundle = await (await getDevEngine(platform)).getBundle()
|
|
560
|
+
let body: string | Buffer = bundle.code
|
|
561
|
+
if (platform === 'ios' && url.searchParams.get('bytecode') === 'hermes') {
|
|
562
|
+
compileBytecode ||= nativeDevBytecodeCompiler(root)
|
|
563
|
+
body = await compileBytecode(
|
|
564
|
+
bundle,
|
|
565
|
+
`${url.origin}/index.bundle?platform=${platform}`
|
|
566
|
+
)
|
|
567
|
+
}
|
|
451
568
|
// a client that connects after this response starts from the current
|
|
452
569
|
// route map and does not need the pending reload intended for the
|
|
453
570
|
// previous runtime.
|
|
@@ -460,7 +577,7 @@ export function createReactNativeDevServerPlugin(
|
|
|
460
577
|
'Cache-Control': 'no-store',
|
|
461
578
|
'Content-Type': 'text/javascript',
|
|
462
579
|
})
|
|
463
|
-
res.end(
|
|
580
|
+
res.end(body)
|
|
464
581
|
} catch (err) {
|
|
465
582
|
console.error(` Error building React Native bundle`)
|
|
466
583
|
console.error(err)
|
|
@@ -101,6 +101,36 @@ export default codegenNativeComponent<NativeProps>('MyCustomView') as HostCompon
|
|
|
101
101
|
)
|
|
102
102
|
})
|
|
103
103
|
|
|
104
|
+
it('forwards arguments of commands declared as method signatures', () => {
|
|
105
|
+
const inputCode = `
|
|
106
|
+
import type { HostComponent, ViewProps } from 'react-native';
|
|
107
|
+
import type { Int32 } from 'react-native/Libraries/Types/CodegenTypes';
|
|
108
|
+
import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
|
|
109
|
+
import codegenNativeCommands from 'react-native/Libraries/Utilities/codegenNativeCommands';
|
|
110
|
+
|
|
111
|
+
interface NativeProps extends ViewProps {}
|
|
112
|
+
interface NativeCommands {
|
|
113
|
+
setPage(viewRef: React.ElementRef<HostComponent<NativeProps>>, index: Int32): void
|
|
114
|
+
setScrollEnabledImperatively(viewRef: React.ElementRef<HostComponent<NativeProps>>, enabled: boolean): void
|
|
115
|
+
}
|
|
116
|
+
export const Commands = codegenNativeCommands<NativeCommands>({
|
|
117
|
+
supportedCommands: ['setPage', 'setScrollEnabledImperatively'],
|
|
118
|
+
});
|
|
119
|
+
export default codegenNativeComponent<NativeProps>('MyPager');
|
|
120
|
+
`
|
|
121
|
+
|
|
122
|
+
const result = transformReactNativeCodegen(
|
|
123
|
+
inputCode,
|
|
124
|
+
'/workspace/src/MyPagerNativeComponent.ts'
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
expect(result?.code).toContain('setPage(ref, index) {')
|
|
128
|
+
expect(result?.code).toContain('dispatchCommand(ref, "setPage", [index]);')
|
|
129
|
+
expect(result?.code).toContain(
|
|
130
|
+
'dispatchCommand(ref, "setScrollEnabledImperatively", [enabled]);'
|
|
131
|
+
)
|
|
132
|
+
})
|
|
133
|
+
|
|
104
134
|
it('throws an error if native commands are exported with a name other than Commands', () => {
|
|
105
135
|
const inputCode = `
|
|
106
136
|
import * as React from 'react';
|