@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,474 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compiles a .cpp/.mm file to a signed arm64 dylib.
|
|
3
|
+
* Called directly by the transformer (same process — no IPC needed).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { execSync } = require('child_process');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
|
|
10
|
+
let _sdkPaths = {}; // 'device' → path, 'simulator' → path
|
|
11
|
+
let _signingIdentity = null;
|
|
12
|
+
let _resolved = false;
|
|
13
|
+
|
|
14
|
+
function getSdkPath(target) {
|
|
15
|
+
if (_sdkPaths[target]) return _sdkPaths[target];
|
|
16
|
+
const sdk = target === 'simulator' ? 'iphonesimulator' : 'iphoneos';
|
|
17
|
+
try {
|
|
18
|
+
_sdkPaths[target] = execSync(`xcrun --sdk ${sdk} --show-sdk-path`, {
|
|
19
|
+
encoding: 'utf8',
|
|
20
|
+
}).trim();
|
|
21
|
+
} catch {
|
|
22
|
+
console.error(`[nativ] No iOS SDK found for ${sdk}`);
|
|
23
|
+
}
|
|
24
|
+
return _sdkPaths[target] || null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function resolveOnce(projectRoot) {
|
|
28
|
+
if (_resolved) return;
|
|
29
|
+
_resolved = true;
|
|
30
|
+
|
|
31
|
+
// Find the signing identity that matches the app's team ID.
|
|
32
|
+
try {
|
|
33
|
+
// Read team ID — app.json first (works without prebuild / with EAS),
|
|
34
|
+
// then fall back to project.pbxproj
|
|
35
|
+
let appTeamId = null;
|
|
36
|
+
|
|
37
|
+
const root = projectRoot || '.';
|
|
38
|
+
try {
|
|
39
|
+
const appJson = JSON.parse(fs.readFileSync(path.join(root, 'app.json'), 'utf8'));
|
|
40
|
+
appTeamId = appJson?.expo?.ios?.appleTeamId || null;
|
|
41
|
+
} catch {}
|
|
42
|
+
|
|
43
|
+
if (!appTeamId) {
|
|
44
|
+
try {
|
|
45
|
+
const pbxprojs = execSync(
|
|
46
|
+
`find "${root}/ios" -name "project.pbxproj" -maxdepth 3 2>/dev/null`,
|
|
47
|
+
{ encoding: 'utf8' }
|
|
48
|
+
).trim().split('\n').filter(Boolean);
|
|
49
|
+
for (const pbx of pbxprojs) {
|
|
50
|
+
try {
|
|
51
|
+
const content = fs.readFileSync(pbx, 'utf8');
|
|
52
|
+
const m = content.match(/DEVELOPMENT_TEAM\s*=\s*(\w+)/);
|
|
53
|
+
if (m) { appTeamId = m[1]; break; }
|
|
54
|
+
} catch {}
|
|
55
|
+
}
|
|
56
|
+
} catch {}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (appTeamId) {
|
|
60
|
+
// Find the codesigning identity whose certificate OU matches the team
|
|
61
|
+
const identities = execSync('security find-identity -v -p codesigning', {
|
|
62
|
+
encoding: 'utf8',
|
|
63
|
+
});
|
|
64
|
+
const idEntries = [...identities.matchAll(/([A-F0-9]{40})\s+"([^"]+)"/g)];
|
|
65
|
+
|
|
66
|
+
for (const [, , name] of idEntries) {
|
|
67
|
+
try {
|
|
68
|
+
const certSubject = execSync(
|
|
69
|
+
`security find-certificate -c "${name}" -p 2>/dev/null | openssl x509 -noout -subject 2>/dev/null`,
|
|
70
|
+
{ encoding: 'utf8' }
|
|
71
|
+
);
|
|
72
|
+
if (certSubject.includes(`OU=${appTeamId}`)) {
|
|
73
|
+
_signingIdentity = name;
|
|
74
|
+
console.log(`[nativ] Signing: ${name} (team ${appTeamId})`);
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
} catch {}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Fallback: first available identity
|
|
82
|
+
if (!_signingIdentity) {
|
|
83
|
+
const match = execSync('security find-identity -v -p codesigning', {
|
|
84
|
+
encoding: 'utf8',
|
|
85
|
+
}).match(/"(Apple Development:[^"]+)"/);
|
|
86
|
+
if (match) _signingIdentity = match[1];
|
|
87
|
+
}
|
|
88
|
+
} catch {}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Compile a .cpp/.mm file + generated bridge to a signed dylib.
|
|
93
|
+
* Returns the dylib path, or null on failure.
|
|
94
|
+
*/
|
|
95
|
+
function compileDylib(filepath, includePaths, exports, projectRoot, { target = 'device' } = {}) {
|
|
96
|
+
resolveOnce(projectRoot);
|
|
97
|
+
const sdkPath = getSdkPath(target);
|
|
98
|
+
if (!sdkPath) return null;
|
|
99
|
+
|
|
100
|
+
const rel = path.relative(projectRoot, filepath);
|
|
101
|
+
const moduleId = rel
|
|
102
|
+
.replace(/\.(cpp|cc|mm)$/, '')
|
|
103
|
+
.replace(/[\/\\]/g, '_')
|
|
104
|
+
.replace(/[^a-zA-Z0-9_]/g, '_');
|
|
105
|
+
|
|
106
|
+
const outputDir = path.join(projectRoot, '.nativ/dylibs', target);
|
|
107
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
108
|
+
const dylibPath = path.join(outputDir, `${moduleId}.dylib`);
|
|
109
|
+
|
|
110
|
+
// Generate bridge source
|
|
111
|
+
const bridgePath = path.join(outputDir, `${moduleId}_bridge.cpp`);
|
|
112
|
+
const bridgeSource = generateBridge(exports, moduleId);
|
|
113
|
+
fs.writeFileSync(bridgePath, bridgeSource);
|
|
114
|
+
|
|
115
|
+
const isObjCpp = filepath.endsWith('.mm');
|
|
116
|
+
const lang = isObjCpp ? 'objective-c++' : 'c++';
|
|
117
|
+
|
|
118
|
+
// Filter out -isysroot from include paths (we provide our own)
|
|
119
|
+
const filteredPaths = [];
|
|
120
|
+
for (let i = 0; i < includePaths.length; i++) {
|
|
121
|
+
if (includePaths[i] === '-isysroot') { i++; continue; }
|
|
122
|
+
filteredPaths.push(includePaths[i]);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const targetTriple = target === 'simulator'
|
|
126
|
+
? 'arm64-apple-ios15.1-simulator'
|
|
127
|
+
: 'arm64-apple-ios15.1';
|
|
128
|
+
|
|
129
|
+
const cmd = [
|
|
130
|
+
'clang++',
|
|
131
|
+
'-x', lang,
|
|
132
|
+
'-std=c++17',
|
|
133
|
+
'-target', targetTriple,
|
|
134
|
+
'-dynamiclib',
|
|
135
|
+
'-fPIC',
|
|
136
|
+
'-isysroot', sdkPath,
|
|
137
|
+
...filteredPaths,
|
|
138
|
+
'-undefined', 'dynamic_lookup',
|
|
139
|
+
'-o', dylibPath,
|
|
140
|
+
filepath,
|
|
141
|
+
bridgePath,
|
|
142
|
+
];
|
|
143
|
+
|
|
144
|
+
// Auto-detect frameworks from #import/#include directives
|
|
145
|
+
if (isObjCpp) {
|
|
146
|
+
const src = fs.readFileSync(filepath, 'utf8');
|
|
147
|
+
const frameworkImports = src.matchAll(/#(?:import|include)\s*<(\w+)\//g);
|
|
148
|
+
// System header dirs that are NOT frameworks
|
|
149
|
+
const notFrameworks = new Set([
|
|
150
|
+
'sys', 'mach', 'os', 'dispatch', 'objc', 'libkern',
|
|
151
|
+
'arm', 'i386', 'machine', 'net', 'netinet', 'arpa',
|
|
152
|
+
]);
|
|
153
|
+
const frameworks = new Set();
|
|
154
|
+
for (const [, fw] of frameworkImports) {
|
|
155
|
+
if (!notFrameworks.has(fw)) {
|
|
156
|
+
frameworks.add(fw);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
// Always include Foundation for ObjC++
|
|
160
|
+
frameworks.add('Foundation');
|
|
161
|
+
for (const fw of frameworks) {
|
|
162
|
+
cmd.push('-framework', fw);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Suppress deprecation warnings — they cause noise but aren't errors
|
|
167
|
+
cmd.push('-Wno-deprecated-declarations');
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
execSync(cmd.join(' '), { stdio: 'pipe', encoding: 'utf8' });
|
|
171
|
+
} catch (err) {
|
|
172
|
+
console.error(`[nativ] dylib compile failed: ${path.basename(filepath)}`);
|
|
173
|
+
console.error((err.stderr || '').slice(0, 1000));
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Sign with the same identity/team as the app
|
|
178
|
+
if (_signingIdentity) {
|
|
179
|
+
try {
|
|
180
|
+
execSync(`codesign -fs "${_signingIdentity}" "${dylibPath}"`, { stdio: 'pipe' });
|
|
181
|
+
} catch {
|
|
182
|
+
// Fallback: ad-hoc sign
|
|
183
|
+
try {
|
|
184
|
+
execSync(`codesign -fs - "${dylibPath}"`, { stdio: 'pipe' });
|
|
185
|
+
} catch {}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const size = fs.statSync(dylibPath).size;
|
|
190
|
+
console.log(`[nativ] Built ${moduleId}.dylib (${(size / 1024).toFixed(1)}KB)`);
|
|
191
|
+
return dylibPath;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function generateBridge(exports, moduleId) {
|
|
195
|
+
const needsDispatch = exports.some(fn => fn.mainThread);
|
|
196
|
+
const lines = [
|
|
197
|
+
'// Hot-reload bridge — auto-generated',
|
|
198
|
+
'#include <string>',
|
|
199
|
+
'#include <cstdlib>',
|
|
200
|
+
...(needsDispatch ? ['#include <dispatch/dispatch.h>'] : []),
|
|
201
|
+
'',
|
|
202
|
+
'// Forward declarations',
|
|
203
|
+
];
|
|
204
|
+
|
|
205
|
+
for (const fn of exports) {
|
|
206
|
+
const argTypes = fn.args.map(a => a.type + ' ' + a.name).join(', ');
|
|
207
|
+
lines.push(`extern ${fn.ret} ${fn.name}(${argTypes});`);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
lines.push('', 'extern "C" {');
|
|
211
|
+
lines.push('typedef const char* (*NativSyncFn)(const char*);');
|
|
212
|
+
lines.push('typedef void (*NativAsyncFn)(const char*, void (*)(const char*), void (*)(const char*, const char*));');
|
|
213
|
+
lines.push('void nativ_register_sync(const char*, const char*, NativSyncFn);');
|
|
214
|
+
lines.push('void nativ_register_async(const char*, const char*, NativAsyncFn);');
|
|
215
|
+
lines.push('');
|
|
216
|
+
|
|
217
|
+
// JSON string escaping helper
|
|
218
|
+
lines.push(String.raw`static std::string _jsonEscapeString(const std::string& s) {
|
|
219
|
+
std::string buf = "\"";
|
|
220
|
+
for (unsigned char c : s) {
|
|
221
|
+
if (c == '"') buf += "\\\"";
|
|
222
|
+
else if (c == '\\') buf += "\\\\";
|
|
223
|
+
else if (c == '\n') buf += "\\n";
|
|
224
|
+
else if (c == '\r') buf += "\\r";
|
|
225
|
+
else if (c == '\t') buf += "\\t";
|
|
226
|
+
else if (c >= 0x20) buf += (char)c;
|
|
227
|
+
}
|
|
228
|
+
buf += "\"";
|
|
229
|
+
return buf;
|
|
230
|
+
}`);
|
|
231
|
+
lines.push('');
|
|
232
|
+
|
|
233
|
+
// JSON parse helpers
|
|
234
|
+
lines.push('static double _parseNumber(const char* &p) {');
|
|
235
|
+
lines.push(' while (*p == \' \' || *p == \',\' || *p == \'[\') p++;');
|
|
236
|
+
lines.push(' char* end; double v = strtod(p, &end); p = end; return v;');
|
|
237
|
+
lines.push('}');
|
|
238
|
+
lines.push('static std::string _parseString(const char* &p) {');
|
|
239
|
+
lines.push(' while (*p && *p != \'"\') p++; if (*p == \'"\') p++;');
|
|
240
|
+
lines.push(' std::string s; while (*p && *p != \'"\') {');
|
|
241
|
+
lines.push(' if (*p == \'\\\\\' && *(p+1)) { p++; s += *p; } else { s += *p; } p++; }');
|
|
242
|
+
lines.push(' if (*p == \'"\') p++; return s;');
|
|
243
|
+
lines.push('}');
|
|
244
|
+
lines.push('');
|
|
245
|
+
|
|
246
|
+
for (const fn of exports) {
|
|
247
|
+
if (fn.async) {
|
|
248
|
+
// Async wrapper: receives argsJson + resolve/reject callbacks
|
|
249
|
+
lines.push(`static void nativ_cpp_async_${moduleId}_${fn.name}(const char* argsJson, void (*resolve)(const char*), void (*reject)(const char*, const char*)) {`);
|
|
250
|
+
lines.push(' const char* p = argsJson;');
|
|
251
|
+
lines.push(' while (*p && *p != \'[\') p++; if (*p == \'[\') p++;');
|
|
252
|
+
|
|
253
|
+
const argNames = [];
|
|
254
|
+
for (const arg of fn.args) {
|
|
255
|
+
const t = arg.type.replace(/const\s+/, '').replace(/\s*&\s*$/, '').trim();
|
|
256
|
+
if (t === 'std::string') {
|
|
257
|
+
lines.push(` std::string ${arg.name} = _parseString(p);`);
|
|
258
|
+
} else {
|
|
259
|
+
lines.push(` ${arg.type} ${arg.name} = (${arg.type})_parseNumber(p);`);
|
|
260
|
+
}
|
|
261
|
+
argNames.push(arg.name);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
lines.push(' try {');
|
|
265
|
+
lines.push(` auto result = ${fn.name}(${argNames.join(', ')});`);
|
|
266
|
+
|
|
267
|
+
const retBase = fn.ret.replace(/const\s+/, '').replace(/\s*&\s*$/, '').trim();
|
|
268
|
+
if (retBase === 'std::string') {
|
|
269
|
+
lines.push(' resolve(_jsonEscapeString(result).c_str());');
|
|
270
|
+
} else if (retBase === 'void') {
|
|
271
|
+
lines.push(' resolve("null");');
|
|
272
|
+
} else {
|
|
273
|
+
lines.push(' std::string buf = std::to_string(result);');
|
|
274
|
+
lines.push(' resolve(buf.c_str());');
|
|
275
|
+
}
|
|
276
|
+
lines.push(' } catch (const std::exception& e) {');
|
|
277
|
+
lines.push(' reject("NATIVE_ERROR", e.what());');
|
|
278
|
+
lines.push(' } catch (...) {');
|
|
279
|
+
lines.push(' reject("NATIVE_ERROR", "Unknown error");');
|
|
280
|
+
lines.push(' }');
|
|
281
|
+
lines.push('}');
|
|
282
|
+
lines.push('');
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
lines.push(`static const char* nativ_cpp_${moduleId}_${fn.name}(const char* argsJson) {`);
|
|
287
|
+
lines.push(' const char* p = argsJson;');
|
|
288
|
+
lines.push(' while (*p && *p != \'[\') p++; if (*p == \'[\') p++;');
|
|
289
|
+
|
|
290
|
+
const argNames = [];
|
|
291
|
+
for (const arg of fn.args) {
|
|
292
|
+
const t = arg.type.replace(/const\s+/, '').replace(/\s*&\s*$/, '').trim();
|
|
293
|
+
if (t === 'std::string') {
|
|
294
|
+
lines.push(` std::string ${arg.name} = _parseString(p);`);
|
|
295
|
+
} else {
|
|
296
|
+
lines.push(` ${arg.type} ${arg.name} = (${arg.type})_parseNumber(p);`);
|
|
297
|
+
}
|
|
298
|
+
argNames.push(arg.name);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const retBase = fn.ret.replace(/const\s+/, '').replace(/\s*&\s*$/, '').trim();
|
|
302
|
+
|
|
303
|
+
// main thread dispatch for UIKit-touching functions
|
|
304
|
+
if (fn.mainThread) {
|
|
305
|
+
lines.push(' static thread_local std::string buf;');
|
|
306
|
+
lines.push(' __block std::string _result;');
|
|
307
|
+
lines.push(' dispatch_sync(dispatch_get_main_queue(), ^{');
|
|
308
|
+
lines.push(` auto result = ${fn.name}(${argNames.join(', ')});`);
|
|
309
|
+
if (retBase === 'std::string') {
|
|
310
|
+
lines.push(' _result = _jsonEscapeString(result);');
|
|
311
|
+
} else {
|
|
312
|
+
lines.push(' _result = std::to_string(result);');
|
|
313
|
+
}
|
|
314
|
+
lines.push(' });');
|
|
315
|
+
lines.push(' buf = _result;');
|
|
316
|
+
lines.push(' return buf.c_str();');
|
|
317
|
+
} else {
|
|
318
|
+
lines.push(` auto result = ${fn.name}(${argNames.join(', ')});`);
|
|
319
|
+
if (retBase === 'std::string') {
|
|
320
|
+
lines.push(' static thread_local std::string buf;');
|
|
321
|
+
lines.push(' buf = _jsonEscapeString(result);');
|
|
322
|
+
lines.push(' return buf.c_str();');
|
|
323
|
+
} else {
|
|
324
|
+
lines.push(' static thread_local std::string buf;');
|
|
325
|
+
lines.push(' buf = std::to_string(result);');
|
|
326
|
+
lines.push(' return buf.c_str();');
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
lines.push('}');
|
|
330
|
+
lines.push('');
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Constructor
|
|
334
|
+
lines.push('__attribute__((constructor))');
|
|
335
|
+
lines.push(`static void nativ_cpp_register_${moduleId}() {`);
|
|
336
|
+
for (const fn of exports) {
|
|
337
|
+
if (fn.async) {
|
|
338
|
+
lines.push(` nativ_register_async("${moduleId}", "${fn.name}", nativ_cpp_async_${moduleId}_${fn.name});`);
|
|
339
|
+
} else {
|
|
340
|
+
lines.push(` nativ_register_sync("${moduleId}", "${fn.name}", nativ_cpp_${moduleId}_${fn.name});`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
lines.push('}');
|
|
344
|
+
lines.push('');
|
|
345
|
+
lines.push('} // extern "C"');
|
|
346
|
+
|
|
347
|
+
return lines.join('\n');
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Compile an ObjC++/C++ component file to a signed dylib.
|
|
352
|
+
* The file defines a props struct + mount() function.
|
|
353
|
+
* The bridge auto-generates nativ_render that extracts props from the snapshot.
|
|
354
|
+
*/
|
|
355
|
+
function compileCppComponentDylib(filepath, includePaths, projectRoot, baseName, componentProps, { target = 'device' } = {}) {
|
|
356
|
+
resolveOnce(projectRoot);
|
|
357
|
+
const sdkPath = getSdkPath(target);
|
|
358
|
+
if (!sdkPath) return null;
|
|
359
|
+
|
|
360
|
+
const componentId = `nativ.${baseName}`;
|
|
361
|
+
const outputDir = path.join(projectRoot, '.nativ/dylibs', target);
|
|
362
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
363
|
+
const dylibPath = path.join(outputDir, `nativ_${baseName}.dylib`);
|
|
364
|
+
|
|
365
|
+
// Find the props struct name from NATIV_COMPONENT macro
|
|
366
|
+
const src = fs.readFileSync(filepath, 'utf8');
|
|
367
|
+
const compMatch = src.match(/NATIV_COMPONENT\s*\(\s*(\w+)\s*,\s*(\w+)\s*\)/);
|
|
368
|
+
const propsTypeName = compMatch ? compMatch[2] : null;
|
|
369
|
+
|
|
370
|
+
// Generate prop extraction code
|
|
371
|
+
const propExtractions = (componentProps || []).map(p => {
|
|
372
|
+
if (p.cppType === 'std::string') {
|
|
373
|
+
return ` props.${p.name} = _nativ_get_string(rt, obj, "${p.jsName}", props.${p.name});`;
|
|
374
|
+
} else if (p.cppType === 'double' || p.cppType === 'float' || p.cppType === 'int') {
|
|
375
|
+
return ` props.${p.name} = _nativ_get_number(rt, obj, "${p.jsName}", props.${p.name});`;
|
|
376
|
+
} else if (p.cppType === 'bool') {
|
|
377
|
+
return ` props.${p.name} = _nativ_get_bool(rt, obj, "${p.jsName}", props.${p.name});`;
|
|
378
|
+
} else if (p.cppType === 'std::function<void()>') {
|
|
379
|
+
return ` props.${p.name} = _nativ_get_callback(rt, obj, "${p.jsName}");`;
|
|
380
|
+
}
|
|
381
|
+
return ` // unknown type for ${p.name}: ${p.cppType}`;
|
|
382
|
+
}).join('\n');
|
|
383
|
+
|
|
384
|
+
// Generate bridge that auto-extracts props and calls mount()
|
|
385
|
+
const bridgePath = path.join(outputDir, `nativ_${baseName}_bridge.mm`);
|
|
386
|
+
const relPath = path.relative(outputDir, filepath);
|
|
387
|
+
const renderFnName = `nativ_${baseName}_render`;
|
|
388
|
+
const bridgeSrc = `
|
|
389
|
+
// Auto-generated component bridge for ${baseName}
|
|
390
|
+
#include "${relPath}"
|
|
391
|
+
|
|
392
|
+
// Auto-generated render function — unique per component for static linking compat
|
|
393
|
+
extern "C"
|
|
394
|
+
void ${renderFnName}(void* view, float width, float height,
|
|
395
|
+
void* jsi_runtime, void* jsi_props) {
|
|
396
|
+
void* rt = jsi_runtime;
|
|
397
|
+
void* obj = jsi_props;
|
|
398
|
+
${propsTypeName ? ` ${propsTypeName} props;
|
|
399
|
+
${propExtractions}
|
|
400
|
+
mount(view, width, height, props);` : ` mount(view, width, height);`}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
extern "C" {
|
|
404
|
+
typedef void (*NativRenderFn)(void*, float, float, void*, void*);
|
|
405
|
+
void nativ_register_render(const char*, NativRenderFn);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
__attribute__((constructor))
|
|
409
|
+
static void register_${baseName}() {
|
|
410
|
+
nativ_register_render("${componentId}", ${renderFnName});
|
|
411
|
+
}
|
|
412
|
+
`;
|
|
413
|
+
fs.writeFileSync(bridgePath, bridgeSrc);
|
|
414
|
+
|
|
415
|
+
const isObjCpp = filepath.endsWith('.mm');
|
|
416
|
+
const lang = isObjCpp ? 'objective-c++' : 'c++';
|
|
417
|
+
|
|
418
|
+
const filteredPaths = [];
|
|
419
|
+
for (let i = 0; i < includePaths.length; i++) {
|
|
420
|
+
if (includePaths[i] === '-isysroot') { i++; continue; }
|
|
421
|
+
filteredPaths.push(includePaths[i]);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const targetTriple = target === 'simulator'
|
|
425
|
+
? 'arm64-apple-ios15.1-simulator'
|
|
426
|
+
: 'arm64-apple-ios15.1';
|
|
427
|
+
|
|
428
|
+
const cmd = [
|
|
429
|
+
'clang++',
|
|
430
|
+
'-x', lang,
|
|
431
|
+
'-std=c++17',
|
|
432
|
+
'-target', targetTriple,
|
|
433
|
+
'-dynamiclib',
|
|
434
|
+
'-fPIC',
|
|
435
|
+
'-isysroot', sdkPath,
|
|
436
|
+
...filteredPaths,
|
|
437
|
+
'-undefined', 'dynamic_lookup',
|
|
438
|
+
'-Wno-deprecated-declarations',
|
|
439
|
+
'-o', dylibPath,
|
|
440
|
+
filepath,
|
|
441
|
+
bridgePath,
|
|
442
|
+
];
|
|
443
|
+
|
|
444
|
+
// Auto-detect frameworks
|
|
445
|
+
if (isObjCpp) {
|
|
446
|
+
const src = fs.readFileSync(filepath, 'utf8');
|
|
447
|
+
const notFrameworks = new Set(['sys', 'mach', 'os', 'dispatch', 'objc', 'libkern', 'arm', 'i386', 'machine', 'net', 'netinet', 'arpa']);
|
|
448
|
+
const frameworks = new Set(['Foundation']);
|
|
449
|
+
for (const [, fw] of src.matchAll(/#(?:import|include)\s*<(\w+)\//g)) {
|
|
450
|
+
if (!notFrameworks.has(fw)) frameworks.add(fw);
|
|
451
|
+
}
|
|
452
|
+
for (const fw of frameworks) cmd.push('-framework', fw);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
try {
|
|
456
|
+
execSync(cmd.join(' '), { stdio: 'pipe', encoding: 'utf8' });
|
|
457
|
+
} catch (err) {
|
|
458
|
+
console.error(`[nativ] Component dylib compile failed: ${path.basename(filepath)}`);
|
|
459
|
+
console.error((err.stderr || '').slice(0, 1000));
|
|
460
|
+
return null;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if (_signingIdentity) {
|
|
464
|
+
try {
|
|
465
|
+
execSync(`codesign -fs "${_signingIdentity}" "${dylibPath}"`, { stdio: 'pipe' });
|
|
466
|
+
} catch {}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const size = fs.statSync(dylibPath).size;
|
|
470
|
+
console.log(`[nativ] Built nativ_${baseName}.dylib component (${(size / 1024).toFixed(1)}KB)`);
|
|
471
|
+
return dylibPath;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
module.exports = { compileDylib, compileCppComponentDylib, generateBridge };
|