vxrn 2.0.0-beta.99.1 → 2.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/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
|
@@ -31,6 +31,7 @@ import { withExpoPublicEnvAliases } from '@vxrn/utils/publicEnv'
|
|
|
31
31
|
import { DEFAULT_ASSET_EXTS } from '../constants/defaults'
|
|
32
32
|
import { getNativePrelude } from '../runtime/native-prelude'
|
|
33
33
|
import { rnCodegenPlugin } from '../plugins/rnCodegenPlugin'
|
|
34
|
+
import { dedupe } from '../config/getBaseViteConfigOnly'
|
|
34
35
|
|
|
35
36
|
// Hermes needs the whole class shape lowered *together*. downleveling only the
|
|
36
37
|
// class fields while leaving `class ... extends` as modern ES6 produces a
|
|
@@ -124,8 +125,11 @@ function getNativeResolveConfig(platform: 'ios' | 'android') {
|
|
|
124
125
|
// both here makes a CommonJS require eligible for an export map's ESM
|
|
125
126
|
// `import` target (for example @babel/runtime), which turns callable CJS
|
|
126
127
|
// helpers into namespace objects at runtime.
|
|
127
|
-
conditionNames: ['react-native'],
|
|
128
|
-
|
|
128
|
+
conditionNames: ['react-native', 'browser'],
|
|
129
|
+
// Native CommonJS consumers need the package's main export shape (punycode
|
|
130
|
+
// exposes ucs2 there, but only its default export contains it in module).
|
|
131
|
+
mainFields: ['react-native', 'browser', 'main', 'module'],
|
|
132
|
+
aliasFields: [['react-native'], ['browser']],
|
|
129
133
|
}
|
|
130
134
|
}
|
|
131
135
|
|
|
@@ -265,22 +269,13 @@ function getNativePlugins(
|
|
|
265
269
|
serverFileExclusionPlugin(),
|
|
266
270
|
// guard server-only / client-only / web-only / native-only imports
|
|
267
271
|
environmentGuardPlugin(),
|
|
272
|
+
missingDynamicImportPlugin(),
|
|
268
273
|
// alias RN's Metro HMR client to a no-op; vxrn drives HMR itself (the
|
|
269
274
|
// rolldown-runtime WebSocket); RN's client otherwise opens a /hot socket and
|
|
270
275
|
// red-boxes "unknown-message [object Object]" on every edit (new arch)
|
|
271
276
|
hmrClientNoopPlugin(),
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
// singleton at react-native/asset-registry. unresolved, rolldown would
|
|
275
|
-
// leave it as an external import that throws when the module runs.
|
|
276
|
-
{
|
|
277
|
-
name: 'vxrn:legacy-asset-registry',
|
|
278
|
-
resolveId(source, importer) {
|
|
279
|
-
if (source === '@react-native/assets-registry/registry') {
|
|
280
|
-
return this.resolve('react-native/asset-registry', importer, { skipSelf: true })
|
|
281
|
-
}
|
|
282
|
-
},
|
|
283
|
-
} satisfies Plugin,
|
|
277
|
+
nativeIdentityAliasPlugin(platform),
|
|
278
|
+
nativeAssetRegistryPlugin(root),
|
|
284
279
|
// route every `react-native/*` import through the app's react native copy.
|
|
285
280
|
// two reasons: package managers can install the same react native version at
|
|
286
281
|
// multiple physical paths and rolldown assigns each path its own module
|
|
@@ -291,6 +286,7 @@ function getNativePlugins(
|
|
|
291
286
|
// @react-native/virtualized-lists itself makes — fail resolution against
|
|
292
287
|
// the export map. metro resolves those from the filesystem, so do the same.
|
|
293
288
|
reactNativeDedupePlugin(root),
|
|
289
|
+
nativePackageDedupePlugin(root),
|
|
294
290
|
// stub CSS imports — native doesn't support CSS and rolldown removed CSS bundling
|
|
295
291
|
cssStubPlugin(),
|
|
296
292
|
// handle import.meta.glob (used by One's route system)
|
|
@@ -317,6 +313,8 @@ function getNativePlugins(
|
|
|
317
313
|
// loops the earlier lowering steps emit.
|
|
318
314
|
hermesLoopsPlugin(sourceMaps),
|
|
319
315
|
...userPlugins,
|
|
316
|
+
nativeExportsPrecedencePlugin(root, platform),
|
|
317
|
+
nativeDeclarationAliasPlugin(root, platform),
|
|
320
318
|
].map((plugin: Plugin) =>
|
|
321
319
|
plugin.api?.vxrnNative ? plugin.api.vxrnNative(context) : plugin
|
|
322
320
|
)
|
|
@@ -445,28 +443,13 @@ export function postProcessNativeBundle(code: string): string {
|
|
|
445
443
|
}
|
|
446
444
|
|
|
447
445
|
/**
|
|
448
|
-
*
|
|
449
|
-
*
|
|
450
|
-
*
|
|
451
|
-
* rolldown's dev() emits the bundle as a *script*. A top-level `var` in a
|
|
452
|
-
* script creates a NON-configurable property on the global object. RN's
|
|
453
|
-
* `Libraries/Network/fetch.js` declares `var ... Headers, Request, ...`, so
|
|
454
|
-
* `global.Headers`/`global.Request` become non-configurable. RN's `setUpXHR`
|
|
455
|
-
* then calls `polyfillGlobal('Headers', ...)`, whose `polyfillObjectProperty`
|
|
456
|
-
* does `Object.defineProperty(global, 'Headers', { configurable: true, ... })`
|
|
457
|
-
* — which throws "Cannot redefine property" and RN converts to
|
|
458
|
-
* `console.error('Failed to set polyfill. Headers is not configurable.')`.
|
|
459
|
-
* In dev that console.error becomes a blocking LogBox redbox, so the app never
|
|
460
|
-
* mounts (every appium navigation then times out). The prod build is immune:
|
|
461
|
-
* its modules are wrapped in closures (no global leak) and it has no LogBox.
|
|
446
|
+
* wrap dev module declarations in a function scope. script-level declarations
|
|
447
|
+
* create global properties that can block react native's lazy polyfills.
|
|
448
|
+
* production uses rolldown's iife format; the dev engine needs this wrapper.
|
|
462
449
|
*
|
|
463
|
-
*
|
|
464
|
-
*
|
|
465
|
-
*
|
|
466
|
-
* (`globalThis.global`/`__DEV__`/`process`/...). Intentional globals survive:
|
|
467
|
-
* the runtime is assigned via `globalThis.__rolldown_runtime__ = ...`, and HMR
|
|
468
|
-
* updates run through a *direct* `eval` inside this scope, so they still see
|
|
469
|
-
* the closure's `__esmMin`/`__toCommonJS`/module bindings.
|
|
450
|
+
* keep the prelude at script scope because it installs intentional globals.
|
|
451
|
+
* the dev runtime assigns itself to globalThis, and hmr's direct eval stays
|
|
452
|
+
* inside this closure so it can reach module bindings and runtime helpers.
|
|
470
453
|
*/
|
|
471
454
|
export function wrapNativeBundleModuleScope(code: string): string {
|
|
472
455
|
// the prelude (intro) ends right before the rolldown runtime region
|
|
@@ -584,7 +567,6 @@ export async function createNativeDevEngine(
|
|
|
584
567
|
let bundleReject: ((error: Error) => void) | null = null
|
|
585
568
|
let bundlePromise: Promise<NativeDevBundle> | null = null
|
|
586
569
|
|
|
587
|
-
const resolvedHost = host === '0.0.0.0' ? 'localhost' : host
|
|
588
570
|
const virtualEntry = nativeVirtualEntryPlugin(root, {
|
|
589
571
|
dev: true,
|
|
590
572
|
platform,
|
|
@@ -657,11 +639,15 @@ export async function createNativeDevEngine(
|
|
|
657
639
|
// only polyfilled once InitializeCore has run) and hand it to the runtime,
|
|
658
640
|
// which owns every message it carries. handed over immediately rather than
|
|
659
641
|
// from an open handler: a host that does not honor `socket.onopen = fn`
|
|
660
|
-
// would otherwise silently never get Fast Refresh.
|
|
642
|
+
// would otherwise silently never get Fast Refresh. the socket goes to the
|
|
643
|
+
// origin the device loaded this bundle from (RN's getDevServer), never the
|
|
644
|
+
// server's own host:port: an emulator behind `adb reverse tcp:8081 tcp:<port>`
|
|
645
|
+
// or a phone on the LAN cannot reach the server's localhost:<port>.
|
|
661
646
|
outro: `
|
|
662
647
|
try {
|
|
663
648
|
var __WS = (init_WebSocket(), __toCommonJS(WebSocket_exports)).default;
|
|
664
|
-
var
|
|
649
|
+
var __devServer = (init_getDevServer(), __toCommonJS(getDevServer_exports)).default();
|
|
650
|
+
var __hmrUrl = __devServer.url.replace(/^http/, 'ws') + 'hot?platform=${platform}&clientId=' + encodeURIComponent(__rolldown_runtime__.clientId);
|
|
665
651
|
__rolldown_runtime__.setup(new __WS(__hmrUrl));
|
|
666
652
|
} catch(e) {
|
|
667
653
|
// a swallowed failure here leaves the app permanently without Fast Refresh and
|
|
@@ -960,7 +946,11 @@ export async function buildNativeBundle(
|
|
|
960
946
|
// last, so it sees what every other plugin produced
|
|
961
947
|
nativeBundlePostProcessPlugin(),
|
|
962
948
|
],
|
|
963
|
-
output:
|
|
949
|
+
output: {
|
|
950
|
+
...getNativeOutputOptions(prelude, sourcemap, minify),
|
|
951
|
+
// native hosts evaluate a script; module bindings must stay out of globals.
|
|
952
|
+
format: 'iife',
|
|
953
|
+
},
|
|
964
954
|
})
|
|
965
955
|
const chunk = result.output.find((o) => o.type === 'chunk' && o.isEntry)
|
|
966
956
|
|
|
@@ -1132,16 +1122,18 @@ ${routeHmr}
|
|
|
1132
1122
|
routes.files = files
|
|
1133
1123
|
if (!useDynamicRouteRegistry) return
|
|
1134
1124
|
|
|
1125
|
+
mkdirSync(dirname(registryPath), { recursive: true })
|
|
1126
|
+
// rolldown resolves the registry through symlinked node_modules before its imports.
|
|
1127
|
+
const registryDir = realpathSync(dirname(registryPath))
|
|
1135
1128
|
const source = `export const routes = {\n${[...files]
|
|
1136
1129
|
.sort()
|
|
1137
1130
|
.map((file) => {
|
|
1138
1131
|
const routeKey = `/${normalizePath(relative(root, file))}`
|
|
1139
|
-
let importPath = normalizePath(relative(
|
|
1132
|
+
let importPath = normalizePath(relative(registryDir, file))
|
|
1140
1133
|
if (!importPath.startsWith('.')) importPath = `./${importPath}`
|
|
1141
1134
|
return ` ${JSON.stringify(routeKey)}: () => import(${JSON.stringify(importPath)})`
|
|
1142
1135
|
})
|
|
1143
1136
|
.join(',\n')}\n}\n`
|
|
1144
|
-
mkdirSync(dirname(registryPath), { recursive: true })
|
|
1145
1137
|
if (!existsSync(registryPath) || readFileSync(registryPath, 'utf8') !== source) {
|
|
1146
1138
|
writeFileSync(registryPath, source)
|
|
1147
1139
|
}
|
|
@@ -1243,6 +1235,36 @@ function serverFileExclusionPlugin(): Plugin {
|
|
|
1243
1235
|
}
|
|
1244
1236
|
}
|
|
1245
1237
|
|
|
1238
|
+
/**
|
|
1239
|
+
* An `import()` of a package that is not installed stays a raw dynamic import
|
|
1240
|
+
* in the bundle, which Hermes cannot parse. Metro resolves it as an optional
|
|
1241
|
+
* dependency: the import rejects with "Cannot find module" when it runs. Do the
|
|
1242
|
+
* same, so a library that probes an optional peer (`await import('x')` inside
|
|
1243
|
+
* try) builds and falls back at runtime.
|
|
1244
|
+
*/
|
|
1245
|
+
function missingDynamicImportPlugin(): Plugin {
|
|
1246
|
+
const PREFIX = '\0vxrn-missing-module:'
|
|
1247
|
+
return {
|
|
1248
|
+
name: 'vxrn:missing-dynamic-import',
|
|
1249
|
+
async resolveId(source, importer, options) {
|
|
1250
|
+
if (options.kind !== 'dynamic-import') return
|
|
1251
|
+
if (source.startsWith('.') || source.startsWith('/') || source.includes(':')) return
|
|
1252
|
+
if (source.startsWith('\0')) return
|
|
1253
|
+
const resolved = await this.resolve(source, importer, {
|
|
1254
|
+
skipSelf: true,
|
|
1255
|
+
kind: options.kind,
|
|
1256
|
+
})
|
|
1257
|
+
if (resolved) return resolved
|
|
1258
|
+
return { id: `${PREFIX}${source}`, external: false }
|
|
1259
|
+
},
|
|
1260
|
+
load(id) {
|
|
1261
|
+
if (!id.startsWith(PREFIX)) return
|
|
1262
|
+
const source = JSON.stringify(`Cannot find module '${id.slice(PREFIX.length)}'`)
|
|
1263
|
+
return { code: `throw new Error(${source});`, moduleType: 'js' as any }
|
|
1264
|
+
},
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1246
1268
|
/**
|
|
1247
1269
|
* Guard environment-specific bare imports in native bundles.
|
|
1248
1270
|
* - server-only, client-only, web-only → throw at runtime
|
|
@@ -1315,6 +1337,203 @@ export function hmrClientNoopPlugin(): Plugin {
|
|
|
1315
1337
|
}
|
|
1316
1338
|
}
|
|
1317
1339
|
|
|
1340
|
+
function nativeIdentityAliasPlugin(platform: 'ios' | 'android'): Plugin {
|
|
1341
|
+
const packages = new Map<
|
|
1342
|
+
string,
|
|
1343
|
+
{ root: string; aliases: Record<string, unknown> } | null
|
|
1344
|
+
>()
|
|
1345
|
+
let resolver: import('rolldown/experimental').ResolverFactory | undefined
|
|
1346
|
+
const findPackage = (
|
|
1347
|
+
directory: string
|
|
1348
|
+
): { root: string; aliases: Record<string, unknown> } | null => {
|
|
1349
|
+
if (packages.has(directory)) return packages.get(directory)!
|
|
1350
|
+
let result: { root: string; aliases: Record<string, unknown> } | null = null
|
|
1351
|
+
const manifest = join(directory, 'package.json')
|
|
1352
|
+
if (existsSync(manifest)) {
|
|
1353
|
+
try {
|
|
1354
|
+
const field = JSON.parse(readFileSync(manifest, 'utf8'))['react-native']
|
|
1355
|
+
result = {
|
|
1356
|
+
root: directory,
|
|
1357
|
+
aliases: field && typeof field === 'object' ? field : {},
|
|
1358
|
+
}
|
|
1359
|
+
} catch {}
|
|
1360
|
+
} else {
|
|
1361
|
+
const parent = dirname(directory)
|
|
1362
|
+
if (parent !== directory) result = findPackage(parent)
|
|
1363
|
+
}
|
|
1364
|
+
packages.set(directory, result)
|
|
1365
|
+
return result
|
|
1366
|
+
}
|
|
1367
|
+
return {
|
|
1368
|
+
name: 'vxrn:native-identity-alias',
|
|
1369
|
+
async resolveId(source, importer) {
|
|
1370
|
+
if (!importer || !source.startsWith('.')) return
|
|
1371
|
+
const pkg = findPackage(dirname(importer))
|
|
1372
|
+
if (!pkg) return
|
|
1373
|
+
const key = `./${normalizePath(relative(pkg.root, resolve(dirname(importer), source)))}`
|
|
1374
|
+
if (pkg.aliases[key] !== key) return
|
|
1375
|
+
// A native identity mapping deliberately keeps the original implementation
|
|
1376
|
+
// even when the browser field maps it elsewhere. Avoid alias recursion.
|
|
1377
|
+
if (!resolver) {
|
|
1378
|
+
const { ResolverFactory } = await import('rolldown/experimental')
|
|
1379
|
+
resolver = new ResolverFactory({
|
|
1380
|
+
...getNativeResolveConfig(platform),
|
|
1381
|
+
aliasFields: [],
|
|
1382
|
+
})
|
|
1383
|
+
}
|
|
1384
|
+
const result = resolver.sync(dirname(importer), source)
|
|
1385
|
+
if (result.path) return result.path
|
|
1386
|
+
},
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
// export maps resolve in key order, so a package listing `browser` before
|
|
1391
|
+
// `react-native` would give native its web build. the browser fallback is for
|
|
1392
|
+
// packages without a native target: where a package's exports name both,
|
|
1393
|
+
// resolve it as metro does, without `browser`.
|
|
1394
|
+
function nativeExportsPrecedencePlugin(
|
|
1395
|
+
root: string,
|
|
1396
|
+
platform: 'ios' | 'android'
|
|
1397
|
+
): Plugin {
|
|
1398
|
+
const manifests = new Map<string, { name?: string; exports?: unknown } | null>()
|
|
1399
|
+
const readManifest = (directory: string) => {
|
|
1400
|
+
if (!manifests.has(directory)) {
|
|
1401
|
+
let manifest = null
|
|
1402
|
+
try {
|
|
1403
|
+
manifest = JSON.parse(readFileSync(join(directory, 'package.json'), 'utf8'))
|
|
1404
|
+
} catch {}
|
|
1405
|
+
manifests.set(directory, manifest)
|
|
1406
|
+
}
|
|
1407
|
+
return manifests.get(directory)
|
|
1408
|
+
}
|
|
1409
|
+
const resolvers = new Map<string, import('rolldown/experimental').ResolverFactory>()
|
|
1410
|
+
return {
|
|
1411
|
+
name: 'vxrn:native-exports-precedence',
|
|
1412
|
+
async resolveId(source, importer, options) {
|
|
1413
|
+
if (source.startsWith('.') || source.startsWith('/') || source.includes(':')) return
|
|
1414
|
+
const resolved = await this.resolve(source, importer, {
|
|
1415
|
+
skipSelf: true,
|
|
1416
|
+
kind: options.kind,
|
|
1417
|
+
})
|
|
1418
|
+
if (!resolved || resolved.external || resolved.id.startsWith('\0')) return resolved
|
|
1419
|
+
const name = source
|
|
1420
|
+
.split('/')
|
|
1421
|
+
.slice(0, source.startsWith('@') ? 2 : 1)
|
|
1422
|
+
.join('/')
|
|
1423
|
+
let directory = dirname(resolved.id)
|
|
1424
|
+
while (readManifest(directory)?.name !== name) {
|
|
1425
|
+
if (dirname(directory) === directory) return resolved
|
|
1426
|
+
directory = dirname(directory)
|
|
1427
|
+
}
|
|
1428
|
+
const exportsMap = JSON.stringify(readManifest(directory)?.exports ?? null)
|
|
1429
|
+
if (!exportsMap.includes('"react-native"') || !exportsMap.includes('"browser"')) {
|
|
1430
|
+
return resolved
|
|
1431
|
+
}
|
|
1432
|
+
const kind = options.kind === 'require-call' ? 'require' : 'import'
|
|
1433
|
+
let resolver = resolvers.get(kind)
|
|
1434
|
+
if (!resolver) {
|
|
1435
|
+
const { ResolverFactory } = await import('rolldown/experimental')
|
|
1436
|
+
resolver = new ResolverFactory({
|
|
1437
|
+
...getNativeResolveConfig(platform),
|
|
1438
|
+
conditionNames: ['react-native', kind, 'default'],
|
|
1439
|
+
})
|
|
1440
|
+
resolvers.set(kind, resolver)
|
|
1441
|
+
}
|
|
1442
|
+
const native = resolver.sync(importer ? dirname(importer) : root, source)
|
|
1443
|
+
if (!native.path || native.path === resolved.id) return resolved
|
|
1444
|
+
return this.resolve(native.path, importer, { skipSelf: true, kind: options.kind })
|
|
1445
|
+
},
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
function nativeDeclarationAliasPlugin(root: string, platform: 'ios' | 'android'): Plugin {
|
|
1450
|
+
const resolvers = new Map<string, import('rolldown/experimental').ResolverFactory>()
|
|
1451
|
+
return {
|
|
1452
|
+
name: 'vxrn:declaration-alias',
|
|
1453
|
+
async resolveId(source, importer, options) {
|
|
1454
|
+
if (source.startsWith('.') || source.startsWith('/') || source.includes(':')) return
|
|
1455
|
+
const resolved = await this.resolve(source, importer, {
|
|
1456
|
+
skipSelf: true,
|
|
1457
|
+
kind: options.kind,
|
|
1458
|
+
})
|
|
1459
|
+
if (!resolved || resolved.external || !/\.d\.[cm]?ts$/.test(resolved.id)) {
|
|
1460
|
+
return resolved
|
|
1461
|
+
}
|
|
1462
|
+
// tsconfig paths can supply types for a package without replacing its
|
|
1463
|
+
// runtime export. Resolve that package with the same native conditions,
|
|
1464
|
+
// but without the declaration alias, and preserve import/require selection.
|
|
1465
|
+
const kind = options.kind === 'require-call' ? 'require' : 'import'
|
|
1466
|
+
let resolver = resolvers.get(kind)
|
|
1467
|
+
if (!resolver) {
|
|
1468
|
+
const { ResolverFactory } = await import('rolldown/experimental')
|
|
1469
|
+
resolver = new ResolverFactory({
|
|
1470
|
+
...getNativeResolveConfig(platform),
|
|
1471
|
+
conditionNames: ['react-native', 'browser', kind, 'default'],
|
|
1472
|
+
})
|
|
1473
|
+
resolvers.set(kind, resolver)
|
|
1474
|
+
}
|
|
1475
|
+
const runtime = resolver.sync(importer ? dirname(importer) : root, source)
|
|
1476
|
+
if (!runtime.path || /\.d\.[cm]?ts$/.test(runtime.path)) {
|
|
1477
|
+
this.error(
|
|
1478
|
+
`Native import ${source} resolves to a declaration without a runtime export`
|
|
1479
|
+
)
|
|
1480
|
+
}
|
|
1481
|
+
return this.resolve(runtime.path, importer, { skipSelf: true, kind: options.kind })
|
|
1482
|
+
},
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
function nativeAssetRegistryPlugin(root: string): Plugin {
|
|
1487
|
+
let registryPath: string | null | undefined
|
|
1488
|
+
return {
|
|
1489
|
+
name: 'vxrn:asset-registry',
|
|
1490
|
+
async resolveId(source, importer) {
|
|
1491
|
+
if (
|
|
1492
|
+
source !== 'react-native/asset-registry' &&
|
|
1493
|
+
source !== '@react-native/assets-registry/registry'
|
|
1494
|
+
) {
|
|
1495
|
+
return
|
|
1496
|
+
}
|
|
1497
|
+
if (registryPath === undefined) {
|
|
1498
|
+
try {
|
|
1499
|
+
const reactNativeRoot = dirname(resolvePath('react-native/package.json', root))
|
|
1500
|
+
// RN 0.87 moved the singleton into RN and exports this file as
|
|
1501
|
+
// react-native/asset-registry. Older RN versions own it in a package.
|
|
1502
|
+
const exportedRegistry = join(reactNativeRoot, 'src/asset-registry.js')
|
|
1503
|
+
registryPath = existsSync(exportedRegistry)
|
|
1504
|
+
? exportedRegistry
|
|
1505
|
+
: resolvePath('@react-native/assets-registry/registry', reactNativeRoot)
|
|
1506
|
+
} catch {
|
|
1507
|
+
registryPath = null
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
if (registryPath) {
|
|
1511
|
+
return this.resolve(normalizePath(registryPath), importer, { skipSelf: true })
|
|
1512
|
+
}
|
|
1513
|
+
},
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
function nativePackageDedupePlugin(root: string): Plugin {
|
|
1518
|
+
// these packages share one native runtime; nested copies cannot initialize it twice.
|
|
1519
|
+
const packages = new Set([
|
|
1520
|
+
...dedupe,
|
|
1521
|
+
'react-native-worklets',
|
|
1522
|
+
'react-native-nitro-modules',
|
|
1523
|
+
])
|
|
1524
|
+
return {
|
|
1525
|
+
name: 'vxrn:native-package-dedupe',
|
|
1526
|
+
async resolveId(source) {
|
|
1527
|
+
const parts = source.split('/')
|
|
1528
|
+
const packageName = source.startsWith('@') ? parts.slice(0, 2).join('/') : parts[0]
|
|
1529
|
+
if (!packages.has(packageName)) return
|
|
1530
|
+
// use the app's package copy for contexts and subpaths, preserving the
|
|
1531
|
+
// native export conditions selected by rolldown.
|
|
1532
|
+
return this.resolve(source, join(root, 'package.json'), { skipSelf: true })
|
|
1533
|
+
},
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1318
1537
|
function reactNativeDedupePlugin(root: string): Plugin {
|
|
1319
1538
|
// package managers can install the same react native version at multiple
|
|
1320
1539
|
// physical paths. rolldown assigns each path a module identity, so resolving
|
|
@@ -1928,7 +2147,7 @@ function copyNativeAssetFiles(
|
|
|
1928
2147
|
|
|
1929
2148
|
/**
|
|
1930
2149
|
* SWC transform for Hermes compatibility.
|
|
1931
|
-
*
|
|
2150
|
+
* Lowers classes, lexical bindings, and async syntax for legacy Hermes.
|
|
1932
2151
|
* Inspired by rollipop's swc-plugin.ts.
|
|
1933
2152
|
*/
|
|
1934
2153
|
export function hermesCompatSWCPlugin(dev: boolean, sourceMaps = false): Plugin {
|
|
@@ -1940,14 +2159,14 @@ export function hermesCompatSWCPlugin(dev: boolean, sourceMaps = false): Plugin
|
|
|
1940
2159
|
if (!/\.[cm]?[jt]sx?$/.test(id)) return
|
|
1941
2160
|
if (id.includes('\0') || id.includes('virtual:')) return
|
|
1942
2161
|
// skip files that don't need transformation
|
|
1943
|
-
const hasClass =
|
|
2162
|
+
const hasClass = /\bclass(?:\s|\{)/.test(code)
|
|
1944
2163
|
const hasAsync = code.includes('async')
|
|
1945
|
-
const
|
|
1946
|
-
if (!hasClass && !hasAsync && !
|
|
2164
|
+
const hasBlockScopedBinding = /\b(?:const|let)\s/.test(code)
|
|
2165
|
+
if (!hasClass && !hasAsync && !hasBlockScopedBinding) return
|
|
1947
2166
|
let output: { code: string; map?: any } | undefined
|
|
1948
|
-
//
|
|
1949
|
-
// below
|
|
1950
|
-
if (code.length <= 500_000) {
|
|
2167
|
+
// Keep the Oxc limit for other large prebuilt files. Class, lexical binding,
|
|
2168
|
+
// and async lowering below have no size exemption for legacy Hermes.
|
|
2169
|
+
if (code.length <= 500_000 || hasClass) {
|
|
1951
2170
|
if (!oxc) oxc = await import('oxc-transform')
|
|
1952
2171
|
const lang = /\.[cm]?ts$/.test(id) ? 'ts' : id.endsWith('.tsx') ? 'tsx' : 'jsx'
|
|
1953
2172
|
const result = oxc.transformSync(id, code, {
|
|
@@ -1967,6 +2186,17 @@ export function hermesCompatSWCPlugin(dev: boolean, sourceMaps = false): Plugin
|
|
|
1967
2186
|
map: sourceMaps ? result.map : undefined,
|
|
1968
2187
|
}
|
|
1969
2188
|
}
|
|
2189
|
+
if (hasClass || hasBlockScopedBinding) {
|
|
2190
|
+
const { transformHermesClasses } = await import('@vxrn/compiler')
|
|
2191
|
+
const classes = await transformHermesClasses(output?.code ?? code, id, sourceMaps)
|
|
2192
|
+
if (classes) {
|
|
2193
|
+
if (classes.map && output?.map) {
|
|
2194
|
+
const remapping = (await import('@jridgewell/remapping')).default
|
|
2195
|
+
classes.map = remapping([classes.map, output.map], () => null)
|
|
2196
|
+
}
|
|
2197
|
+
output = classes
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
1970
2200
|
const lowered = await transformHermesAsync(output?.code ?? code, id, sourceMaps)
|
|
1971
2201
|
if (!lowered) return output
|
|
1972
2202
|
if (lowered.map && output?.map) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
1
|
+
import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
2
2
|
import { tmpdir } from 'node:os'
|
|
3
3
|
import { createServer } from 'node:http'
|
|
4
4
|
import { dirname, join } from 'node:path'
|
|
@@ -17,6 +17,49 @@ vi.mock('../utils/patches', () => ({
|
|
|
17
17
|
const here = dirname(fileURLToPath(import.meta.url))
|
|
18
18
|
const workspaceRoot = join(here, '..', '..', '..')
|
|
19
19
|
|
|
20
|
+
// an android sdk whose adb reports exactly these connected serials
|
|
21
|
+
function fakeAndroidSdk(serials: string[]) {
|
|
22
|
+
const sdk = mkdtempSync(join(tmpdir(), 'nativerun-sdk-'))
|
|
23
|
+
mkdirSync(join(sdk, 'platform-tools'))
|
|
24
|
+
const listing = ['List of devices attached', ...serials.map((serial) => `${serial}\tdevice`)]
|
|
25
|
+
writeFileSync(
|
|
26
|
+
join(sdk, 'platform-tools', 'adb'),
|
|
27
|
+
`#!/bin/sh\nprintf '%s\\n' ${listing.map((line) => `'${line}'`).join(' ')}\n`
|
|
28
|
+
)
|
|
29
|
+
chmodSync(join(sdk, 'platform-tools', 'adb'), 0o755)
|
|
30
|
+
return sdk
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function withAndroidSdk<T>(serials: string[], serial: string | undefined, run: () => Promise<T>) {
|
|
34
|
+
const sdk = fakeAndroidSdk(serials)
|
|
35
|
+
const previous = { home: process.env.ANDROID_HOME, serial: process.env.ANDROID_SERIAL }
|
|
36
|
+
process.env.ANDROID_HOME = sdk
|
|
37
|
+
if (serial) process.env.ANDROID_SERIAL = serial
|
|
38
|
+
else delete process.env.ANDROID_SERIAL
|
|
39
|
+
try {
|
|
40
|
+
return await run()
|
|
41
|
+
} finally {
|
|
42
|
+
if (previous.home === undefined) delete process.env.ANDROID_HOME
|
|
43
|
+
else process.env.ANDROID_HOME = previous.home
|
|
44
|
+
if (previous.serial === undefined) delete process.env.ANDROID_SERIAL
|
|
45
|
+
else process.env.ANDROID_SERIAL = previous.serial
|
|
46
|
+
rmSync(sdk, { recursive: true, force: true })
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function withDevServer<T>(run: (port: number) => Promise<T>) {
|
|
51
|
+
const server = createServer((req, res) => {
|
|
52
|
+
res.end(req.url === '/status' ? 'packager-status:running' : 'ok')
|
|
53
|
+
})
|
|
54
|
+
await new Promise<void>((resolve) => server.listen(0, '127.0.0.1', resolve))
|
|
55
|
+
const address = server.address()
|
|
56
|
+
try {
|
|
57
|
+
return await run(typeof address === 'object' && address ? address.port : 0)
|
|
58
|
+
} finally {
|
|
59
|
+
server.close()
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
20
63
|
describe('expo-free run commands', () => {
|
|
21
64
|
it('delegates build/install/launch to community cli with no packager', () => {
|
|
22
65
|
expect(buildNativeRunCommand({ platform: 'ios', port: 8082 })).toEqual({
|
|
@@ -32,22 +75,34 @@ describe('expo-free run commands', () => {
|
|
|
32
75
|
})
|
|
33
76
|
|
|
34
77
|
it('forwards an explicit simulator target instead of letting the cli pick', () => {
|
|
35
|
-
expect(
|
|
36
|
-
buildNativeRunCommand({ platform: 'ios', simulator: 'iPhone 16' })
|
|
37
|
-
).toEqual({
|
|
78
|
+
expect(buildNativeRunCommand({ platform: 'ios', simulator: 'iPhone 16' })).toEqual({
|
|
38
79
|
command: 'run-ios',
|
|
39
80
|
argv: ['--no-packager', '--port', '8081', '--simulator', 'iPhone 16'],
|
|
40
81
|
port: 8081,
|
|
41
82
|
})
|
|
42
|
-
expect(
|
|
43
|
-
buildNativeRunCommand({ platform: 'ios', udid: 'some-udid' })
|
|
44
|
-
).toEqual({
|
|
83
|
+
expect(buildNativeRunCommand({ platform: 'ios', udid: 'some-udid' })).toEqual({
|
|
45
84
|
command: 'run-ios',
|
|
46
85
|
argv: ['--no-packager', '--port', '8081', '--udid', 'some-udid'],
|
|
47
86
|
port: 8081,
|
|
48
87
|
})
|
|
49
88
|
})
|
|
50
89
|
|
|
90
|
+
it('targets the selected Android device rather than launching on every emulator', () => {
|
|
91
|
+
vi.stubEnv('ANDROID_SERIAL', 'emulator-5582')
|
|
92
|
+
try {
|
|
93
|
+
expect(buildNativeRunCommand({ platform: 'android', port: 8097 }).argv).toEqual([
|
|
94
|
+
'--no-packager',
|
|
95
|
+
'--port',
|
|
96
|
+
'8097',
|
|
97
|
+
'--device',
|
|
98
|
+
'emulator-5582',
|
|
99
|
+
])
|
|
100
|
+
expect(buildNativeRunCommand({ platform: 'ios' }).argv).not.toContain('--device')
|
|
101
|
+
} finally {
|
|
102
|
+
vi.unstubAllEnvs()
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
|
|
51
106
|
it('never resolves the expo cli', () => {
|
|
52
107
|
const source = readFileSync(join(here, 'nativeRun.ts'), 'utf8')
|
|
53
108
|
expect(source).not.toContain('expo/cli')
|
|
@@ -65,9 +120,10 @@ describe('expo-free run commands', () => {
|
|
|
65
120
|
const port = typeof address === 'object' && address ? address.port : 0
|
|
66
121
|
const calls: Array<{ executable: string; argv: string[] }> = []
|
|
67
122
|
const previousMetroPort = process.env.RCT_METRO_PORT
|
|
123
|
+
const root = mkdtempSync(join(tmpdir(), 'vxrn-run-no-app-cli-'))
|
|
68
124
|
try {
|
|
69
125
|
await nativeRun({
|
|
70
|
-
root
|
|
126
|
+
root,
|
|
71
127
|
platform: 'ios',
|
|
72
128
|
port,
|
|
73
129
|
spawn: (executable, argv) => {
|
|
@@ -84,6 +140,7 @@ describe('expo-free run commands', () => {
|
|
|
84
140
|
if (previousMetroPort === undefined) delete process.env.RCT_METRO_PORT
|
|
85
141
|
else process.env.RCT_METRO_PORT = previousMetroPort
|
|
86
142
|
server.close()
|
|
143
|
+
rmSync(root, { recursive: true, force: true })
|
|
87
144
|
}
|
|
88
145
|
})
|
|
89
146
|
|
|
@@ -101,18 +158,20 @@ describe('expo-free run commands', () => {
|
|
|
101
158
|
)
|
|
102
159
|
|
|
103
160
|
try {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
161
|
+
await withAndroidSdk(['emulator-5554'], undefined, async () => {
|
|
162
|
+
const launch = nativeRun({
|
|
163
|
+
root: workspaceRoot,
|
|
164
|
+
platform: 'android',
|
|
165
|
+
port,
|
|
166
|
+
spawn: (_executable, argv) => calls.push(argv),
|
|
167
|
+
})
|
|
168
|
+
await new Promise<void>((resolve) => setImmediate(resolve))
|
|
169
|
+
expect(calls).toHaveLength(0)
|
|
112
170
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
171
|
+
finishPatches?.()
|
|
172
|
+
await launch
|
|
173
|
+
expect(calls).toHaveLength(1)
|
|
174
|
+
})
|
|
116
175
|
} finally {
|
|
117
176
|
server.close()
|
|
118
177
|
}
|
|
@@ -141,14 +200,17 @@ describe('expo-free run commands', () => {
|
|
|
141
200
|
}
|
|
142
201
|
})
|
|
143
202
|
|
|
144
|
-
it('resolves the ios bundle id from
|
|
203
|
+
it('resolves the ios bundle id from the prebuilt ios project', () => {
|
|
145
204
|
const dir = mkdtempSync(join(tmpdir(), 'nativerun-'))
|
|
146
205
|
try {
|
|
147
206
|
expect(resolveIosBundleId(dir)).toBeNull()
|
|
148
|
-
|
|
149
|
-
expect(resolveIosBundleId(dir)).toBe('dev.example.app')
|
|
150
|
-
writeFileSync(join(dir, 'app.json'), 'not json')
|
|
207
|
+
mkdirSync(join(dir, 'ios', 'Example.xcodeproj'), { recursive: true })
|
|
151
208
|
expect(resolveIosBundleId(dir)).toBeNull()
|
|
209
|
+
writeFileSync(
|
|
210
|
+
join(dir, 'ios', 'Example.xcodeproj', 'project.pbxproj'),
|
|
211
|
+
'buildSettings = {\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = dev.example.app;\n\t\t\t};'
|
|
212
|
+
)
|
|
213
|
+
expect(resolveIosBundleId(dir)).toBe('dev.example.app')
|
|
152
214
|
} finally {
|
|
153
215
|
rmSync(dir, { recursive: true, force: true })
|
|
154
216
|
}
|
|
@@ -166,4 +228,44 @@ describe('expo-free run commands', () => {
|
|
|
166
228
|
})
|
|
167
229
|
).rejects.toThrow(/No dev server running/)
|
|
168
230
|
})
|
|
231
|
+
|
|
232
|
+
it('fails before launching when no android device is connected', async () => {
|
|
233
|
+
const spawn = vi.fn()
|
|
234
|
+
await withDevServer((port) =>
|
|
235
|
+
withAndroidSdk([], undefined, () =>
|
|
236
|
+
expect(
|
|
237
|
+
nativeRun({ root: workspaceRoot, platform: 'android', port, spawn })
|
|
238
|
+
).rejects.toThrow(/No Android device or emulator is connected/)
|
|
239
|
+
)
|
|
240
|
+
)
|
|
241
|
+
expect(spawn).not.toHaveBeenCalled()
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
it('fails before launching when the chosen android serial is not connected', async () => {
|
|
245
|
+
const spawn = vi.fn()
|
|
246
|
+
await withDevServer((port) =>
|
|
247
|
+
withAndroidSdk(['emulator-5554'], 'emulator-5556', () =>
|
|
248
|
+
expect(
|
|
249
|
+
nativeRun({ root: workspaceRoot, platform: 'android', port, spawn })
|
|
250
|
+
).rejects.toThrow(/emulator-5556 is not connected \(connected: emulator-5554\)/)
|
|
251
|
+
)
|
|
252
|
+
)
|
|
253
|
+
expect(spawn).not.toHaveBeenCalled()
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
it('launches on the chosen android serial when it is connected', async () => {
|
|
257
|
+
const calls: string[][] = []
|
|
258
|
+
await withDevServer((port) =>
|
|
259
|
+
withAndroidSdk(['emulator-5554', 'emulator-5556'], 'emulator-5556', () =>
|
|
260
|
+
nativeRun({
|
|
261
|
+
root: workspaceRoot,
|
|
262
|
+
platform: 'android',
|
|
263
|
+
port,
|
|
264
|
+
spawn: (_executable, argv) => calls.push(argv),
|
|
265
|
+
})
|
|
266
|
+
)
|
|
267
|
+
)
|
|
268
|
+
expect(calls).toHaveLength(1)
|
|
269
|
+
expect(calls[0]).toEqual(expect.arrayContaining(['--device', 'emulator-5556']))
|
|
270
|
+
})
|
|
169
271
|
})
|