@rejaul/react-native-rust 1.0.2 → 2.0.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/README.md +129 -2
- package/dist/app.js +285 -0
- package/dist/codegen/constants.js +168 -0
- package/dist/codegen/index.js +38 -0
- package/dist/codegen/project.js +221 -0
- package/{bin → dist}/codegen/renderers.js +201 -84
- package/dist/codegen/spec.js +158 -0
- package/dist/codegen/types.js +16 -0
- package/dist/codegen.js +22 -0
- package/dist/dev/buildQueue.js +54 -0
- package/dist/dev/watcher.js +90 -0
- package/dist/metro.js +100 -0
- package/dist/react-native-rust-lib.js +470 -0
- package/dist/react-native-rust.js +130 -0
- package/dist/types.js +16 -0
- package/metro.js +2 -0
- package/package.json +30 -4
- package/bin/codegen/constants.js +0 -39
- package/bin/codegen/index.js +0 -9
- package/bin/codegen/project.js +0 -168
- package/bin/codegen/spec.js +0 -156
- package/bin/codegen.js +0 -1
- package/bin/react-native-rust-lib.js +0 -302
- package/bin/react-native-rust.js +0 -82
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var react_native_rust_lib_exports = {};
|
|
30
|
+
__export(react_native_rust_lib_exports, {
|
|
31
|
+
build: () => build,
|
|
32
|
+
doctor: () => doctor,
|
|
33
|
+
generate: () => generate,
|
|
34
|
+
init: () => init,
|
|
35
|
+
packageRoot: () => packageRoot,
|
|
36
|
+
readJson: () => readJson,
|
|
37
|
+
run: () => run,
|
|
38
|
+
watch: () => watch
|
|
39
|
+
});
|
|
40
|
+
module.exports = __toCommonJS(react_native_rust_lib_exports);
|
|
41
|
+
var import_node_fs = __toESM(require("node:fs"));
|
|
42
|
+
var import_node_path = __toESM(require("node:path"));
|
|
43
|
+
var import_node_child_process = require("node:child_process");
|
|
44
|
+
var import_codegen = require("./codegen");
|
|
45
|
+
var import_watcher = require("./dev/watcher");
|
|
46
|
+
var import_buildQueue = require("./dev/buildQueue");
|
|
47
|
+
const rustDirectory = "rust";
|
|
48
|
+
const iosTargets = ["aarch64-apple-ios", "aarch64-apple-ios-sim", "x86_64-apple-ios"];
|
|
49
|
+
const androidTargets = [
|
|
50
|
+
["arm64-v8a", "aarch64-linux-android"],
|
|
51
|
+
["armeabi-v7a", "armv7-linux-androideabi"],
|
|
52
|
+
["x86", "i686-linux-android"],
|
|
53
|
+
["x86_64", "x86_64-linux-android"]
|
|
54
|
+
];
|
|
55
|
+
function run(command, args, options = {}) {
|
|
56
|
+
const result = (0, import_node_child_process.spawnSync)(command, args, {
|
|
57
|
+
cwd: options.cwd || process.cwd(),
|
|
58
|
+
encoding: "utf8",
|
|
59
|
+
stdio: options.inherit ? "inherit" : "pipe"
|
|
60
|
+
});
|
|
61
|
+
if (result.error) return { ok: false, message: result.error.message };
|
|
62
|
+
if (result.status !== 0) {
|
|
63
|
+
return {
|
|
64
|
+
ok: false,
|
|
65
|
+
message: (result.stderr || result.stdout || `${command} exited with ${result.status}`).trim()
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
return { ok: true, output: (result.stdout || "").trim() };
|
|
69
|
+
}
|
|
70
|
+
function readJson(filePath) {
|
|
71
|
+
try {
|
|
72
|
+
return JSON.parse(import_node_fs.default.readFileSync(filePath, "utf8"));
|
|
73
|
+
} catch (error) {
|
|
74
|
+
throw new Error(`Cannot read ${filePath}: ${error.message}`, { cause: error });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function packageRoot() {
|
|
78
|
+
const root = process.cwd();
|
|
79
|
+
const manifestPath = import_node_path.default.join(root, "package.json");
|
|
80
|
+
if (!import_node_fs.default.existsSync(manifestPath)) {
|
|
81
|
+
throw new Error("Run this command from the root of a React Native library.");
|
|
82
|
+
}
|
|
83
|
+
const manifest = readJson(manifestPath);
|
|
84
|
+
const dependencies = { ...manifest.dependencies, ...manifest.peerDependencies };
|
|
85
|
+
if (!dependencies["react-native"]) {
|
|
86
|
+
throw new Error("This package does not declare react-native as a dependency or peer dependency.");
|
|
87
|
+
}
|
|
88
|
+
const configPath = import_node_path.default.join(root, "react-native.config.js");
|
|
89
|
+
if (!import_node_fs.default.existsSync(configPath)) {
|
|
90
|
+
throw new Error("Missing react-native.config.js. Create the library with the C++ module template.");
|
|
91
|
+
}
|
|
92
|
+
const config = import_node_fs.default.readFileSync(configPath, "utf8");
|
|
93
|
+
const moduleMatch = config.match(/cxxModuleHeaderName\s*:\s*['"]([a-zA-Z0-9_]+)Impl['"]/);
|
|
94
|
+
if (!moduleMatch || !config.includes("cxxModuleCMakeListsPath")) {
|
|
95
|
+
throw new Error("The library is not configured as a C++ TurboModule. Choose the C++ module template.");
|
|
96
|
+
}
|
|
97
|
+
return { root, manifest, manifestPath, moduleName: moduleMatch[1] };
|
|
98
|
+
}
|
|
99
|
+
function resolveRustRoot(root, manifest) {
|
|
100
|
+
return import_node_path.default.resolve(root, manifest.reactNativeRust?.rustDir || rustDirectory);
|
|
101
|
+
}
|
|
102
|
+
function readCrateName(rustRoot) {
|
|
103
|
+
const manifestPath = import_node_path.default.join(rustRoot, "Cargo.toml");
|
|
104
|
+
const cargoManifest = import_node_fs.default.readFileSync(manifestPath, "utf8");
|
|
105
|
+
const crateMatch = cargoManifest.match(/^name\s*=\s*"([a-zA-Z0-9_-]+)"/m);
|
|
106
|
+
if (!crateMatch) throw new Error("Could not read the Rust crate name from rust/Cargo.toml.");
|
|
107
|
+
return crateMatch[1];
|
|
108
|
+
}
|
|
109
|
+
function init() {
|
|
110
|
+
const { root, manifest, manifestPath, moduleName } = packageRoot();
|
|
111
|
+
const rustPath = resolveRustRoot(root, manifest);
|
|
112
|
+
if (import_node_fs.default.existsSync(rustPath)) {
|
|
113
|
+
throw new Error(`${import_node_path.default.relative(root, rustPath) || "rust"}/ already exists; no files were changed.`);
|
|
114
|
+
}
|
|
115
|
+
const scripts = manifest.scripts || {};
|
|
116
|
+
if (scripts["rust:test"] || scripts["rust:generate"]) {
|
|
117
|
+
throw new Error("package.json already defines a rust:test or rust:generate script; no files were changed.");
|
|
118
|
+
}
|
|
119
|
+
const projectFiles = {
|
|
120
|
+
cppHeader: import_node_path.default.join(root, "cpp", `${moduleName}Impl.h`),
|
|
121
|
+
cppSource: import_node_path.default.join(root, "cpp", `${moduleName}Impl.cpp`),
|
|
122
|
+
nativeSpec: import_node_path.default.join(root, "src", `Native${moduleName}.ts`),
|
|
123
|
+
libraryIndex: import_node_path.default.join(root, "src", "index.tsx"),
|
|
124
|
+
androidCmake: import_node_path.default.join(root, "android", "CMakeLists.txt")
|
|
125
|
+
};
|
|
126
|
+
const podspecs = import_node_fs.default.readdirSync(root).filter((fileName) => fileName.endsWith(".podspec"));
|
|
127
|
+
if (podspecs.length !== 1) throw new Error("Expected exactly one CocoaPods spec file. No files were changed.");
|
|
128
|
+
projectFiles.podspec = import_node_path.default.join(root, podspecs[0]);
|
|
129
|
+
if (Object.values(projectFiles).some((filePath) => !import_node_fs.default.existsSync(filePath))) {
|
|
130
|
+
throw new Error("Expected C++ library template files were not found. No files were changed.");
|
|
131
|
+
}
|
|
132
|
+
const contents = Object.fromEntries(Object.entries(projectFiles).map(([key, filePath]) => [
|
|
133
|
+
key,
|
|
134
|
+
import_node_fs.default.readFileSync(filePath, "utf8")
|
|
135
|
+
]));
|
|
136
|
+
const targetMatch = contents.androidCmake.match(/add_library\(\s*([a-zA-Z0-9_-]+)\s+STATIC/);
|
|
137
|
+
if (!targetMatch || !contents.cppHeader.includes("#pragma once")) {
|
|
138
|
+
throw new Error("The native files do not match the supported C++ TurboModule template. No files were changed.");
|
|
139
|
+
}
|
|
140
|
+
const methods = (0, import_codegen.parseSpec)(contents.nativeSpec, import_node_path.default.relative(root, projectFiles.nativeSpec));
|
|
141
|
+
const crateName = (manifest.name || "react-native-rust").replace(/^@[^/]+\//, "").replace(/[^a-zA-Z0-9_]+/g, "_");
|
|
142
|
+
const crateDirName = /^\d/.test(crateName) ? `rust_${crateName.toLowerCase()}` : crateName.toLowerCase();
|
|
143
|
+
const targetName = targetMatch[1];
|
|
144
|
+
const linkMatch = contents.androidCmake.match(/target_link_libraries\(\s*([a-zA-Z0-9_-]+)([\s\S]*?)\n\)/);
|
|
145
|
+
if (!linkMatch || linkMatch[1] !== targetName) {
|
|
146
|
+
throw new Error("Could not locate the C++ target link block in android/CMakeLists.txt.");
|
|
147
|
+
}
|
|
148
|
+
const androidLink = `${linkMatch[0].slice(0, -2)}
|
|
149
|
+
rust_core
|
|
150
|
+
log
|
|
151
|
+
dl
|
|
152
|
+
m
|
|
153
|
+
)`;
|
|
154
|
+
const androidRustBuildTarget = (0, import_codegen.resolveOutsideReference)(root, import_node_path.default.join(rustPath, "build", "android"), "rust-build-android");
|
|
155
|
+
const androidRustBuildPath = (0, import_codegen.toPosixRelative)(import_node_path.default.join(root, "android"), androidRustBuildTarget);
|
|
156
|
+
const rustImport = `add_library(rust_core STATIC IMPORTED)
|
|
157
|
+
set_target_properties(rust_core PROPERTIES
|
|
158
|
+
IMPORTED_LOCATION "\${CMAKE_CURRENT_LIST_DIR}/${androidRustBuildPath}/\${ANDROID_ABI}/lib${crateDirName}.a"
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
`;
|
|
162
|
+
const androidCmake = contents.androidCmake.replace(linkMatch[0], androidLink).replace(/add_library\(/, `${rustImport}add_library(`);
|
|
163
|
+
const podspecSource = contents.podspec.match(/^\s*s\.source_files\s*=.*$/m);
|
|
164
|
+
if (!podspecSource) throw new Error("Could not locate source_files in the CocoaPods spec.");
|
|
165
|
+
const iosRustBuildPath = (0, import_codegen.toPosixRelative)(root, import_node_path.default.join(rustPath, "build", "ios")).startsWith("..") ? "rust-build-ios" : (0, import_codegen.toPosixRelative)(root, import_node_path.default.join(rustPath, "build", "ios"));
|
|
166
|
+
const podspec = contents.podspec.replace(
|
|
167
|
+
podspecSource[0],
|
|
168
|
+
`${podspecSource[0]}
|
|
169
|
+
s.vendored_frameworks = "${iosRustBuildPath}/${moduleName}Rust.xcframework"`
|
|
170
|
+
);
|
|
171
|
+
const rustFiles = {
|
|
172
|
+
"Cargo.toml": `[package]
|
|
173
|
+
name = "${crateDirName}"
|
|
174
|
+
version = "0.1.0"
|
|
175
|
+
edition = "2021"
|
|
176
|
+
|
|
177
|
+
[lib]
|
|
178
|
+
crate-type = ["staticlib", "cdylib"]
|
|
179
|
+
|
|
180
|
+
[dependencies]
|
|
181
|
+
serde = { version = "1", features = ["derive"] }
|
|
182
|
+
serde_json = "1"
|
|
183
|
+
|
|
184
|
+
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
|
185
|
+
wasm-bindgen = "0.2"
|
|
186
|
+
js-sys = "0.3"
|
|
187
|
+
|
|
188
|
+
[build-dependencies]
|
|
189
|
+
cbindgen = "0.26"
|
|
190
|
+
`,
|
|
191
|
+
"build.rs": `use std::path::PathBuf;
|
|
192
|
+
|
|
193
|
+
fn main() {
|
|
194
|
+
let crate_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
|
|
195
|
+
let output = PathBuf::from(&crate_dir).join("include/rust_api.h");
|
|
196
|
+
std::fs::create_dir_all(output.parent().unwrap()).unwrap();
|
|
197
|
+
cbindgen::generate(&crate_dir)
|
|
198
|
+
.expect("failed to generate the Rust C header")
|
|
199
|
+
.write_to_file(output);
|
|
200
|
+
}
|
|
201
|
+
`,
|
|
202
|
+
"cbindgen.toml": `language = "C++"
|
|
203
|
+
`,
|
|
204
|
+
"src/ffi.rs": (0, import_codegen.renderRustFfiModule)(),
|
|
205
|
+
"README.md": `# Rust core
|
|
206
|
+
|
|
207
|
+
Rust handlers are generated from the TurboModule Spec interface in src/Native${moduleName}.ts. Edit the generated functions under rust/src/api/ and regenerate native glue with npx react-native-rust generate.
|
|
208
|
+
|
|
209
|
+
Run cargo test --manifest-path rust/Cargo.toml for Rust tests. Build iOS and Android artifacts with npx react-native-rust build ios and npx react-native-rust build android before native app builds. Build the react-native-web WebAssembly package with npx react-native-rust build web (requires wasm-pack and the wasm32-unknown-unknown Rust target).
|
|
210
|
+
`
|
|
211
|
+
};
|
|
212
|
+
const updates = (0, import_codegen.renderProjectBindings)(root, moduleName, methods, true, crateDirName, rustPath);
|
|
213
|
+
updates.set(projectFiles.androidCmake, androidCmake);
|
|
214
|
+
updates.set(projectFiles.podspec, podspec);
|
|
215
|
+
const cliManifest = readJson(import_node_path.default.join(__dirname, "..", "package.json"));
|
|
216
|
+
const cliRoot = import_node_path.default.resolve(__dirname, "..");
|
|
217
|
+
const cliIsInstalled = cliRoot.split(import_node_path.default.sep).includes("node_modules");
|
|
218
|
+
const cliDependency = cliIsInstalled ? `^${cliManifest.version}` : `file:${import_node_path.default.relative(root, cliRoot).split(import_node_path.default.sep).join("/") || "."}`;
|
|
219
|
+
const updatedManifest = {
|
|
220
|
+
...manifest,
|
|
221
|
+
devDependencies: {
|
|
222
|
+
...manifest.devDependencies,
|
|
223
|
+
[cliManifest.name]: manifest.devDependencies?.[cliManifest.name] || cliDependency
|
|
224
|
+
},
|
|
225
|
+
scripts: {
|
|
226
|
+
...scripts,
|
|
227
|
+
"rust:test": `cargo test --manifest-path ${(0, import_codegen.toPosixRelative)(root, rustPath)}/Cargo.toml`,
|
|
228
|
+
"rust:generate": "react-native-rust generate",
|
|
229
|
+
"rust:build:ios": "react-native-rust build ios",
|
|
230
|
+
"rust:build:android": "react-native-rust build android",
|
|
231
|
+
"rust:build:web": "react-native-rust build web"
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
const rustIsInsideRoot = !(0, import_codegen.toPosixRelative)(root, rustPath).startsWith("..");
|
|
235
|
+
if (rustIsInsideRoot) {
|
|
236
|
+
const packageFiles = Array.isArray(updatedManifest.files) ? [...updatedManifest.files] : [];
|
|
237
|
+
for (const filePath of ["rust/", "rust/build/", "rust/include/"]) {
|
|
238
|
+
if (!packageFiles.includes(filePath)) packageFiles.push(filePath);
|
|
239
|
+
}
|
|
240
|
+
updatedManifest.files = packageFiles;
|
|
241
|
+
}
|
|
242
|
+
updates.set(manifestPath, `${JSON.stringify(updatedManifest, null, 2)}
|
|
243
|
+
`);
|
|
244
|
+
if (rustIsInsideRoot) {
|
|
245
|
+
const gitignorePath = import_node_path.default.join(root, ".gitignore");
|
|
246
|
+
const gitignore = import_node_fs.default.existsSync(gitignorePath) ? import_node_fs.default.readFileSync(gitignorePath, "utf8") : "";
|
|
247
|
+
const ignoredRustOutputs = ["rust/target/", "rust/build/"].filter((entry) => !gitignore.split(/\r?\n/).includes(entry));
|
|
248
|
+
if (ignoredRustOutputs.length > 0) {
|
|
249
|
+
const separator = gitignore.length > 0 && !gitignore.endsWith("\n") ? "\n" : "";
|
|
250
|
+
updates.set(gitignorePath, `${gitignore}${separator}${ignoredRustOutputs.join("\n")}
|
|
251
|
+
`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
for (const [relativePath, fileContents] of Object.entries(rustFiles)) {
|
|
255
|
+
const outputPath = import_node_path.default.join(rustPath, relativePath);
|
|
256
|
+
import_node_fs.default.mkdirSync(import_node_path.default.dirname(outputPath), { recursive: true });
|
|
257
|
+
import_node_fs.default.writeFileSync(outputPath, fileContents);
|
|
258
|
+
}
|
|
259
|
+
for (const [filePath, fileContents] of updates) {
|
|
260
|
+
import_node_fs.default.mkdirSync(import_node_path.default.dirname(filePath), { recursive: true });
|
|
261
|
+
import_node_fs.default.writeFileSync(filePath, fileContents);
|
|
262
|
+
}
|
|
263
|
+
console.log(`Generated ${methods.length} Rust handler(s) and native bindings from Spec.`);
|
|
264
|
+
console.log("Run `npx react-native-rust build ios` or `npx react-native-rust build android` before building the React Native app.");
|
|
265
|
+
}
|
|
266
|
+
function generate() {
|
|
267
|
+
const { root, manifest, moduleName } = packageRoot();
|
|
268
|
+
const rustPath = resolveRustRoot(root, manifest);
|
|
269
|
+
if (!import_node_fs.default.existsSync(import_node_path.default.join(rustPath, "Cargo.toml"))) {
|
|
270
|
+
throw new Error("Rust is not initialized. Run `npx react-native-rust init` first.");
|
|
271
|
+
}
|
|
272
|
+
const nativeSpecPath = import_node_path.default.join(root, "src", `Native${moduleName}.ts`);
|
|
273
|
+
const methods = (0, import_codegen.parseSpec)(import_node_fs.default.readFileSync(nativeSpecPath, "utf8"), import_node_path.default.relative(root, nativeSpecPath));
|
|
274
|
+
const updates = (0, import_codegen.renderProjectBindings)(root, moduleName, methods, false, readCrateName(rustPath), rustPath);
|
|
275
|
+
for (const [filePath, fileContents] of updates) {
|
|
276
|
+
import_node_fs.default.mkdirSync(import_node_path.default.dirname(filePath), { recursive: true });
|
|
277
|
+
import_node_fs.default.writeFileSync(filePath, fileContents);
|
|
278
|
+
}
|
|
279
|
+
console.log(`Regenerated Rust, C++, and TypeScript glue for ${methods.length} Spec method(s).`);
|
|
280
|
+
}
|
|
281
|
+
function doctor(target) {
|
|
282
|
+
const { root, manifest } = packageRoot();
|
|
283
|
+
if (!import_node_fs.default.existsSync(import_node_path.default.join(resolveRustRoot(root, manifest), "Cargo.toml"))) {
|
|
284
|
+
throw new Error("Rust is not initialized. Run `npx react-native-rust init` first.");
|
|
285
|
+
}
|
|
286
|
+
let failed = false;
|
|
287
|
+
for (const command of ["rustc", "cargo"]) {
|
|
288
|
+
const result = run(command, ["--version"]);
|
|
289
|
+
console.log(`${result.ok ? "OK" : "MISSING"} ${command}${result.output ? `: ${result.output}` : ""}`);
|
|
290
|
+
failed ||= !result.ok;
|
|
291
|
+
}
|
|
292
|
+
if (target === "ios") {
|
|
293
|
+
const xcode = run("xcodebuild", ["-version"]);
|
|
294
|
+
const lipo = run("lipo", ["-version"]);
|
|
295
|
+
console.log(`${xcode.ok ? "OK" : "MISSING"} Xcode`);
|
|
296
|
+
console.log(`${lipo.ok ? "OK" : "MISSING"} lipo`);
|
|
297
|
+
failed ||= process.platform !== "darwin" || !xcode.ok || !lipo.ok;
|
|
298
|
+
}
|
|
299
|
+
if (target === "android") {
|
|
300
|
+
const ndkPath = process.env.ANDROID_NDK_HOME || process.env.ANDROID_NDK_ROOT;
|
|
301
|
+
const cargoNdk = run("cargo", ["ndk", "--version"]);
|
|
302
|
+
console.log(`${ndkPath ? "OK" : "CHECK"} Android NDK path (cargo-ndk can auto-detect Android Studio installs)`);
|
|
303
|
+
console.log(`${cargoNdk.ok ? "OK" : "MISSING"} cargo-ndk`);
|
|
304
|
+
failed ||= !cargoNdk.ok;
|
|
305
|
+
}
|
|
306
|
+
if (target === "web") {
|
|
307
|
+
const wasmPack = run("wasm-pack", ["--version"]);
|
|
308
|
+
const installedTargets = run("rustup", ["target", "list", "--installed"]);
|
|
309
|
+
const hasWasmTarget = installedTargets.ok && (installedTargets.output || "").includes("wasm32-unknown-unknown");
|
|
310
|
+
console.log(`${wasmPack.ok ? "OK" : "MISSING"} wasm-pack`);
|
|
311
|
+
console.log(`${hasWasmTarget ? "OK" : "MISSING"} wasm32-unknown-unknown Rust target (rustup target add wasm32-unknown-unknown)`);
|
|
312
|
+
failed ||= !wasmPack.ok || !hasWasmTarget;
|
|
313
|
+
}
|
|
314
|
+
if (failed) process.exitCode = 1;
|
|
315
|
+
}
|
|
316
|
+
function build(platform) {
|
|
317
|
+
const { root, manifest, moduleName } = packageRoot();
|
|
318
|
+
const rustRoot = resolveRustRoot(root, manifest);
|
|
319
|
+
const manifestPath = import_node_path.default.join(rustRoot, "Cargo.toml");
|
|
320
|
+
if (!import_node_fs.default.existsSync(manifestPath)) {
|
|
321
|
+
throw new Error("Rust is not initialized. Run `npx react-native-rust init` first.");
|
|
322
|
+
}
|
|
323
|
+
const crateName = readCrateName(rustRoot);
|
|
324
|
+
const releasePath = (target) => import_node_path.default.join(rustRoot, "target", target, "release", `lib${crateName}.a`);
|
|
325
|
+
(0, import_codegen.resolveOutsideReference)(root, import_node_path.default.join(rustRoot, "build", "android"), "rust-build-android");
|
|
326
|
+
if (platform === "ios") {
|
|
327
|
+
for (const target of iosTargets) {
|
|
328
|
+
const result = run("cargo", ["build", "--release", "--target", target, "--manifest-path", manifestPath], { inherit: true });
|
|
329
|
+
if (!result.ok) throw new Error(`iOS build failed for ${target}: ${result.message}`);
|
|
330
|
+
}
|
|
331
|
+
const buildPath = import_node_path.default.join(rustRoot, "build", "ios");
|
|
332
|
+
import_node_fs.default.mkdirSync(buildPath, { recursive: true });
|
|
333
|
+
const simulatorDirectory = import_node_path.default.join(buildPath, "simulator");
|
|
334
|
+
import_node_fs.default.mkdirSync(simulatorDirectory, { recursive: true });
|
|
335
|
+
const simulatorArchive = import_node_path.default.join(simulatorDirectory, `lib${crateName}.a`);
|
|
336
|
+
const lipo = run("lipo", [
|
|
337
|
+
"-create",
|
|
338
|
+
releasePath("aarch64-apple-ios-sim"),
|
|
339
|
+
releasePath("x86_64-apple-ios"),
|
|
340
|
+
"-output",
|
|
341
|
+
simulatorArchive
|
|
342
|
+
], { inherit: true });
|
|
343
|
+
if (!lipo.ok) throw new Error(`Could not combine iOS simulator archives: ${lipo.message}`);
|
|
344
|
+
const frameworkPath = import_node_path.default.join(buildPath, `${moduleName}Rust.xcframework`);
|
|
345
|
+
import_node_fs.default.rmSync(frameworkPath, { recursive: true, force: true });
|
|
346
|
+
const xcframework = run("xcodebuild", [
|
|
347
|
+
"-create-xcframework",
|
|
348
|
+
"-library",
|
|
349
|
+
releasePath("aarch64-apple-ios"),
|
|
350
|
+
"-headers",
|
|
351
|
+
import_node_path.default.join(rustRoot, "include"),
|
|
352
|
+
"-library",
|
|
353
|
+
simulatorArchive,
|
|
354
|
+
"-headers",
|
|
355
|
+
import_node_path.default.join(rustRoot, "include"),
|
|
356
|
+
"-output",
|
|
357
|
+
frameworkPath
|
|
358
|
+
], { inherit: true });
|
|
359
|
+
if (!xcframework.ok) throw new Error(`Could not create the iOS XCFramework: ${xcframework.message}`);
|
|
360
|
+
if ((0, import_codegen.toPosixRelative)(root, frameworkPath).startsWith("..")) {
|
|
361
|
+
const podFrameworkPath = import_node_path.default.join(root, "rust-build-ios", `${moduleName}Rust.xcframework`);
|
|
362
|
+
import_node_fs.default.rmSync(import_node_path.default.dirname(podFrameworkPath), { recursive: true, force: true });
|
|
363
|
+
import_node_fs.default.mkdirSync(import_node_path.default.dirname(podFrameworkPath), { recursive: true });
|
|
364
|
+
import_node_fs.default.cpSync(frameworkPath, podFrameworkPath, { recursive: true });
|
|
365
|
+
}
|
|
366
|
+
console.log(`Built ${import_node_path.default.relative(root, frameworkPath)} for ${manifest.name}.`);
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
if (platform === "android") {
|
|
370
|
+
const result = run("cargo", [
|
|
371
|
+
"ndk",
|
|
372
|
+
"-t",
|
|
373
|
+
"arm64-v8a",
|
|
374
|
+
"-t",
|
|
375
|
+
"armeabi-v7a",
|
|
376
|
+
"-t",
|
|
377
|
+
"x86",
|
|
378
|
+
"-t",
|
|
379
|
+
"x86_64",
|
|
380
|
+
"build",
|
|
381
|
+
"--release"
|
|
382
|
+
], { inherit: true, cwd: rustRoot });
|
|
383
|
+
if (!result.ok) throw new Error(`Android build failed: ${result.message}`);
|
|
384
|
+
for (const [abi, target] of androidTargets) {
|
|
385
|
+
const outputDirectory = import_node_path.default.join(rustRoot, "build", "android", abi);
|
|
386
|
+
import_node_fs.default.mkdirSync(outputDirectory, { recursive: true });
|
|
387
|
+
import_node_fs.default.copyFileSync(releasePath(target), import_node_path.default.join(outputDirectory, `lib${crateName}.a`));
|
|
388
|
+
}
|
|
389
|
+
console.log(`Built Android static archives for ${manifest.name}.`);
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
if (platform === "web") {
|
|
393
|
+
const result = run("wasm-pack", ["build", "--target", "web", "--out-dir", import_node_path.default.join("build", "web", "pkg")], { inherit: true, cwd: rustRoot });
|
|
394
|
+
if (!result.ok) throw new Error(`Web build failed: ${result.message}`);
|
|
395
|
+
console.log(`Built ${(0, import_codegen.toPosixRelative)(root, import_node_path.default.join(rustRoot, "build", "web", "pkg"))} for ${manifest.name}.`);
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
throw new Error("Choose a build target: ios, android, or web.");
|
|
399
|
+
}
|
|
400
|
+
function watch(options = {}) {
|
|
401
|
+
const { root, manifest, moduleName } = packageRoot();
|
|
402
|
+
const rustRoot = resolveRustRoot(root, manifest);
|
|
403
|
+
const cargoManifest = import_node_path.default.join(rustRoot, "Cargo.toml");
|
|
404
|
+
if (!import_node_fs.default.existsSync(cargoManifest)) {
|
|
405
|
+
throw new Error("Rust is not initialized. Run `npx react-native-rust init` first.");
|
|
406
|
+
}
|
|
407
|
+
const log = options.log || console.log;
|
|
408
|
+
const target = { rustRoot, specPath: import_node_path.default.join(root, "src", `Native${moduleName}.ts`) };
|
|
409
|
+
let pendingKinds = /* @__PURE__ */ new Set();
|
|
410
|
+
async function runWatchCycle(kinds) {
|
|
411
|
+
if (kinds.has("spec")) {
|
|
412
|
+
log("[RN Rust] Spec changed. Regenerating native bindings...");
|
|
413
|
+
try {
|
|
414
|
+
generate();
|
|
415
|
+
options.onGenerated?.();
|
|
416
|
+
} catch (error) {
|
|
417
|
+
log("[RN Rust] Code generation failed.");
|
|
418
|
+
log("Possible causes:");
|
|
419
|
+
log(" - invalid Rust API");
|
|
420
|
+
log(" - unsupported parameter type");
|
|
421
|
+
log(" - unsupported return type");
|
|
422
|
+
log(" - invalid annotation/spec");
|
|
423
|
+
log(` ${error.message}`);
|
|
424
|
+
log("[Rust] Waiting for changes...");
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
log("[RN Rust] Native bindings updated.");
|
|
428
|
+
}
|
|
429
|
+
log("[Rust] Building...");
|
|
430
|
+
const start = Date.now();
|
|
431
|
+
const result = run("cargo", ["build", "--manifest-path", cargoManifest], { inherit: true });
|
|
432
|
+
const elapsed = ((Date.now() - start) / 1e3).toFixed(1);
|
|
433
|
+
if (!result.ok) {
|
|
434
|
+
log("[Rust] Build failed.");
|
|
435
|
+
log("[Rust] Waiting for changes...");
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
log(`[Rust] Build completed in ${elapsed}s`);
|
|
439
|
+
log("[Rust] Native library updated.");
|
|
440
|
+
log("[RN Rust] Native code cannot hot-swap into a running app: run `npm run rust:build:ios` / `rust:build:android` / `rust:build:web` and reinstall (or refresh the browser tab, for react-native-web) to load this change.");
|
|
441
|
+
log("[Rust] Waiting for changes...");
|
|
442
|
+
}
|
|
443
|
+
const queue = new import_buildQueue.SingleFlightQueue(() => {
|
|
444
|
+
const kinds = pendingKinds;
|
|
445
|
+
pendingKinds = /* @__PURE__ */ new Set();
|
|
446
|
+
return runWatchCycle(kinds);
|
|
447
|
+
});
|
|
448
|
+
const watcher = (0, import_watcher.watchRustProject)({
|
|
449
|
+
target,
|
|
450
|
+
log,
|
|
451
|
+
onChange: (kinds) => {
|
|
452
|
+
for (const kind of kinds) pendingKinds.add(kind);
|
|
453
|
+
queue.request();
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
log(`[Rust] Watching ${import_node_path.default.relative(process.cwd(), rustRoot) || "rust"}/ and ${import_node_path.default.relative(process.cwd(), target.specPath)}`);
|
|
457
|
+
log("[Rust] Waiting for changes...");
|
|
458
|
+
return { stop: () => watcher.close() };
|
|
459
|
+
}
|
|
460
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
461
|
+
0 && (module.exports = {
|
|
462
|
+
build,
|
|
463
|
+
doctor,
|
|
464
|
+
generate,
|
|
465
|
+
init,
|
|
466
|
+
packageRoot,
|
|
467
|
+
readJson,
|
|
468
|
+
run,
|
|
469
|
+
watch
|
|
470
|
+
});
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
25
|
+
var import_node_fs = __toESM(require("node:fs"));
|
|
26
|
+
var import_node_path = __toESM(require("node:path"));
|
|
27
|
+
var import_node_child_process = require("node:child_process");
|
|
28
|
+
var import_react_native_rust_lib = require("./react-native-rust-lib");
|
|
29
|
+
var import_app = require("./app");
|
|
30
|
+
function printHelp() {
|
|
31
|
+
console.log(`react-native-rust - Rust helper for React Native C++ TurboModule libraries
|
|
32
|
+
|
|
33
|
+
Usage:
|
|
34
|
+
react-native-rust create <name> Create a React Native library and initialize Rust support
|
|
35
|
+
react-native-rust init Add Rust support and generate functions from the Spec interface
|
|
36
|
+
react-native-rust init --app Add app-local Rust support to a React Native app
|
|
37
|
+
react-native-rust generate Regenerate Rust, C++, and TypeScript glue from Spec
|
|
38
|
+
react-native-rust doctor [target] Check Rust and optional ios/android/web prerequisites
|
|
39
|
+
react-native-rust build ios Build an iOS XCFramework
|
|
40
|
+
react-native-rust build android Build Android static archives for supported ABIs
|
|
41
|
+
react-native-rust build web Build a WebAssembly package for react-native-web
|
|
42
|
+
react-native-rust watch Watch the Spec and rust/ for changes and rebuild automatically
|
|
43
|
+
react-native-rust --help Show this help
|
|
44
|
+
|
|
45
|
+
Create a library first with the C++ module template from:
|
|
46
|
+
npx create-react-native-library@latest`);
|
|
47
|
+
}
|
|
48
|
+
function createProject(projectName) {
|
|
49
|
+
if (!projectName || projectName.startsWith("-") || import_node_path.default.basename(projectName) !== projectName) {
|
|
50
|
+
throw new Error("Usage: react-native-rust create <library-name>");
|
|
51
|
+
}
|
|
52
|
+
const projectPath = import_node_path.default.resolve(process.cwd(), projectName);
|
|
53
|
+
if (import_node_fs.default.existsSync(projectPath)) {
|
|
54
|
+
throw new Error(`The target directory already exists: ${projectPath}`);
|
|
55
|
+
}
|
|
56
|
+
console.log("In the scaffolder prompts, select a TurboModule library using C++.");
|
|
57
|
+
const scaffold = (0, import_node_child_process.spawnSync)("npx", ["--yes", "create-react-native-library@latest", projectName], {
|
|
58
|
+
cwd: process.cwd(),
|
|
59
|
+
stdio: "inherit",
|
|
60
|
+
encoding: "utf8"
|
|
61
|
+
});
|
|
62
|
+
if (scaffold.error) throw new Error(`React Native library creation failed: ${scaffold.error.message}`);
|
|
63
|
+
if (scaffold.status !== 0) throw new Error("React Native library creation failed.");
|
|
64
|
+
if (!import_node_fs.default.existsSync(import_node_path.default.join(projectPath, "package.json"))) {
|
|
65
|
+
throw new Error(`The scaffolder did not create ${projectPath}.`);
|
|
66
|
+
}
|
|
67
|
+
const initialized = (0, import_node_child_process.spawnSync)(process.execPath, [__filename, "init"], {
|
|
68
|
+
cwd: projectPath,
|
|
69
|
+
stdio: "inherit",
|
|
70
|
+
encoding: "utf8"
|
|
71
|
+
});
|
|
72
|
+
if (initialized.error) {
|
|
73
|
+
throw new Error(`Rust initialization failed in ${projectPath}: ${initialized.error.message}`);
|
|
74
|
+
}
|
|
75
|
+
if (initialized.status !== 0) {
|
|
76
|
+
throw new Error(`Rust initialization failed in ${projectPath}.`);
|
|
77
|
+
}
|
|
78
|
+
console.log(`Created ${projectName}. Next run \`cd ${projectName} && yarn install\`.`);
|
|
79
|
+
}
|
|
80
|
+
function main(args) {
|
|
81
|
+
const [command, option] = args;
|
|
82
|
+
if (!command || command === "--help" || command === "-h") return printHelp();
|
|
83
|
+
if (command === "create") return createProject(option);
|
|
84
|
+
if (command === "init") {
|
|
85
|
+
if (option === "--app") return (0, import_app.initApp)();
|
|
86
|
+
if (option) throw new Error("Use `react-native-rust init --app` for an app, or run `react-native-rust init` from a C++ TurboModule library.");
|
|
87
|
+
return (0, import_react_native_rust_lib.init)();
|
|
88
|
+
}
|
|
89
|
+
const appRoot = process.cwd();
|
|
90
|
+
const appModule = (0, import_app.appModuleRoot)(appRoot);
|
|
91
|
+
if (command === "generate") {
|
|
92
|
+
if (!appModule) return (0, import_react_native_rust_lib.generate)();
|
|
93
|
+
return (0, import_app.inDirectory)(appModule, () => {
|
|
94
|
+
(0, import_react_native_rust_lib.generate)();
|
|
95
|
+
(0, import_app.runCodegen)(appRoot, appModule);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
if (command === "doctor") {
|
|
99
|
+
if (option && option !== "ios" && option !== "android" && option !== "web") {
|
|
100
|
+
throw new Error("Choose a doctor target: ios, android, or web.");
|
|
101
|
+
}
|
|
102
|
+
const target = option;
|
|
103
|
+
return appModule ? (0, import_app.inDirectory)(appModule, () => (0, import_react_native_rust_lib.doctor)(target)) : (0, import_react_native_rust_lib.doctor)(target);
|
|
104
|
+
}
|
|
105
|
+
if (command === "build") {
|
|
106
|
+
if (!appModule) return (0, import_react_native_rust_lib.build)(option);
|
|
107
|
+
(0, import_app.runCodegen)(appRoot, appModule);
|
|
108
|
+
return (0, import_app.inDirectory)(appModule, () => (0, import_react_native_rust_lib.build)(option));
|
|
109
|
+
}
|
|
110
|
+
if (command === "watch") {
|
|
111
|
+
if (appModule) process.chdir(appModule);
|
|
112
|
+
const handle = (0, import_react_native_rust_lib.watch)(appModule ? { onGenerated: () => (0, import_app.runCodegen)(appRoot, appModule) } : {});
|
|
113
|
+
const shutdown = (signal) => {
|
|
114
|
+
console.log(`
|
|
115
|
+
[Rust] Received ${signal}, stopping watcher...`);
|
|
116
|
+
handle.stop().finally(() => process.exit(0));
|
|
117
|
+
};
|
|
118
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
119
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
120
|
+
process.on("SIGHUP", () => shutdown("SIGHUP"));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
throw new Error(`Unknown command: ${command}. Run react-native-rust --help for usage.`);
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
main(process.argv.slice(2));
|
|
127
|
+
} catch (error) {
|
|
128
|
+
console.error(`Error: ${error.message}`);
|
|
129
|
+
process.exitCode = 1;
|
|
130
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __copyProps = (to, from, except, desc) => {
|
|
7
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
8
|
+
for (let key of __getOwnPropNames(from))
|
|
9
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
10
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
11
|
+
}
|
|
12
|
+
return to;
|
|
13
|
+
};
|
|
14
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
15
|
+
var types_exports = {};
|
|
16
|
+
module.exports = __toCommonJS(types_exports);
|
package/metro.js
ADDED