@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.
Files changed (42) hide show
  1. package/NativFabric.podspec +41 -0
  2. package/android/build.gradle +128 -0
  3. package/android/src/main/AndroidManifest.xml +2 -0
  4. package/android/src/main/cpp/CMakeLists.txt +59 -0
  5. package/android/src/main/cpp/NativBindingsInstaller.cpp +393 -0
  6. package/android/src/main/cpp/NativRuntime.cpp +508 -0
  7. package/android/src/main/java/com/nativfabric/ComposeHost.kt +26 -0
  8. package/android/src/main/java/com/nativfabric/NativContainerPackage.kt +35 -0
  9. package/android/src/main/java/com/nativfabric/NativContainerView.kt +51 -0
  10. package/android/src/main/java/com/nativfabric/NativContainerViewManager.kt +62 -0
  11. package/android/src/main/java/com/nativfabric/NativRuntime.kt +201 -0
  12. package/android/src/main/java/com/nativfabric/NativRuntimeModule.kt +37 -0
  13. package/android/src/main/java/com/nativfabric/compose/ComposeWrappers.kt +45 -0
  14. package/app.plugin.js +159 -0
  15. package/expo-module.config.json +6 -0
  16. package/ios/NativContainerComponentView.mm +137 -0
  17. package/ios/NativRuntime.h +36 -0
  18. package/ios/NativRuntime.mm +549 -0
  19. package/metro/Nativ.h +126 -0
  20. package/metro/compilers/android-compiler.js +339 -0
  21. package/metro/compilers/dylib-compiler.js +474 -0
  22. package/metro/compilers/kotlin-compiler.js +632 -0
  23. package/metro/compilers/rust-compiler.js +722 -0
  24. package/metro/compilers/static-compiler.js +1118 -0
  25. package/metro/compilers/swift-compiler.js +363 -0
  26. package/metro/extractors/cpp-ast-extractor.js +126 -0
  27. package/metro/extractors/kotlin-extractor.js +125 -0
  28. package/metro/extractors/rust-extractor.js +118 -0
  29. package/metro/index.js +236 -0
  30. package/metro/transformer.js +649 -0
  31. package/metro/utils/bridge-generator.js +50 -0
  32. package/metro/utils/compile-commands.js +104 -0
  33. package/metro/utils/cpp-daemon.js +344 -0
  34. package/metro/utils/dts-generator.js +32 -0
  35. package/metro/utils/include-resolver.js +73 -0
  36. package/metro/utils/kotlin-daemon.js +394 -0
  37. package/metro/utils/type-mapper.js +63 -0
  38. package/package.json +43 -0
  39. package/react-native.config.js +13 -0
  40. package/src/NativContainerNativeComponent.ts +9 -0
  41. package/src/NativeNativRuntime.ts +8 -0
  42. 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,6 @@
1
+ {
2
+ "platforms": ["ios"],
3
+ "ios": {
4
+ "podName": "NativFabric"
5
+ }
6
+ }
@@ -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