@react-native-native/nativ-fabric 0.1.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/NativFabric.podspec +41 -0
- package/android/build.gradle +128 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/cpp/CMakeLists.txt +59 -0
- package/android/src/main/cpp/NativBindingsInstaller.cpp +393 -0
- package/android/src/main/cpp/NativRuntime.cpp +508 -0
- package/android/src/main/java/com/nativfabric/ComposeHost.kt +26 -0
- package/android/src/main/java/com/nativfabric/NativContainerPackage.kt +35 -0
- package/android/src/main/java/com/nativfabric/NativContainerView.kt +51 -0
- package/android/src/main/java/com/nativfabric/NativContainerViewManager.kt +62 -0
- package/android/src/main/java/com/nativfabric/NativRuntime.kt +201 -0
- package/android/src/main/java/com/nativfabric/NativRuntimeModule.kt +37 -0
- package/android/src/main/java/com/nativfabric/compose/ComposeWrappers.kt +45 -0
- package/app.plugin.js +159 -0
- package/expo-module.config.json +6 -0
- package/ios/NativContainerComponentView.mm +137 -0
- package/ios/NativRuntime.h +36 -0
- package/ios/NativRuntime.mm +549 -0
- package/metro/Nativ.h +126 -0
- package/metro/compilers/android-compiler.js +339 -0
- package/metro/compilers/dylib-compiler.js +474 -0
- package/metro/compilers/kotlin-compiler.js +632 -0
- package/metro/compilers/rust-compiler.js +722 -0
- package/metro/compilers/static-compiler.js +1118 -0
- package/metro/compilers/swift-compiler.js +363 -0
- package/metro/extractors/cpp-ast-extractor.js +126 -0
- package/metro/extractors/kotlin-extractor.js +125 -0
- package/metro/extractors/rust-extractor.js +118 -0
- package/metro/index.js +236 -0
- package/metro/transformer.js +649 -0
- package/metro/utils/bridge-generator.js +50 -0
- package/metro/utils/compile-commands.js +104 -0
- package/metro/utils/cpp-daemon.js +344 -0
- package/metro/utils/dts-generator.js +32 -0
- package/metro/utils/include-resolver.js +73 -0
- package/metro/utils/kotlin-daemon.js +394 -0
- package/metro/utils/type-mapper.js +63 -0
- package/package.json +43 -0
- package/react-native.config.js +13 -0
- package/src/NativContainerNativeComponent.ts +9 -0
- package/src/NativeNativRuntime.ts +8 -0
- package/src/index.ts +4 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
package com.nativfabric
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import com.nativfabric.BuildConfig
|
|
5
|
+
import android.view.View
|
|
6
|
+
import android.view.ViewGroup
|
|
7
|
+
import dalvik.system.DexClassLoader
|
|
8
|
+
import java.io.File
|
|
9
|
+
import java.lang.reflect.Method
|
|
10
|
+
import java.util.concurrent.ConcurrentHashMap
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* NativRuntime — native function registry, .so loader, and .dex loader.
|
|
14
|
+
* Android equivalent of iOS's NativRuntime.
|
|
15
|
+
*
|
|
16
|
+
* Native (.so): render functions and sync functions registered via JNI
|
|
17
|
+
* from loaded .so files (__attribute__((constructor))).
|
|
18
|
+
*
|
|
19
|
+
* Kotlin (.dex): loaded via DexClassLoader, dispatched via reflection.
|
|
20
|
+
*/
|
|
21
|
+
object NativRuntime {
|
|
22
|
+
|
|
23
|
+
// Props snapshot: plain key-value maps (no JNI references)
|
|
24
|
+
data class PropsSnapshot(
|
|
25
|
+
val strings: Map<String, String> = emptyMap(),
|
|
26
|
+
val numbers: Map<String, Double> = emptyMap(),
|
|
27
|
+
val bools: Map<String, Boolean> = emptyMap(),
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
// Component props store
|
|
31
|
+
private val propsStore = ConcurrentHashMap<String, PropsSnapshot>()
|
|
32
|
+
|
|
33
|
+
// Loaded .so libraries
|
|
34
|
+
private val loadedLibs = ConcurrentHashMap<String, Boolean>()
|
|
35
|
+
|
|
36
|
+
// Loaded Kotlin modules: moduleId → dispatch Method
|
|
37
|
+
private val kotlinDispatch = ConcurrentHashMap<String, Method>()
|
|
38
|
+
|
|
39
|
+
// Loaded Kotlin Compose renderers: componentId → render Method
|
|
40
|
+
private val kotlinRenderers = ConcurrentHashMap<String, Method>()
|
|
41
|
+
|
|
42
|
+
// Application context (set from NativRuntimeModule)
|
|
43
|
+
var appContext: Context? = null
|
|
44
|
+
|
|
45
|
+
fun setComponentProps(componentId: String, props: PropsSnapshot) {
|
|
46
|
+
propsStore[componentId] = props
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
fun getComponentProps(componentId: String): PropsSnapshot? {
|
|
50
|
+
return propsStore[componentId]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
fun tryRender(componentId: String, view: View, width: Float, height: Float) {
|
|
54
|
+
// Check Kotlin renderers first (View-based components from .dex)
|
|
55
|
+
val renderer = kotlinRenderers[componentId]
|
|
56
|
+
if (renderer != null && view is ViewGroup) {
|
|
57
|
+
try {
|
|
58
|
+
val props = propsStore[componentId]
|
|
59
|
+
val propsMap = mutableMapOf<String, Any?>()
|
|
60
|
+
props?.strings?.forEach { (k, v) -> propsMap[k] = v }
|
|
61
|
+
props?.numbers?.forEach { (k, v) -> propsMap[k] = v }
|
|
62
|
+
props?.bools?.forEach { (k, v) -> propsMap[k] = v }
|
|
63
|
+
renderer.invoke(null, view, propsMap)
|
|
64
|
+
return
|
|
65
|
+
} catch (e: Exception) {
|
|
66
|
+
android.util.Log.e("NativRuntime", "Kotlin render failed: ${e.message}", e)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Fall back to native JNI render
|
|
71
|
+
val props = propsStore[componentId]
|
|
72
|
+
nativeTryRender(componentId, view, width, height,
|
|
73
|
+
props?.strings ?: emptyMap(),
|
|
74
|
+
props?.numbers ?: emptyMap(),
|
|
75
|
+
props?.bools ?: emptyMap())
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
fun loadLibrary(path: String): Boolean {
|
|
79
|
+
return try {
|
|
80
|
+
System.load(path)
|
|
81
|
+
loadedLibs[path] = true
|
|
82
|
+
true
|
|
83
|
+
} catch (e: UnsatisfiedLinkError) {
|
|
84
|
+
android.util.Log.e("NativRuntime", "Failed to load $path: ${e.message}")
|
|
85
|
+
false
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Load a .dex file and register its module for dispatch.
|
|
91
|
+
* The .dex must contain a class com.nativfabric.generated.NativModule_<moduleId>
|
|
92
|
+
* with a static dispatch(String, String): String method.
|
|
93
|
+
*/
|
|
94
|
+
fun loadDex(dexPath: String, moduleId: String): Boolean {
|
|
95
|
+
// Production: Kotlin is compiled into the APK by Gradle — no DexClassLoader needed
|
|
96
|
+
if (!BuildConfig.DEBUG) return false
|
|
97
|
+
|
|
98
|
+
val ctx = appContext
|
|
99
|
+
if (ctx == null) {
|
|
100
|
+
android.util.Log.e("NativRuntime", "loadDex: appContext is null!")
|
|
101
|
+
return false
|
|
102
|
+
}
|
|
103
|
+
return try {
|
|
104
|
+
val dexFile = File(dexPath)
|
|
105
|
+
val optimizedDir = File(ctx.cacheDir, "nativ_dex_opt")
|
|
106
|
+
optimizedDir.mkdirs()
|
|
107
|
+
|
|
108
|
+
// Android 14+ requires dex files to be read-only
|
|
109
|
+
dexFile.setReadOnly()
|
|
110
|
+
|
|
111
|
+
val loader = DexClassLoader(
|
|
112
|
+
dexFile.absolutePath,
|
|
113
|
+
optimizedDir.absolutePath,
|
|
114
|
+
null,
|
|
115
|
+
ctx.classLoader
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
val className = "com.nativfabric.generated.NativModule_$moduleId"
|
|
119
|
+
val clazz = loader.loadClass(className)
|
|
120
|
+
|
|
121
|
+
// Check for dispatch method (function module)
|
|
122
|
+
try {
|
|
123
|
+
val dispatch = clazz.getMethod("dispatch", String::class.java, String::class.java)
|
|
124
|
+
kotlinDispatch[moduleId] = dispatch
|
|
125
|
+
android.util.Log.i("NativRuntime", "Loaded Kotlin module: $moduleId")
|
|
126
|
+
} catch (_: NoSuchMethodException) {}
|
|
127
|
+
|
|
128
|
+
// Check for render method (Compose component)
|
|
129
|
+
try {
|
|
130
|
+
val render = clazz.getMethod("render", ViewGroup::class.java, Map::class.java)
|
|
131
|
+
val componentId = "nativ.$moduleId"
|
|
132
|
+
kotlinRenderers[componentId] = render
|
|
133
|
+
android.util.Log.i("NativRuntime", "Loaded Kotlin component: $componentId")
|
|
134
|
+
} catch (_: NoSuchMethodException) {}
|
|
135
|
+
|
|
136
|
+
true
|
|
137
|
+
} catch (e: Exception) {
|
|
138
|
+
android.util.Log.e("NativRuntime", "loadDex failed: ${e.message}")
|
|
139
|
+
false
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Call a Kotlin function loaded from .dex.
|
|
145
|
+
*/
|
|
146
|
+
fun callKotlin(moduleId: String, fnName: String, argsJson: String): String? {
|
|
147
|
+
val dispatch = kotlinDispatch[moduleId] ?: return null
|
|
148
|
+
return try {
|
|
149
|
+
dispatch.invoke(null, fnName, argsJson) as? String
|
|
150
|
+
} catch (e: Exception) {
|
|
151
|
+
android.util.Log.e("NativRuntime", "callKotlin failed: $moduleId::$fnName: ${e.message}")
|
|
152
|
+
null
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// JNI functions — implemented in C++ (libnativruntime.so)
|
|
157
|
+
external fun nativeInit()
|
|
158
|
+
external fun nativeTryRender(
|
|
159
|
+
componentId: String, view: View, width: Float, height: Float,
|
|
160
|
+
strings: Map<String, String>, numbers: Map<String, Double>, bools: Map<String, Boolean>
|
|
161
|
+
)
|
|
162
|
+
external fun nativeCallSync(moduleId: String, fnName: String, argsJson: String): String?
|
|
163
|
+
|
|
164
|
+
init {
|
|
165
|
+
try {
|
|
166
|
+
System.loadLibrary("nativruntime")
|
|
167
|
+
nativeInit()
|
|
168
|
+
} catch (e: UnsatisfiedLinkError) {
|
|
169
|
+
android.util.Log.w("NativRuntime", "nativruntime not loaded: ${e.message}")
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Production: register Kotlin modules compiled into the APK by Gradle.
|
|
173
|
+
// In dev mode, modules are loaded via DexClassLoader (loadDex).
|
|
174
|
+
if (!BuildConfig.DEBUG) {
|
|
175
|
+
try {
|
|
176
|
+
Class.forName("com.nativfabric.generated.NativModuleRegistry")
|
|
177
|
+
.getMethod("ensure")
|
|
178
|
+
.invoke(null)
|
|
179
|
+
android.util.Log.i("NativRuntime", "Production: Kotlin module registry loaded")
|
|
180
|
+
} catch (e: ClassNotFoundException) {
|
|
181
|
+
// No generated modules — that's fine
|
|
182
|
+
} catch (e: Exception) {
|
|
183
|
+
android.util.Log.w("NativRuntime", "Kotlin registry failed: ${e.message}")
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Register a Kotlin dispatch method (production — called by generated NativModuleRegistry) */
|
|
189
|
+
@JvmStatic
|
|
190
|
+
fun registerKotlinDispatch(moduleId: String, dispatch: java.lang.reflect.Method) {
|
|
191
|
+
kotlinDispatch[moduleId] = dispatch
|
|
192
|
+
android.util.Log.i("NativRuntime", "Production: registered Kotlin dispatch: $moduleId")
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Register a Kotlin renderer method (production — called by generated NativModuleRegistry) */
|
|
196
|
+
@JvmStatic
|
|
197
|
+
fun registerKotlinRenderer(componentId: String, render: java.lang.reflect.Method) {
|
|
198
|
+
kotlinRenderers[componentId] = render
|
|
199
|
+
android.util.Log.i("NativRuntime", "Production: registered Kotlin renderer: $componentId")
|
|
200
|
+
}
|
|
201
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
package com.nativfabric
|
|
2
|
+
|
|
3
|
+
import com.facebook.proguard.annotations.DoNotStrip
|
|
4
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
5
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
6
|
+
import com.facebook.react.turbomodule.core.interfaces.BindingsInstallerHolder
|
|
7
|
+
import com.facebook.react.turbomodule.core.interfaces.TurboModuleWithJSIBindings
|
|
8
|
+
import com.facebook.fbreact.specs.NativeNativRuntimeSpec
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* TurboModule that installs global.__nativ JSI bindings when loaded.
|
|
12
|
+
* Uses TurboModuleWithJSIBindings — RN calls installJSIBindingsWithRuntime
|
|
13
|
+
* automatically when the module is first accessed from JS.
|
|
14
|
+
*
|
|
15
|
+
* No BindingsInstaller on MainApplication.kt needed.
|
|
16
|
+
* No dependency on the old expo-ferrum module.
|
|
17
|
+
*/
|
|
18
|
+
@DoNotStrip
|
|
19
|
+
@ReactModule(name = NativRuntimeModule.NAME)
|
|
20
|
+
class NativRuntimeModule(context: ReactApplicationContext) :
|
|
21
|
+
NativeNativRuntimeSpec(context), TurboModuleWithJSIBindings {
|
|
22
|
+
|
|
23
|
+
override fun getConstants(): Map<String, Any> = emptyMap()
|
|
24
|
+
|
|
25
|
+
@DoNotStrip
|
|
26
|
+
external override fun getBindingsInstaller(): BindingsInstallerHolder
|
|
27
|
+
|
|
28
|
+
override fun getName(): String = NAME
|
|
29
|
+
|
|
30
|
+
companion object {
|
|
31
|
+
const val NAME = "NativRuntime"
|
|
32
|
+
|
|
33
|
+
init {
|
|
34
|
+
System.loadLibrary("nativruntime")
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Non-inline wrappers for inline Compose layout functions.
|
|
2
|
+
// Compiled WITH the Compose plugin so call sites get transformed correctly.
|
|
3
|
+
// User code imports these instead of the inline originals.
|
|
4
|
+
package com.nativfabric.compose
|
|
5
|
+
|
|
6
|
+
import androidx.compose.runtime.Composable
|
|
7
|
+
import androidx.compose.ui.Alignment
|
|
8
|
+
import androidx.compose.ui.Modifier
|
|
9
|
+
import androidx.compose.foundation.layout.Arrangement
|
|
10
|
+
|
|
11
|
+
@Composable
|
|
12
|
+
fun Box(
|
|
13
|
+
modifier: Modifier = Modifier,
|
|
14
|
+
contentAlignment: Alignment = Alignment.TopStart,
|
|
15
|
+
propagateMinConstraints: Boolean = false,
|
|
16
|
+
content: @Composable androidx.compose.foundation.layout.BoxScope.() -> Unit
|
|
17
|
+
) {
|
|
18
|
+
androidx.compose.foundation.layout.Box(modifier, contentAlignment, propagateMinConstraints, content)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@Composable
|
|
22
|
+
fun Column(
|
|
23
|
+
modifier: Modifier = Modifier,
|
|
24
|
+
verticalArrangement: Arrangement.Vertical = Arrangement.Top,
|
|
25
|
+
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
|
|
26
|
+
content: @Composable androidx.compose.foundation.layout.ColumnScope.() -> Unit
|
|
27
|
+
) {
|
|
28
|
+
androidx.compose.foundation.layout.Column(modifier, verticalArrangement, horizontalAlignment, content)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@Composable
|
|
32
|
+
fun Row(
|
|
33
|
+
modifier: Modifier = Modifier,
|
|
34
|
+
horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
|
|
35
|
+
verticalAlignment: Alignment.Vertical = Alignment.Top,
|
|
36
|
+
content: @Composable androidx.compose.foundation.layout.RowScope.() -> Unit
|
|
37
|
+
) {
|
|
38
|
+
androidx.compose.foundation.layout.Row(modifier, horizontalArrangement, verticalAlignment, content)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@Composable
|
|
42
|
+
fun Spacer(modifier: Modifier) {
|
|
43
|
+
androidx.compose.foundation.layout.Spacer(modifier)
|
|
44
|
+
}
|
|
45
|
+
|
package/app.plugin.js
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React Native Native — Expo config plugin.
|
|
3
|
+
*
|
|
4
|
+
* Creates a CocoaPods pod for user native code with three fixed-name files:
|
|
5
|
+
* - bridges/nativ_bridges.mm — all C++/ObjC++ bridges + Swift/Rust registration
|
|
6
|
+
* - bridges/nativ_bridges.swift — all Swift source + @_cdecl wrappers
|
|
7
|
+
* - bridges/libnativ_user.a — unified Rust static library
|
|
8
|
+
*
|
|
9
|
+
* Stubs are created at prebuild time (so pod install picks them up).
|
|
10
|
+
* A :before_compile script phase regenerates them with real content on Release builds.
|
|
11
|
+
* Debug builds skip the script — all native code loads via Metro dylibs.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const { withDangerousMod, withXcodeProject } = require('@expo/config-plugins');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
|
|
18
|
+
function withReactNativeNative(config) {
|
|
19
|
+
config = withNativPod(config);
|
|
20
|
+
config = withPodfileEntry(config);
|
|
21
|
+
config = withForceLoad(config);
|
|
22
|
+
return config;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ─── Generate .nativ/ with podspec + stub bridge files ──────────────────
|
|
26
|
+
|
|
27
|
+
function withNativPod(config) {
|
|
28
|
+
return withDangerousMod(config, ['ios', (config) => {
|
|
29
|
+
const projectRoot = config.modRequest.projectRoot;
|
|
30
|
+
const pkgDir = path.join(projectRoot, '.nativ');
|
|
31
|
+
const bridgesDir = path.join(pkgDir, 'bridges');
|
|
32
|
+
fs.mkdirSync(bridgesDir, { recursive: true });
|
|
33
|
+
|
|
34
|
+
// Resolve paths relative to .nativ/ for the podspec
|
|
35
|
+
const nativFabricDir = path.resolve(__dirname);
|
|
36
|
+
const relCompiler = path.relative(pkgDir, path.join(nativFabricDir, 'metro/compilers/static-compiler.js'));
|
|
37
|
+
const relMetroDir = path.relative(pkgDir, path.join(nativFabricDir, 'metro'));
|
|
38
|
+
|
|
39
|
+
// ── Stub source files (valid code that compiles to nothing) ──
|
|
40
|
+
fs.writeFileSync(path.join(bridgesDir, 'nativ_bridges.mm'),
|
|
41
|
+
'// Stub — real bridges generated at Release build time by React Native Native\n');
|
|
42
|
+
fs.writeFileSync(path.join(bridgesDir, 'nativ_bridges.swift'),
|
|
43
|
+
'// Stub — real bridges generated at Release build time by React Native Native\nimport Foundation\n');
|
|
44
|
+
// Empty ar archive (valid .a that links to nothing)
|
|
45
|
+
fs.writeFileSync(path.join(bridgesDir, 'libnativ_user.a'), '!<arch>\n');
|
|
46
|
+
|
|
47
|
+
// ── Build script (called by CocoaPods script phase) ──
|
|
48
|
+
fs.writeFileSync(path.join(pkgDir, 'build-bridges.sh'),
|
|
49
|
+
`#!/bin/bash
|
|
50
|
+
set -euo pipefail
|
|
51
|
+
|
|
52
|
+
if [ "\${CONFIGURATION}" = "Debug" ]; then
|
|
53
|
+
echo "[nativ] Debug build — skipping static compilation"
|
|
54
|
+
exit 0
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
echo "[nativ] Building native bridges for Release..."
|
|
58
|
+
node "$PODS_TARGET_SRCROOT/${relCompiler}" \\
|
|
59
|
+
--platform ios \\
|
|
60
|
+
--root "$PODS_TARGET_SRCROOT/.." \\
|
|
61
|
+
--output "$PODS_TARGET_SRCROOT/bridges"
|
|
62
|
+
`);
|
|
63
|
+
fs.chmodSync(path.join(pkgDir, 'build-bridges.sh'), 0o755);
|
|
64
|
+
|
|
65
|
+
// ── Podspec ──
|
|
66
|
+
fs.writeFileSync(path.join(pkgDir, 'ReactNativeNativeUserCode.podspec'),
|
|
67
|
+
`Pod::Spec.new do |s|
|
|
68
|
+
s.name = 'ReactNativeNativeUserCode'
|
|
69
|
+
s.version = '0.1.0'
|
|
70
|
+
s.summary = 'User native code — React Native Native'
|
|
71
|
+
s.homepage = 'https://react-native-native.dev'
|
|
72
|
+
s.license = { :type => 'MIT' }
|
|
73
|
+
s.author = 'Generated'
|
|
74
|
+
s.source = { :git => '' }
|
|
75
|
+
s.platforms = { :ios => '15.1' }
|
|
76
|
+
s.static_framework = true
|
|
77
|
+
|
|
78
|
+
s.source_files = ['bridges/nativ_bridges.mm', 'bridges/nativ_bridges.swift']
|
|
79
|
+
s.vendored_libraries = ['bridges/libnativ_user.a']
|
|
80
|
+
s.preserve_paths = ['bridges/*']
|
|
81
|
+
|
|
82
|
+
s.dependency 'NativFabric'
|
|
83
|
+
|
|
84
|
+
s.pod_target_xcconfig = {
|
|
85
|
+
'CLANG_CXX_LANGUAGE_STANDARD' => 'c++17',
|
|
86
|
+
'HEADER_SEARCH_PATHS' => '"$(PODS_TARGET_SRCROOT)/${relMetroDir}"',
|
|
87
|
+
'OTHER_LDFLAGS' => '$(inherited) -lc++',
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
s.script_phase = {
|
|
91
|
+
:name => 'Build Native Bridges',
|
|
92
|
+
:script => 'bash "$PODS_TARGET_SRCROOT/build-bridges.sh"',
|
|
93
|
+
:execution_position => :before_compile,
|
|
94
|
+
}
|
|
95
|
+
end
|
|
96
|
+
`);
|
|
97
|
+
|
|
98
|
+
return config;
|
|
99
|
+
}]);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ─── Add pod to Podfile ─────────────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
function withPodfileEntry(config) {
|
|
105
|
+
return withDangerousMod(config, ['ios', (config) => {
|
|
106
|
+
const projectRoot = config.modRequest.projectRoot;
|
|
107
|
+
const podfilePath = path.join(projectRoot, 'ios', 'Podfile');
|
|
108
|
+
|
|
109
|
+
if (!fs.existsSync(podfilePath)) return config;
|
|
110
|
+
|
|
111
|
+
let podfile = fs.readFileSync(podfilePath, 'utf8');
|
|
112
|
+
const podLine = " pod 'ReactNativeNativeUserCode', :path => File.join(__dir__, '..', '.nativ')";
|
|
113
|
+
|
|
114
|
+
if (!podfile.includes('ReactNativeNativeUserCode')) {
|
|
115
|
+
// Insert after use_expo_modules! (standard Expo Podfile location)
|
|
116
|
+
const insertIdx = podfile.indexOf('use_expo_modules!');
|
|
117
|
+
if (insertIdx !== -1) {
|
|
118
|
+
const lineEnd = podfile.indexOf('\n', insertIdx);
|
|
119
|
+
podfile = podfile.slice(0, lineEnd + 1) + podLine + '\n' + podfile.slice(lineEnd + 1);
|
|
120
|
+
} else {
|
|
121
|
+
// Fallback: insert before the last 'end' in the target block
|
|
122
|
+
const lastEnd = podfile.lastIndexOf('\nend');
|
|
123
|
+
if (lastEnd !== -1) {
|
|
124
|
+
podfile = podfile.slice(0, lastEnd) + '\n' + podLine + podfile.slice(lastEnd);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
fs.writeFileSync(podfilePath, podfile);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return config;
|
|
131
|
+
}]);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ─── Force-load constructors from static libraries in Release ───────────
|
|
135
|
+
|
|
136
|
+
function withForceLoad(config) {
|
|
137
|
+
return withXcodeProject(config, (config) => {
|
|
138
|
+
const xcodeProject = config.modResults;
|
|
139
|
+
const buildSettings = xcodeProject.pbxXCBuildConfigurationSection();
|
|
140
|
+
|
|
141
|
+
for (const key of Object.keys(buildSettings)) {
|
|
142
|
+
const cfg = buildSettings[key];
|
|
143
|
+
if (typeof cfg === 'object' && cfg.name === 'Release' && cfg.buildSettings) {
|
|
144
|
+
const flags = cfg.buildSettings.OTHER_LDFLAGS || ['$(inherited)'];
|
|
145
|
+
if (!flags.includes('-all_load')) {
|
|
146
|
+
if (typeof flags === 'string') {
|
|
147
|
+
cfg.buildSettings.OTHER_LDFLAGS = [flags, '-all_load'];
|
|
148
|
+
} else {
|
|
149
|
+
flags.push('-all_load');
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return config;
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
module.exports = withReactNativeNative;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// NativContainerComponentView — Fabric component that hosts native views
|
|
2
|
+
// rendered by Rust/C++/Swift/ObjC++ via react-native-native.
|
|
3
|
+
//
|
|
4
|
+
// Uses self.contentView (RCTViewComponentView built-in) to isolate our
|
|
5
|
+
// rendering from Fabric's own view management. Fabric manages `self`,
|
|
6
|
+
// we manage `_contentView`. No background color conflicts.
|
|
7
|
+
|
|
8
|
+
#import <React/RCTViewComponentView.h>
|
|
9
|
+
#import <React/RCTComponentViewFactory.h>
|
|
10
|
+
|
|
11
|
+
#include "react/renderer/components/NativFabricSpec/ComponentDescriptors.h"
|
|
12
|
+
#include "react/renderer/components/NativFabricSpec/EventEmitters.h"
|
|
13
|
+
#include "react/renderer/components/NativFabricSpec/Props.h"
|
|
14
|
+
#include "react/renderer/components/NativFabricSpec/RCTComponentViewHelpers.h"
|
|
15
|
+
|
|
16
|
+
#import "NativRuntime.h"
|
|
17
|
+
|
|
18
|
+
using namespace facebook::react;
|
|
19
|
+
|
|
20
|
+
@interface NativContainerComponentView : RCTViewComponentView
|
|
21
|
+
@end
|
|
22
|
+
|
|
23
|
+
@implementation NativContainerComponentView {
|
|
24
|
+
UIView *_contentView;
|
|
25
|
+
std::string _componentId;
|
|
26
|
+
std::string _propsJson;
|
|
27
|
+
BOOL _mounted;
|
|
28
|
+
CGSize _lastRenderedSize;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
+ (void)load {
|
|
32
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
33
|
+
[[RCTComponentViewFactory currentComponentViewFactory]
|
|
34
|
+
registerComponentViewClass:NativContainerComponentView.class];
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
+ (ComponentDescriptorProvider)componentDescriptorProvider {
|
|
39
|
+
return concreteComponentDescriptorProvider<NativContainerComponentDescriptor>();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
- (instancetype)initWithFrame:(CGRect)frame {
|
|
43
|
+
if (self = [super initWithFrame:frame]) {
|
|
44
|
+
static const auto defaultProps = std::make_shared<const NativContainerProps>();
|
|
45
|
+
_props = defaultProps;
|
|
46
|
+
|
|
47
|
+
_contentView = [[UIView alloc] init];
|
|
48
|
+
_contentView.clipsToBounds = YES;
|
|
49
|
+
self.contentView = _contentView;
|
|
50
|
+
|
|
51
|
+
_mounted = NO;
|
|
52
|
+
}
|
|
53
|
+
return self;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
- (void)updateProps:(const Props::Shared &)props
|
|
57
|
+
oldProps:(const Props::Shared &)oldProps {
|
|
58
|
+
auto const &newProps = *std::static_pointer_cast<NativContainerProps const>(props);
|
|
59
|
+
|
|
60
|
+
bool componentChanged = (_componentId != newProps.componentId);
|
|
61
|
+
bool propsChanged = (_propsJson != newProps.propsJson);
|
|
62
|
+
|
|
63
|
+
_componentId = newProps.componentId;
|
|
64
|
+
_propsJson = newProps.propsJson;
|
|
65
|
+
|
|
66
|
+
if (componentChanged) {
|
|
67
|
+
_mounted = NO;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
[super updateProps:props oldProps:oldProps];
|
|
71
|
+
|
|
72
|
+
if ((componentChanged || propsChanged) && !_componentId.empty()) {
|
|
73
|
+
CGSize size = _contentView.bounds.size;
|
|
74
|
+
if (!_mounted && size.width > 0 && size.height > 0) {
|
|
75
|
+
[self _fullRender];
|
|
76
|
+
} else if (_mounted) {
|
|
77
|
+
[self _updateRender];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
- (void)layoutSubviews {
|
|
83
|
+
[super layoutSubviews];
|
|
84
|
+
|
|
85
|
+
// Sync contentView frame to self
|
|
86
|
+
_contentView.frame = self.bounds;
|
|
87
|
+
|
|
88
|
+
CGSize size = _contentView.bounds.size;
|
|
89
|
+
if (!_componentId.empty() && size.width > 0 && size.height > 0) {
|
|
90
|
+
if (!_mounted) {
|
|
91
|
+
_lastRenderedSize = size;
|
|
92
|
+
[self _fullRender];
|
|
93
|
+
} else if (!CGSizeEqualToSize(size, _lastRenderedSize)) {
|
|
94
|
+
_lastRenderedSize = size;
|
|
95
|
+
[self _updateRender];
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
- (void)_fullRender {
|
|
101
|
+
[self _clearContentView];
|
|
102
|
+
extern const char* nativ_try_render(const char*, void*, float, float);
|
|
103
|
+
const char* result = nativ_try_render(
|
|
104
|
+
_componentId.c_str(), (__bridge void*)_contentView,
|
|
105
|
+
_contentView.bounds.size.width, _contentView.bounds.size.height);
|
|
106
|
+
if (result) {
|
|
107
|
+
_mounted = YES;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
- (void)_updateRender {
|
|
112
|
+
[self _clearContentView];
|
|
113
|
+
extern const char* nativ_try_render(const char*, void*, float, float);
|
|
114
|
+
nativ_try_render(
|
|
115
|
+
_componentId.c_str(), (__bridge void*)_contentView,
|
|
116
|
+
_contentView.bounds.size.width, _contentView.bounds.size.height);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
- (void)_clearContentView {
|
|
120
|
+
for (UIView *sub in [_contentView.subviews copy]) {
|
|
121
|
+
[sub removeFromSuperview];
|
|
122
|
+
}
|
|
123
|
+
for (CALayer *sub in [_contentView.layer.sublayers copy]) {
|
|
124
|
+
[sub removeFromSuperlayer];
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
- (void)prepareForRecycle {
|
|
129
|
+
[super prepareForRecycle];
|
|
130
|
+
_componentId.clear();
|
|
131
|
+
_propsJson.clear();
|
|
132
|
+
_mounted = NO;
|
|
133
|
+
_lastRenderedSize = CGSizeZero;
|
|
134
|
+
[self _clearContentView];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
@end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
// C ABI function types
|
|
4
|
+
typedef const char* (*NativSyncFn)(const char*);
|
|
5
|
+
typedef void (*NativAsyncFn)(const char*, void (*)(const char*), void (*)(const char*, const char*));
|
|
6
|
+
typedef void (*NativRenderFn)(void*, float, float, void*, void*);
|
|
7
|
+
|
|
8
|
+
#ifdef __cplusplus
|
|
9
|
+
extern "C" {
|
|
10
|
+
#endif
|
|
11
|
+
|
|
12
|
+
void nativ_register_sync(const char* moduleId, const char* fnName, NativSyncFn fn);
|
|
13
|
+
void nativ_register_async(const char* moduleId, const char* fnName, NativAsyncFn fn);
|
|
14
|
+
void nativ_register_render(const char* componentId, NativRenderFn fn);
|
|
15
|
+
const char* nativ_try_render(const char* componentId, void* view, float w, float h);
|
|
16
|
+
|
|
17
|
+
// JSI value access (C wrappers for Rust/Swift)
|
|
18
|
+
const char* nativ_jsi_get_string(void* runtime, void* object, const char* prop_name);
|
|
19
|
+
double nativ_jsi_get_number(void* runtime, void* object, const char* prop_name);
|
|
20
|
+
int nativ_jsi_get_bool(void* runtime, void* object, const char* prop_name);
|
|
21
|
+
int nativ_jsi_has_prop(void* runtime, void* object, const char* prop_name);
|
|
22
|
+
void nativ_jsi_call_function(void* runtime, void* object, const char* prop_name);
|
|
23
|
+
void nativ_jsi_call_function_with_string(void* runtime, void* object, const char* prop_name, const char* arg);
|
|
24
|
+
|
|
25
|
+
// Type checking
|
|
26
|
+
int nativ_jsi_is_array(void* runtime, void* object, const char* prop_name);
|
|
27
|
+
int nativ_jsi_get_array_length(void* runtime, void* object, const char* prop_name);
|
|
28
|
+
int nativ_jsi_is_object(void* runtime, void* object, const char* prop_name);
|
|
29
|
+
int nativ_jsi_is_null(void* runtime, void* object, const char* prop_name);
|
|
30
|
+
|
|
31
|
+
// Entry point for expo-ferrum backward compatibility
|
|
32
|
+
void nativ_cpp_install(void *runtimePtr);
|
|
33
|
+
|
|
34
|
+
#ifdef __cplusplus
|
|
35
|
+
}
|
|
36
|
+
#endif
|