@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,394 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* kotlin-daemon.js — persistent Kotlin compiler daemon.
|
|
3
|
+
*
|
|
4
|
+
* Starts a long-running JVM with the Kotlin compiler warm in memory.
|
|
5
|
+
* Communication via a simple TCP socket (sync-friendly from Node).
|
|
6
|
+
* First compile: ~3-5s (class loading). Subsequent compiles: ~1-2s.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { spawn, execSync } = require('child_process');
|
|
10
|
+
const net = require('net');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
|
|
14
|
+
let _daemon = null;
|
|
15
|
+
let _port = null;
|
|
16
|
+
|
|
17
|
+
const DAEMON_PORT_FILE = '/tmp/nativ-kotlin-daemon.port';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Start the Kotlin compiler daemon. Called once when Metro starts.
|
|
21
|
+
*/
|
|
22
|
+
function startDaemon(projectRoot) {
|
|
23
|
+
if (_daemon) return;
|
|
24
|
+
|
|
25
|
+
const gradleCache = path.join(process.env.HOME || '', '.gradle/caches/modules-2/files-2.1');
|
|
26
|
+
const localCache = path.join(projectRoot, '.nativ/kotlin-cache');
|
|
27
|
+
const findJar = (group, artifact) => {
|
|
28
|
+
// Check Gradle cache first
|
|
29
|
+
try {
|
|
30
|
+
const result = execSync(
|
|
31
|
+
`find "${gradleCache}/${group}/${artifact}" -name "*.jar" -not -name "*sources*" -not -name "*javadoc*" 2>/dev/null | sort -V | tail -1`,
|
|
32
|
+
{ encoding: 'utf8' }
|
|
33
|
+
).trim();
|
|
34
|
+
if (result) return result;
|
|
35
|
+
} catch {}
|
|
36
|
+
// Fall back to local cache (populated by setup-kotlin)
|
|
37
|
+
try {
|
|
38
|
+
const files = fs.readdirSync(localCache).filter(f => f.startsWith(artifact) && f.endsWith('.jar'));
|
|
39
|
+
if (files.length > 0) return path.join(localCache, files[0]);
|
|
40
|
+
} catch {}
|
|
41
|
+
return '';
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const embeddableJars = [
|
|
45
|
+
findJar('org.jetbrains.kotlin', 'kotlin-compiler-embeddable'),
|
|
46
|
+
findJar('org.jetbrains.kotlin', 'kotlin-stdlib'),
|
|
47
|
+
findJar('org.jetbrains.kotlin', 'kotlin-script-runtime'),
|
|
48
|
+
findJar('org.jetbrains.kotlinx', 'kotlinx-coroutines-core-jvm'),
|
|
49
|
+
findJar('org.jetbrains.intellij.deps', 'trove4j'),
|
|
50
|
+
findJar('org.jetbrains', 'annotations'),
|
|
51
|
+
].filter(Boolean);
|
|
52
|
+
|
|
53
|
+
if (embeddableJars.length < 3) {
|
|
54
|
+
console.warn('[nativ] Kotlin JARs not found — daemon not started');
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Find d8.jar to include in daemon (avoids separate JVM for dex conversion)
|
|
59
|
+
const androidHome = process.env.ANDROID_HOME || process.env.ANDROID_SDK_ROOT
|
|
60
|
+
|| path.join(process.env.HOME || '', 'Library/Android/sdk');
|
|
61
|
+
let d8Jar = null;
|
|
62
|
+
try {
|
|
63
|
+
const btDir = path.join(androidHome, 'build-tools');
|
|
64
|
+
const versions = fs.readdirSync(btDir).sort();
|
|
65
|
+
if (versions.length > 0) {
|
|
66
|
+
const candidate = path.join(btDir, versions[versions.length - 1], 'lib/d8.jar');
|
|
67
|
+
if (fs.existsSync(candidate)) d8Jar = candidate;
|
|
68
|
+
}
|
|
69
|
+
} catch {}
|
|
70
|
+
|
|
71
|
+
// Write the daemon Java source
|
|
72
|
+
const daemonDir = path.join(projectRoot, '.nativ/daemon');
|
|
73
|
+
fs.mkdirSync(daemonDir, { recursive: true });
|
|
74
|
+
const srcPath = path.join(daemonDir, 'KotlinDaemon.java');
|
|
75
|
+
fs.writeFileSync(srcPath, DAEMON_JAVA);
|
|
76
|
+
|
|
77
|
+
// Use the full (non-embeddable) compiler if available — needed for compiler API
|
|
78
|
+
// (KotlinCoreEnvironment uses un-shaded com.intellij.* classes)
|
|
79
|
+
// Read Kotlin version from config, fall back to scanning
|
|
80
|
+
let _ktVer = null;
|
|
81
|
+
try {
|
|
82
|
+
const cfg = JSON.parse(fs.readFileSync(path.join(projectRoot, '.nativ/nativ.config.json'), 'utf8'));
|
|
83
|
+
_ktVer = cfg.kotlin?.version;
|
|
84
|
+
} catch {}
|
|
85
|
+
const fullCompiler = _ktVer
|
|
86
|
+
? path.join(projectRoot, `.nativ/compose-pretransform/kotlin-compiler-${_ktVer}.jar`)
|
|
87
|
+
: (() => {
|
|
88
|
+
try {
|
|
89
|
+
const files = fs.readdirSync(path.join(projectRoot, '.nativ/compose-pretransform'));
|
|
90
|
+
const match = files.find(f => f.startsWith('kotlin-compiler-') && f.endsWith('.jar'));
|
|
91
|
+
return match ? path.join(projectRoot, '.nativ/compose-pretransform', match) : '';
|
|
92
|
+
} catch { return ''; }
|
|
93
|
+
})();
|
|
94
|
+
const compilerJars = fs.existsSync(fullCompiler)
|
|
95
|
+
? [daemonDir, fullCompiler, ...embeddableJars.filter(j => !j.includes('kotlin-compiler-embeddable'))]
|
|
96
|
+
: [daemonDir, ...embeddableJars];
|
|
97
|
+
if (d8Jar) compilerJars.push(d8Jar);
|
|
98
|
+
const jvmCp = compilerJars.join(':');
|
|
99
|
+
|
|
100
|
+
// Compile + start in background — doesn't block Metro startup
|
|
101
|
+
const classFile = path.join(daemonDir, 'KotlinDaemon.class');
|
|
102
|
+
const needsCompile = !fs.existsSync(classFile) ||
|
|
103
|
+
fs.statSync(srcPath).mtimeMs > fs.statSync(classFile).mtimeMs;
|
|
104
|
+
|
|
105
|
+
const launch = () => {
|
|
106
|
+
console.log('[nativ] Starting Kotlin compiler daemon...');
|
|
107
|
+
_daemon = spawn('java', [
|
|
108
|
+
'-Xmx1g',
|
|
109
|
+
'-XX:+UseG1GC',
|
|
110
|
+
'-XX:ReservedCodeCacheSize=256m',
|
|
111
|
+
'-cp', jvmCp,
|
|
112
|
+
'KotlinDaemon',
|
|
113
|
+
], {
|
|
114
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
115
|
+
detached: false,
|
|
116
|
+
});
|
|
117
|
+
wireUpDaemon();
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
if (needsCompile) {
|
|
121
|
+
// Compile async, then launch
|
|
122
|
+
const javacCp = fs.existsSync(fullCompiler) ? fullCompiler : embeddableJars[0];
|
|
123
|
+
const javac = spawn('javac', ['-cp', javacCp, srcPath, '-d', daemonDir], {
|
|
124
|
+
stdio: 'pipe',
|
|
125
|
+
});
|
|
126
|
+
javac.on('close', (code) => {
|
|
127
|
+
if (code === 0) launch();
|
|
128
|
+
else console.error('[nativ] Failed to compile Kotlin daemon');
|
|
129
|
+
});
|
|
130
|
+
} else {
|
|
131
|
+
// Already compiled — launch immediately (still async via spawn)
|
|
132
|
+
launch();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function wireUpDaemon() {
|
|
136
|
+
_daemon.stderr.on('data', (d) => {
|
|
137
|
+
const s = d.toString().trim();
|
|
138
|
+
if (s) console.error('[kotlin-daemon]', s.slice(0, 200));
|
|
139
|
+
});
|
|
140
|
+
_daemon.stdout.on('data', (d) => {
|
|
141
|
+
const s = d.toString().trim();
|
|
142
|
+
if (s.startsWith('PORT:')) {
|
|
143
|
+
_port = parseInt(s.split(':')[1]);
|
|
144
|
+
fs.writeFileSync(DAEMON_PORT_FILE, String(_port));
|
|
145
|
+
console.log(`[nativ] Kotlin daemon ready on port ${_port}`);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
_daemon.on('exit', (code) => {
|
|
149
|
+
console.log(`[nativ] Kotlin daemon exited (${code})`);
|
|
150
|
+
_daemon = null;
|
|
151
|
+
_port = null;
|
|
152
|
+
try { fs.unlinkSync(DAEMON_PORT_FILE); } catch {}
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Compile a .kt file via the daemon. SYNCHRONOUS (for Metro transformer).
|
|
159
|
+
* @param {Object} request - { sourceFile, outputDir, classpath, plugin? }
|
|
160
|
+
* @returns {{ success: boolean, error?: string }}
|
|
161
|
+
*/
|
|
162
|
+
function compileSyncViaDaemon(request) {
|
|
163
|
+
if (!_port) {
|
|
164
|
+
// Try reading port from file (in case daemon started in parent process)
|
|
165
|
+
try {
|
|
166
|
+
_port = parseInt(fs.readFileSync(DAEMON_PORT_FILE, 'utf8'));
|
|
167
|
+
} catch {}
|
|
168
|
+
}
|
|
169
|
+
if (!_port) return null;
|
|
170
|
+
|
|
171
|
+
const reqJson = JSON.stringify(request);
|
|
172
|
+
|
|
173
|
+
// Write request to temp file, send via nc (netcat)
|
|
174
|
+
const reqFile = `/tmp/nativ-kt-req-${process.pid}.json`;
|
|
175
|
+
fs.writeFileSync(reqFile, reqJson + '\n');
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
const result = execSync(
|
|
179
|
+
`nc -w 120 127.0.0.1 ${_port} < "${reqFile}"`,
|
|
180
|
+
{ encoding: 'utf8', timeout: 120000, stdio: ['pipe', 'pipe', 'pipe'] }
|
|
181
|
+
).trim();
|
|
182
|
+
try { fs.unlinkSync(reqFile); } catch {}
|
|
183
|
+
return JSON.parse(result);
|
|
184
|
+
} catch (e) {
|
|
185
|
+
try { fs.unlinkSync(reqFile); } catch {}
|
|
186
|
+
return { success: false, error: e.message?.slice(0, 200) };
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function stopDaemon() {
|
|
191
|
+
if (_daemon) {
|
|
192
|
+
try { _daemon.kill(); } catch {}
|
|
193
|
+
_daemon = null;
|
|
194
|
+
_port = null;
|
|
195
|
+
try { fs.unlinkSync(DAEMON_PORT_FILE); } catch {}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function isDaemonReady() {
|
|
200
|
+
if (_port) return true;
|
|
201
|
+
// Check port file (daemon started in main process, we're in a worker)
|
|
202
|
+
try {
|
|
203
|
+
_port = parseInt(fs.readFileSync(DAEMON_PORT_FILE, 'utf8'));
|
|
204
|
+
return _port > 0;
|
|
205
|
+
} catch { return false; }
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// ─── Daemon Java source (TCP server) ──────────────────────────────────
|
|
209
|
+
|
|
210
|
+
const DAEMON_JAVA = String.raw`
|
|
211
|
+
import java.io.*;
|
|
212
|
+
import java.net.*;
|
|
213
|
+
import java.util.*;
|
|
214
|
+
|
|
215
|
+
public class KotlinDaemon {
|
|
216
|
+
public static void main(String[] args) throws Exception {
|
|
217
|
+
ServerSocket server = new ServerSocket(0); // random port
|
|
218
|
+
int port = server.getLocalPort();
|
|
219
|
+
System.out.println("PORT:" + port);
|
|
220
|
+
System.out.flush();
|
|
221
|
+
|
|
222
|
+
// Pre-warm: compile with FULL classpath (android.jar + stdlib) to warm
|
|
223
|
+
// the JIT for classpath scanning, type resolution, and IR lowering.
|
|
224
|
+
// A trivial warmup doesn't help — need android imports to exercise real codepaths.
|
|
225
|
+
try {
|
|
226
|
+
java.io.File warmup = java.io.File.createTempFile("warmup", ".kt");
|
|
227
|
+
warmup.deleteOnExit();
|
|
228
|
+
java.io.FileWriter fw = new java.io.FileWriter(warmup);
|
|
229
|
+
fw.write("import android.view.View\nimport android.widget.TextView\n" +
|
|
230
|
+
"import android.widget.FrameLayout\nimport android.graphics.Color\n" +
|
|
231
|
+
"fun _warmup(ctx: android.content.Context) {\n" +
|
|
232
|
+
" val tv = TextView(ctx); tv.text = \"hi\"; tv.setTextColor(Color.WHITE)\n" +
|
|
233
|
+
" val fl = FrameLayout(ctx); fl.addView(tv)\n}\n");
|
|
234
|
+
fw.close();
|
|
235
|
+
java.io.File warmupOut = java.io.File.createTempFile("warmup-out", "");
|
|
236
|
+
warmupOut.delete(); warmupOut.mkdirs(); warmupOut.deleteOnExit();
|
|
237
|
+
|
|
238
|
+
// Build classpath: find android.jar and kotlin-stdlib from JVM classpath
|
|
239
|
+
String fullCp = System.getProperty("java.class.path");
|
|
240
|
+
String androidJar = "", stdlibJar = "";
|
|
241
|
+
for (String p : fullCp.split(":")) {
|
|
242
|
+
if (p.contains("android.jar") || p.contains("platforms/android")) androidJar = p;
|
|
243
|
+
if (p.contains("kotlin-stdlib") && !p.contains("script")) stdlibJar = p;
|
|
244
|
+
}
|
|
245
|
+
// If no android.jar on daemon classpath, try common location
|
|
246
|
+
if (androidJar.isEmpty()) {
|
|
247
|
+
String home = System.getProperty("user.home");
|
|
248
|
+
java.io.File aj = new java.io.File(home + "/Library/Android/sdk/platforms/android-36/android.jar");
|
|
249
|
+
if (aj.exists()) androidJar = aj.getAbsolutePath();
|
|
250
|
+
}
|
|
251
|
+
String warmupCp = androidJar + ":" + stdlibJar;
|
|
252
|
+
|
|
253
|
+
long t = System.currentTimeMillis();
|
|
254
|
+
for (int i = 0; i < 3; i++) {
|
|
255
|
+
var c = new org.jetbrains.kotlin.cli.jvm.K2JVMCompiler();
|
|
256
|
+
org.jetbrains.kotlin.cli.common.CLICompiler.doMainNoExit(c, new String[]{
|
|
257
|
+
warmup.getAbsolutePath(), "-d", warmupOut.getAbsolutePath(),
|
|
258
|
+
"-classpath", warmupCp, "-no-reflect", "-jvm-target", "17"
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
System.err.println("Kotlin daemon: JIT warmup done in " +
|
|
262
|
+
(System.currentTimeMillis() - t) + "ms (3 compiles with android.jar)");
|
|
263
|
+
|
|
264
|
+
// Also warm d8 if available
|
|
265
|
+
try {
|
|
266
|
+
Class.forName("com.android.tools.r8.D8");
|
|
267
|
+
System.err.println("Kotlin daemon: d8 class loaded");
|
|
268
|
+
} catch (Exception ignore) {}
|
|
269
|
+
} catch (Exception e) {
|
|
270
|
+
System.err.println("Warmup failed: " + e.getMessage());
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
while (true) {
|
|
274
|
+
try {
|
|
275
|
+
Socket client = server.accept();
|
|
276
|
+
BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
|
|
277
|
+
PrintWriter out = new PrintWriter(client.getOutputStream(), true);
|
|
278
|
+
|
|
279
|
+
String line = in.readLine();
|
|
280
|
+
if (line == null || line.isEmpty()) { client.close(); continue; }
|
|
281
|
+
|
|
282
|
+
String result = compile(line);
|
|
283
|
+
out.println(result);
|
|
284
|
+
client.close();
|
|
285
|
+
} catch (Exception e) {
|
|
286
|
+
// keep running
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Reuse compiler instance — internal caches may persist
|
|
292
|
+
static final org.jetbrains.kotlin.cli.jvm.K2JVMCompiler sharedCompiler =
|
|
293
|
+
new org.jetbrains.kotlin.cli.jvm.K2JVMCompiler();
|
|
294
|
+
|
|
295
|
+
// ─── Compile ───────────────────────────────────────────────────────
|
|
296
|
+
static String compile(String json) {
|
|
297
|
+
try {
|
|
298
|
+
String sourceFile = extract(json, "sourceFile");
|
|
299
|
+
String outputDir = extract(json, "outputDir");
|
|
300
|
+
String classpath = extract(json, "classpath");
|
|
301
|
+
String plugin = extract(json, "plugin");
|
|
302
|
+
String dexOutput = extract(json, "dexOutput");
|
|
303
|
+
String androidJar = extract(json, "androidJar");
|
|
304
|
+
|
|
305
|
+
long t0 = System.currentTimeMillis();
|
|
306
|
+
|
|
307
|
+
// Build compiler args
|
|
308
|
+
List<String> argList = new ArrayList<>();
|
|
309
|
+
argList.add(sourceFile);
|
|
310
|
+
argList.add("-d"); argList.add(outputDir);
|
|
311
|
+
argList.add("-classpath"); argList.add(classpath);
|
|
312
|
+
argList.add("-no-reflect"); argList.add("-jvm-target"); argList.add("17");
|
|
313
|
+
if (plugin != null && !plugin.isEmpty()) {
|
|
314
|
+
argList.add("-Xplugin=" + plugin);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
PrintStream oldErr = System.err;
|
|
318
|
+
ByteArrayOutputStream errBuf = new ByteArrayOutputStream();
|
|
319
|
+
System.setErr(new PrintStream(errBuf));
|
|
320
|
+
|
|
321
|
+
var compiler = new org.jetbrains.kotlin.cli.jvm.K2JVMCompiler();
|
|
322
|
+
var exit = org.jetbrains.kotlin.cli.common.CLICompiler.doMainNoExit(
|
|
323
|
+
compiler, argList.toArray(new String[0]));
|
|
324
|
+
long kotlincMs = System.currentTimeMillis() - t0;
|
|
325
|
+
|
|
326
|
+
System.setErr(oldErr);
|
|
327
|
+
|
|
328
|
+
if (exit != org.jetbrains.kotlin.cli.common.ExitCode.OK) {
|
|
329
|
+
String err = errBuf.toString().replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n").replace("\r", "");
|
|
330
|
+
return "{\"success\":false,\"error\":\"" + err + "\"}";
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Step 2: d8 → .dex
|
|
334
|
+
long d8Ms = 0;
|
|
335
|
+
if (dexOutput != null && !dexOutput.isEmpty()) {
|
|
336
|
+
try {
|
|
337
|
+
long t1 = System.currentTimeMillis();
|
|
338
|
+
List<String> classFiles = new ArrayList<>();
|
|
339
|
+
findClassFiles(new java.io.File(outputDir), classFiles);
|
|
340
|
+
|
|
341
|
+
List<String> d8Args = new ArrayList<>();
|
|
342
|
+
d8Args.add("--output");
|
|
343
|
+
d8Args.add(new java.io.File(dexOutput).getParent());
|
|
344
|
+
d8Args.add("--min-api"); d8Args.add("24");
|
|
345
|
+
d8Args.add("--no-desugaring");
|
|
346
|
+
if (androidJar != null && !androidJar.isEmpty()) {
|
|
347
|
+
d8Args.add("--lib"); d8Args.add(androidJar);
|
|
348
|
+
}
|
|
349
|
+
d8Args.addAll(classFiles);
|
|
350
|
+
|
|
351
|
+
Class<?> d8Class = Class.forName("com.android.tools.r8.D8");
|
|
352
|
+
d8Class.getMethod("main", String[].class)
|
|
353
|
+
.invoke(null, (Object) d8Args.toArray(new String[0]));
|
|
354
|
+
|
|
355
|
+
java.io.File classesDex = new java.io.File(
|
|
356
|
+
new java.io.File(dexOutput).getParent(), "classes.dex");
|
|
357
|
+
if (classesDex.exists()) classesDex.renameTo(new java.io.File(dexOutput));
|
|
358
|
+
|
|
359
|
+
d8Ms = System.currentTimeMillis() - t1;
|
|
360
|
+
} catch (Exception e) {
|
|
361
|
+
return "{\"success\":false,\"error\":\"d8: " +
|
|
362
|
+
(e.getMessage() != null ? e.getMessage().replace("\"", "\\\"") : "unknown") + "\"}";
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return "{\"success\":true,\"kotlincMs\":" + kotlincMs + ",\"d8Ms\":" + d8Ms + "}";
|
|
367
|
+
} catch (Exception e) {
|
|
368
|
+
String msg = e.getMessage() != null ? e.getMessage().replace("\"", "\\\"") : "unknown";
|
|
369
|
+
return "{\"success\":false,\"error\":\"" + msg + "\"}";
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
static void findClassFiles(java.io.File dir, List<String> result) {
|
|
374
|
+
java.io.File[] files = dir.listFiles();
|
|
375
|
+
if (files == null) return;
|
|
376
|
+
for (java.io.File f : files) {
|
|
377
|
+
if (f.isDirectory()) findClassFiles(f, result);
|
|
378
|
+
else if (f.getName().endsWith(".class")) result.add(f.getAbsolutePath());
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
static String extract(String json, String key) {
|
|
383
|
+
String search = "\"" + key + "\":\"";
|
|
384
|
+
int start = json.indexOf(search);
|
|
385
|
+
if (start < 0) return null;
|
|
386
|
+
start += search.length();
|
|
387
|
+
int end = json.indexOf("\"", start);
|
|
388
|
+
if (end < 0) return null;
|
|
389
|
+
return json.substring(start, end).replace("\\\\", "\\").replace("\\\"", "\"");
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
`;
|
|
393
|
+
|
|
394
|
+
module.exports = { startDaemon, compileSyncViaDaemon, stopDaemon, isDaemonReady };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maps C++ types to TypeScript types for .d.ts generation.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const CPP_TO_TS = {
|
|
6
|
+
// Primitives
|
|
7
|
+
'int': 'number',
|
|
8
|
+
'int32_t': 'number',
|
|
9
|
+
'int64_t': 'number',
|
|
10
|
+
'uint32_t': 'number',
|
|
11
|
+
'uint64_t': 'number',
|
|
12
|
+
'size_t': 'number',
|
|
13
|
+
'float': 'number',
|
|
14
|
+
'double': 'number',
|
|
15
|
+
'bool': 'boolean',
|
|
16
|
+
'void': 'void',
|
|
17
|
+
|
|
18
|
+
// Strings
|
|
19
|
+
'std::string': 'string',
|
|
20
|
+
'const std::string &': 'string',
|
|
21
|
+
'const std::string&': 'string',
|
|
22
|
+
'const char *': 'string',
|
|
23
|
+
'const char*': 'string',
|
|
24
|
+
|
|
25
|
+
// Buffers
|
|
26
|
+
'std::vector<uint8_t>': 'Uint8Array',
|
|
27
|
+
'std::vector<float>': 'Float32Array',
|
|
28
|
+
|
|
29
|
+
// Collections
|
|
30
|
+
'std::vector<std::string>': 'string[]',
|
|
31
|
+
'std::vector<int>': 'number[]',
|
|
32
|
+
'std::vector<double>': 'number[]',
|
|
33
|
+
|
|
34
|
+
// Optional
|
|
35
|
+
'std::optional<std::string>': 'string | null',
|
|
36
|
+
'std::optional<int>': 'number | null',
|
|
37
|
+
'std::optional<double>': 'number | null',
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
function cppTypeToTS(cppType, customTypes = {}) {
|
|
41
|
+
const trimmed = cppType.trim()
|
|
42
|
+
.replace(/\s+/g, ' ') // normalize whitespace
|
|
43
|
+
.replace(/\s*&\s*$/, '') // strip trailing reference
|
|
44
|
+
.replace(/^const\s+/, 'const ') // normalize const prefix
|
|
45
|
+
.trim();
|
|
46
|
+
|
|
47
|
+
// Check custom NATIV_TYPE mappings first
|
|
48
|
+
if (customTypes[trimmed]) return customTypes[trimmed];
|
|
49
|
+
|
|
50
|
+
// Check built-in mapping (try with and without const/ref)
|
|
51
|
+
if (CPP_TO_TS[trimmed]) return CPP_TO_TS[trimmed];
|
|
52
|
+
|
|
53
|
+
// Try stripping const and reference
|
|
54
|
+
const stripped = trimmed
|
|
55
|
+
.replace(/^const\s+/, '')
|
|
56
|
+
.replace(/\s*[&*]\s*$/, '')
|
|
57
|
+
.trim();
|
|
58
|
+
if (CPP_TO_TS[stripped]) return CPP_TO_TS[stripped];
|
|
59
|
+
|
|
60
|
+
return 'unknown';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
module.exports = { cppTypeToTS, CPP_TO_TS };
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@react-native-native/nativ-fabric",
|
|
3
|
+
"description": "Write native code directly in your React Native project. Import like any Javascript module.",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"main": "src/index.ts",
|
|
6
|
+
"codegenConfig": {
|
|
7
|
+
"name": "NativFabricSpec",
|
|
8
|
+
"type": "all",
|
|
9
|
+
"jsSrcsDir": "src"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@expo/config-plugins": ">=9.0.0",
|
|
13
|
+
"@react-native-native/cli": "^0.1.0"
|
|
14
|
+
},
|
|
15
|
+
"peerDependencies": {
|
|
16
|
+
"react": "*",
|
|
17
|
+
"react-native": ">=0.83"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/react-native-native/react-native-native.git"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"react-native",
|
|
25
|
+
"native",
|
|
26
|
+
"c++",
|
|
27
|
+
"rust",
|
|
28
|
+
"ffi",
|
|
29
|
+
"jni",
|
|
30
|
+
"ndk",
|
|
31
|
+
"objc",
|
|
32
|
+
"swift",
|
|
33
|
+
"performance",
|
|
34
|
+
"native-modules",
|
|
35
|
+
"expo"
|
|
36
|
+
],
|
|
37
|
+
"author": "Kim Brandwijk <kim.brandwijk@gmail.com>",
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/react-native-native/react-native-native/issues"
|
|
41
|
+
},
|
|
42
|
+
"homepage": "https://react-native-native.github.io"
|
|
43
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
dependency: {
|
|
3
|
+
platforms: {
|
|
4
|
+
ios: {
|
|
5
|
+
podspecPath: __dirname + '/NativFabric.podspec',
|
|
6
|
+
},
|
|
7
|
+
android: {
|
|
8
|
+
packageImportPath: 'import com.nativfabric.NativContainerPackage;',
|
|
9
|
+
packageInstance: 'new NativContainerPackage()',
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ViewProps } from "react-native";
|
|
2
|
+
import { codegenNativeComponent } from "react-native";
|
|
3
|
+
|
|
4
|
+
interface NativContainerProps extends ViewProps {
|
|
5
|
+
componentId: string;
|
|
6
|
+
propsJson?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default codegenNativeComponent<NativContainerProps>("NativContainer");
|
package/src/index.ts
ADDED