@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
package/README.md
CHANGED
|
@@ -4,11 +4,22 @@ Generate Rust functions and native bindings from a React Native TurboModule Type
|
|
|
4
4
|
|
|
5
5
|
The CLI currently targets the **C++ TurboModule** template created by [`create-react-native-library`](https://www.npmjs.com/package/create-react-native-library).
|
|
6
6
|
|
|
7
|
+
## Screenshots
|
|
8
|
+
|
|
9
|
+
The generated [`react-native-awesome-library-example`](react-native-awesome-library-example) app calling every generated method and rendering its live Rust result, on all three platforms:
|
|
10
|
+
|
|
11
|
+
| Android | iOS | Web |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
|  |  |  |
|
|
14
|
+
|
|
7
15
|
## Contents
|
|
8
16
|
|
|
17
|
+
- [Screenshots](#screenshots)
|
|
9
18
|
- [Requirements](#requirements)
|
|
10
19
|
- [Create a library](#create-a-library)
|
|
11
20
|
- [Add Rust functions](#add-rust-functions)
|
|
21
|
+
- [Use Rust in an app](#use-rust-in-an-app)
|
|
22
|
+
- [Automatic development mode](#automatic-development-mode)
|
|
12
23
|
- [Build and run](#build-and-run)
|
|
13
24
|
- [Supported types](#supported-types)
|
|
14
25
|
- [Publish](#publish)
|
|
@@ -24,6 +35,7 @@ The CLI currently targets the **C++ TurboModule** template created by [`create-r
|
|
|
24
35
|
- Rust stable (`rustup`, `cargo`).
|
|
25
36
|
- Android builds: Android SDK/NDK and `cargo-ndk`.
|
|
26
37
|
- iOS builds: macOS, Xcode command-line tools, and the Rust Apple targets.
|
|
38
|
+
- Web builds (`react-native-web`): [`wasm-pack`](https://rustwasm.github.io/wasm-pack/) and the `wasm32-unknown-unknown` Rust target (`rustup target add wasm32-unknown-unknown`).
|
|
27
39
|
|
|
28
40
|
## Create a library
|
|
29
41
|
|
|
@@ -42,10 +54,10 @@ cd react-native-awesome-library
|
|
|
42
54
|
yarn install
|
|
43
55
|
```
|
|
44
56
|
|
|
45
|
-
To run the CLI from its source checkout before publishing,
|
|
57
|
+
To run the CLI from its source checkout before publishing, build it once (`npm run build` from the checkout root), then invoke the compiled output by path from the desired parent folder:
|
|
46
58
|
|
|
47
59
|
```sh
|
|
48
|
-
node /path/to/rust_to_cpp/
|
|
60
|
+
node /path/to/rust_to_cpp/dist/react-native-rust.js create react-native-awesome-library
|
|
49
61
|
```
|
|
50
62
|
|
|
51
63
|
To add Rust support to an existing C++ TurboModule library, install the CLI and initialize it from that library's root:
|
|
@@ -89,6 +101,89 @@ Run Rust tests with:
|
|
|
89
101
|
yarn rust:test
|
|
90
102
|
```
|
|
91
103
|
|
|
104
|
+
## Use Rust in an app
|
|
105
|
+
|
|
106
|
+
For app-specific Rust code, run the CLI from the root of an existing React Native Community CLI app that has both `android/` and `ios/` folders:
|
|
107
|
+
|
|
108
|
+
```sh
|
|
109
|
+
npx react-native-rust init --app
|
|
110
|
+
npm install
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The command creates a private local TurboModule package in `.rust-native/` (hidden, generated C++/TurboModule glue only — never hand-edit it), and puts the Rust crate itself at the app's own root, `rust/`, alongside `android/` and `ios/`. It adds `.rust-native/` to the app as a local dependency and generates its C++ bridge. It does not publish a separate library. App-local mode currently supports C++ TurboModule apps with both Android and iOS projects.
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
my-app/
|
|
117
|
+
├── rust/ ← edit this: your Rust crate
|
|
118
|
+
│ ├── Cargo.toml
|
|
119
|
+
│ └── src/api/
|
|
120
|
+
├── .rust-native/ ← generated TurboModule glue; do not edit
|
|
121
|
+
├── android/
|
|
122
|
+
├── ios/
|
|
123
|
+
└── package.json
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Edit `.rust-native/src/NativeRustApp.ts` to declare methods, then implement their generated handlers under `rust/src/api/`. From the app root, regenerate the bindings and React Native Codegen output with:
|
|
127
|
+
|
|
128
|
+
```sh
|
|
129
|
+
npm run rust:generate
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Build the Rust archive for the target platform, then build and launch the React Native app:
|
|
133
|
+
|
|
134
|
+
```sh
|
|
135
|
+
npm run rust:build:android
|
|
136
|
+
npx react-native run-android
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
For iOS, build the XCFramework, install Pods, then launch the app:
|
|
140
|
+
|
|
141
|
+
```sh
|
|
142
|
+
npm run rust:build:ios
|
|
143
|
+
cd ios && pod install && cd ..
|
|
144
|
+
npx react-native run-ios
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
The generated module is app-local and can be used from the app's JavaScript imports. The root `example/` project in this repository exercises this workflow.
|
|
148
|
+
|
|
149
|
+
## Automatic development mode
|
|
150
|
+
|
|
151
|
+
The commands above (`rust:generate`, `rust:build:*`) still work exactly as before and remain fully supported. For day-to-day development, `react-native-rust watch` runs instead, watching the `Spec` and `rust/` and reacting automatically:
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
Legacy workflow: init → generate → build (repeated by hand after every change)
|
|
155
|
+
New workflow: init once → npx react-native-rust watch (or npx react-native start, with Metro integration below)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
On every save, the watcher classifies the change and reacts accordingly, matching what a manual run of `generate`/`build` would already produce:
|
|
159
|
+
|
|
160
|
+
- **A Rust file under `rust/src/`, or `Cargo.toml`/`Cargo.lock`, changes** (an implementation-only edit): it runs an incremental `cargo build` only. Native bindings are not regenerated.
|
|
161
|
+
- **The `Spec` file changes** (a new method, or a signature change): it runs `generate()` first — preserving hand-written Rust handler bodies, exactly like running `rust:generate` by hand — and then `cargo build`.
|
|
162
|
+
- Rapid successive saves are debounced and coalesced into one build; a build already in progress is never interrupted, and at most one more run is queued behind it.
|
|
163
|
+
- A failed build or a failed code generation prints the error and keeps watching; it does not crash the process or leave stale generated files.
|
|
164
|
+
|
|
165
|
+
Run it directly:
|
|
166
|
+
|
|
167
|
+
```sh
|
|
168
|
+
npx react-native-rust watch
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Or wire it into Metro so `npx react-native start` runs it automatically:
|
|
172
|
+
|
|
173
|
+
```js
|
|
174
|
+
// metro.config.js
|
|
175
|
+
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
|
|
176
|
+
const { withRust } = require('@rejaul/react-native-rust/metro');
|
|
177
|
+
|
|
178
|
+
module.exports = withRust(mergeConfig(getDefaultConfig(__dirname), {}));
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
`withRust()` detects your Rust project automatically at `rust/` (both library mode and app-local mode keep it at the project root) and adds it to `watchFolders`; pass `{ rustDir: './rust' }` only if your Rust project lives somewhere non-standard. It starts the watcher only while Metro's dev server is actually serving requests — never during a production/release bundle — and its `[Rust]`/`[RN Rust]` output streams into the same terminal as Metro's own. If Rust hasn't been initialized yet, it no-ops with a one-line notice instead of erroring, so it's safe to add before running `init`.
|
|
182
|
+
|
|
183
|
+
If Metro integration isn't wired up (or doesn't fit your setup), run `npx react-native-rust watch` in a separate terminal alongside `npx react-native start` — it does the same thing standalone.
|
|
184
|
+
|
|
185
|
+
**What this does not do:** Rust and C++ are native code. A running Android/iOS app cannot hot-swap a rebuilt native library the way Metro hot-swaps JavaScript. Each cycle rebuilds the Rust crate and prints a reminder that you still need to run `npm run rust:build:ios` / `rust:build:android` and reinstall the app to load the change on a simulator or device. A `Spec` change also regenerates `src/rust-generated.native.tsx`, which Metro's own JS watcher will pick up and Fast Refresh on its own, same as any other JS/TS change.
|
|
186
|
+
|
|
92
187
|
## Build and run
|
|
93
188
|
|
|
94
189
|
Run these commands from the library root. Generate TurboModule Codegen output with `yarn prepare` before building the host app.
|
|
@@ -150,6 +245,30 @@ Rust outputs are written under `rust/build/` and included in the generated libra
|
|
|
150
245
|
|
|
151
246
|
The example screen in `example/src/App.tsx` exercises the supported scalar, JSON, Promise, and callback signatures.
|
|
152
247
|
|
|
248
|
+
### Web
|
|
249
|
+
|
|
250
|
+
Install `wasm-pack` and the `wasm32-unknown-unknown` target once:
|
|
251
|
+
|
|
252
|
+
```sh
|
|
253
|
+
cargo install wasm-pack
|
|
254
|
+
rustup target add wasm32-unknown-unknown
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Build the WebAssembly package:
|
|
258
|
+
|
|
259
|
+
```sh
|
|
260
|
+
yarn rust:build:web
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
This compiles `rust/` to `rust/build/web/pkg/` with `wasm-pack build --target web`, reusing the same `rust/src/api/<method>.rs` handlers as native — no separate implementation is needed. It replaces the generated `src/rust-generated.tsx` module (used by web bundlers that don't understand Metro's `.native.` file convention, such as Vite or webpack) with a real, WASM-backed implementation instead of the "not supported on native platforms" stub used before this module exists. Call and await its exported `initRustWeb()` once before using any generated function on web, since loading a `.wasm` file is asynchronous:
|
|
264
|
+
|
|
265
|
+
```ts
|
|
266
|
+
import { initRustWeb, multiply } from 'react-native-awesome-library';
|
|
267
|
+
|
|
268
|
+
await initRustWeb();
|
|
269
|
+
multiply(3, 7); // 21
|
|
270
|
+
```
|
|
271
|
+
|
|
153
272
|
## Supported types
|
|
154
273
|
|
|
155
274
|
The generator supports these TypeScript-to-Rust/C++ mappings:
|
|
@@ -169,6 +288,8 @@ Arrays and `UnsafeObject` values cross the native boundary as JSON, so their con
|
|
|
169
288
|
|
|
170
289
|
Typed object structs, optional or rest parameters, destructured parameters, generic methods, and overloads are not supported. Unsupported signatures fail before generated files are changed.
|
|
171
290
|
|
|
291
|
+
The same mappings apply on `react-native-web`: values cross as JSON strings via `wasm-bindgen` instead of the JSI C++ bridge, and callback parameters are passed as a JS function. See [Web](#web) for the build step and the async `initRustWeb()` call it requires.
|
|
292
|
+
|
|
172
293
|
## Publish
|
|
173
294
|
|
|
174
295
|
Before publishing a generated React Native library, generate its TypeScript/native outputs and build the Rust artifacts for the platforms you intend to ship:
|
|
@@ -177,6 +298,7 @@ Before publishing a generated React Native library, generate its TypeScript/nati
|
|
|
177
298
|
yarn prepare
|
|
178
299
|
yarn rust:build:ios
|
|
179
300
|
yarn rust:build:android
|
|
301
|
+
yarn rust:build:web
|
|
180
302
|
npm pack --dry-run
|
|
181
303
|
```
|
|
182
304
|
|
|
@@ -196,7 +318,12 @@ Pushing a version tag such as `v1.0.2` runs [`.github/workflows/publish.yml`](.g
|
|
|
196
318
|
- Android CMake cannot find `android/generated/jni`: run `yarn prepare` from the library root.
|
|
197
319
|
- Android CMake cannot find a Rust `.a` archive: run `yarn rust:build:android` before launching the example.
|
|
198
320
|
- iOS reports a missing Rust XCFramework: run `yarn rust:build:ios`, then `pod install` in `example/ios`.
|
|
321
|
+
- `wasm-pack: command not found`: install it with `cargo install wasm-pack`, or run `react-native-rust doctor web` to check both prerequisites.
|
|
322
|
+
- The web app throws "Call and await initRustWeb()...": await `initRustWeb()` once before calling any other generated function; loading the `.wasm` file is asynchronous even though the generated calls are not.
|
|
199
323
|
- A React Native type is reported as unsupported: check the mappings and signature restrictions in [Supported types](#supported-types).
|
|
324
|
+
- `react-native-rust watch` prints "Rust is not initialized": run `react-native-rust init` (or `init --app`) first; the watcher requires `rust/Cargo.toml` to exist.
|
|
325
|
+
- Changes made in a running app never appear, even after "`[Rust] Build completed`": native code cannot hot-swap into a running process. Run `npm run rust:build:ios` / `rust:build:android` and reinstall the app, as [Automatic development mode](#automatic-development-mode) describes.
|
|
326
|
+
- `npx react-native start` doesn't print any `[Rust]` lines: confirm `metro.config.js` calls `withRust(...)` and that `rust/Cargo.toml` exists at the project root; otherwise run `npx react-native-rust watch` in a separate terminal.
|
|
200
327
|
|
|
201
328
|
## Contributing
|
|
202
329
|
|
package/dist/app.js
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
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 app_exports = {};
|
|
30
|
+
__export(app_exports, {
|
|
31
|
+
appModuleRoot: () => appModuleRoot,
|
|
32
|
+
inDirectory: () => inDirectory,
|
|
33
|
+
initApp: () => initApp,
|
|
34
|
+
runCodegen: () => runCodegen
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(app_exports);
|
|
37
|
+
var import_node_fs = __toESM(require("node:fs"));
|
|
38
|
+
var import_node_path = __toESM(require("node:path"));
|
|
39
|
+
var import_node_child_process = require("node:child_process");
|
|
40
|
+
var import_react_native_rust_lib = require("./react-native-rust-lib");
|
|
41
|
+
const moduleDirectory = ".rust-native";
|
|
42
|
+
const moduleName = "RustApp";
|
|
43
|
+
const packageName = "rust-app-native-module";
|
|
44
|
+
function writeFiles(root, files) {
|
|
45
|
+
for (const [relativePath, contents] of Object.entries(files)) {
|
|
46
|
+
const outputPath = import_node_path.default.join(root, relativePath);
|
|
47
|
+
import_node_fs.default.mkdirSync(import_node_path.default.dirname(outputPath), { recursive: true });
|
|
48
|
+
import_node_fs.default.writeFileSync(outputPath, contents);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function scaffoldFiles(appName) {
|
|
52
|
+
const javaPackageName = appName.replace(/([a-z0-9])([A-Z])/g, "$1.$2").replace(/[^a-zA-Z0-9]+/g, ".").replace(/^\.+|\.+$/g, "").toLowerCase() || "com.rustapp";
|
|
53
|
+
return {
|
|
54
|
+
"package.json": `${JSON.stringify({
|
|
55
|
+
name: packageName,
|
|
56
|
+
version: "0.0.1",
|
|
57
|
+
description: "App-local Rust TurboModule",
|
|
58
|
+
main: "src/index.tsx",
|
|
59
|
+
"react-native": "src/index.tsx",
|
|
60
|
+
types: "src/index.tsx",
|
|
61
|
+
files: ["src/", "cpp/", "android/", "ios/", "*.podspec", "react-native.config.js"],
|
|
62
|
+
peerDependencies: { react: "*", "react-native": ">=0.76" },
|
|
63
|
+
codegenConfig: {
|
|
64
|
+
name: `${moduleName}Spec`,
|
|
65
|
+
type: "modules",
|
|
66
|
+
jsSrcsDir: "src",
|
|
67
|
+
outputDir: { ios: "ios/generated", android: "android/generated" },
|
|
68
|
+
android: { javaPackageName },
|
|
69
|
+
includesGeneratedCode: true
|
|
70
|
+
},
|
|
71
|
+
// The Rust crate lives at the app's own root, not inside this generated module package.
|
|
72
|
+
reactNativeRust: { rustDir: "../rust" }
|
|
73
|
+
}, null, 2)}
|
|
74
|
+
`,
|
|
75
|
+
"react-native.config.js": `module.exports = {
|
|
76
|
+
dependency: {
|
|
77
|
+
platforms: {
|
|
78
|
+
android: {
|
|
79
|
+
cmakeListsPath: 'generated/jni/CMakeLists.txt',
|
|
80
|
+
cxxModuleCMakeListsModuleName: '${packageName}',
|
|
81
|
+
cxxModuleCMakeListsPath: 'CMakeLists.txt',
|
|
82
|
+
cxxModuleHeaderName: '${moduleName}Impl',
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
`,
|
|
88
|
+
[`${moduleName}.podspec`]: `Pod::Spec.new do |s|
|
|
89
|
+
s.name = "${moduleName}"
|
|
90
|
+
s.version = "0.0.1"
|
|
91
|
+
s.summary = "App-local Rust TurboModule"
|
|
92
|
+
s.homepage = "https://example.invalid"
|
|
93
|
+
s.license = "MIT"
|
|
94
|
+
s.author = "Local app"
|
|
95
|
+
s.source = { :git => "https://example.invalid" }
|
|
96
|
+
s.platforms = { :ios => min_ios_version_supported }
|
|
97
|
+
s.source_files = "cpp/**/*.{hpp,cpp,c,h}", "ios/**/*.{h,m,mm}", "ios/generated/*.{h,cpp,mm}"
|
|
98
|
+
install_modules_dependencies(s)
|
|
99
|
+
end
|
|
100
|
+
`,
|
|
101
|
+
// rust-include, rust-build-android, rust-build-ios: generated by `init`/`build` to reach
|
|
102
|
+
// the Rust crate at the app's own root (see resolveOutsideReference) — a real symlink or, for
|
|
103
|
+
// iOS, a copied build artifact, never hand-authored.
|
|
104
|
+
".gitignore": "node_modules/\nrust-include\nrust-build-android\nrust-build-ios\nrust-build-web-pkg\n",
|
|
105
|
+
"src/NativeRustApp.ts": `import { TurboModuleRegistry, type TurboModule } from 'react-native';
|
|
106
|
+
|
|
107
|
+
export interface Spec extends TurboModule {
|
|
108
|
+
multiply(a: number, b: number): number;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export default TurboModuleRegistry.getEnforcing<Spec>('${moduleName}');
|
|
112
|
+
`,
|
|
113
|
+
"src/index.tsx": "export * from './rust-generated';\n",
|
|
114
|
+
"cpp/RustAppImpl.h": `#pragma once
|
|
115
|
+
|
|
116
|
+
#include <${moduleName}SpecJSI.h>
|
|
117
|
+
|
|
118
|
+
#include <memory>
|
|
119
|
+
|
|
120
|
+
namespace facebook::react {
|
|
121
|
+
|
|
122
|
+
class ${moduleName}Impl : public Native${moduleName}CxxSpec<${moduleName}Impl> {
|
|
123
|
+
public:
|
|
124
|
+
${moduleName}Impl(std::shared_ptr<CallInvoker> jsInvoker);
|
|
125
|
+
double multiply(jsi::Runtime& rt, double a, double b);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
}
|
|
129
|
+
`,
|
|
130
|
+
"cpp/RustAppImpl.cpp": `#include "RustAppImpl.h"
|
|
131
|
+
|
|
132
|
+
namespace facebook::react {
|
|
133
|
+
|
|
134
|
+
${moduleName}Impl::${moduleName}Impl(std::shared_ptr<CallInvoker> jsInvoker)
|
|
135
|
+
: Native${moduleName}CxxSpec(std::move(jsInvoker)) {}
|
|
136
|
+
|
|
137
|
+
double ${moduleName}Impl::multiply(jsi::Runtime&, double a, double b) {
|
|
138
|
+
return a * b;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
}
|
|
142
|
+
`,
|
|
143
|
+
"android/CMakeLists.txt": `cmake_minimum_required(VERSION 3.13)
|
|
144
|
+
set(CMAKE_VERBOSE_MAKEFILE ON)
|
|
145
|
+
|
|
146
|
+
add_library(
|
|
147
|
+
${packageName}
|
|
148
|
+
STATIC
|
|
149
|
+
../cpp/${moduleName}Impl.cpp
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
set_target_properties(
|
|
153
|
+
${packageName} PROPERTIES
|
|
154
|
+
CXX_STANDARD 20
|
|
155
|
+
CXX_STANDARD_REQUIRED ON
|
|
156
|
+
CXX_EXTENSIONS OFF
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
target_include_directories(${packageName} PUBLIC ../cpp)
|
|
160
|
+
|
|
161
|
+
target_link_libraries(
|
|
162
|
+
${packageName}
|
|
163
|
+
jsi
|
|
164
|
+
reactnative
|
|
165
|
+
react_codegen_${moduleName}Spec
|
|
166
|
+
)
|
|
167
|
+
`,
|
|
168
|
+
"ios/OnLoad.mm": `#import <Foundation/Foundation.h>
|
|
169
|
+
#import "${moduleName}Impl.h"
|
|
170
|
+
#import <ReactCommon/CxxTurboModuleUtils.h>
|
|
171
|
+
|
|
172
|
+
@interface ${moduleName}OnLoad : NSObject
|
|
173
|
+
@end
|
|
174
|
+
|
|
175
|
+
@implementation ${moduleName}OnLoad
|
|
176
|
+
|
|
177
|
+
using namespace facebook::react;
|
|
178
|
+
|
|
179
|
+
+ (void)load
|
|
180
|
+
{
|
|
181
|
+
registerCxxModuleToGlobalModuleMap(
|
|
182
|
+
std::string(${moduleName}Impl::kModuleName),
|
|
183
|
+
[](std::shared_ptr<CallInvoker> jsInvoker) {
|
|
184
|
+
return std::make_shared<${moduleName}Impl>(jsInvoker);
|
|
185
|
+
}
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
@end
|
|
190
|
+
`
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
function appModuleRoot(appRoot = process.cwd()) {
|
|
194
|
+
const moduleRoot = import_node_path.default.join(appRoot, moduleDirectory);
|
|
195
|
+
if (!import_node_fs.default.existsSync(import_node_path.default.join(moduleRoot, "package.json"))) return null;
|
|
196
|
+
return import_node_fs.default.existsSync(import_node_path.default.join(appRoot, "rust", "Cargo.toml")) ? moduleRoot : null;
|
|
197
|
+
}
|
|
198
|
+
function inDirectory(directory, callback) {
|
|
199
|
+
const previousDirectory = process.cwd();
|
|
200
|
+
process.chdir(directory);
|
|
201
|
+
try {
|
|
202
|
+
return callback();
|
|
203
|
+
} finally {
|
|
204
|
+
process.chdir(previousDirectory);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function runCodegen(appRoot, moduleRoot) {
|
|
208
|
+
const script = import_node_path.default.join(appRoot, "node_modules", "react-native", "scripts", "generate-codegen-artifacts.js");
|
|
209
|
+
if (!import_node_fs.default.existsSync(script)) {
|
|
210
|
+
throw new Error("React Native dependencies are missing. Install the app dependencies, then run `react-native-rust generate`.");
|
|
211
|
+
}
|
|
212
|
+
const result = (0, import_node_child_process.spawnSync)(process.execPath, [script, "-p", moduleRoot, "-t", "all", "-s", "library"], {
|
|
213
|
+
cwd: appRoot,
|
|
214
|
+
encoding: "utf8",
|
|
215
|
+
stdio: "pipe"
|
|
216
|
+
});
|
|
217
|
+
if (result.error || result.status !== 0) {
|
|
218
|
+
throw new Error(`React Native Codegen failed: ${(result.stderr || result.stdout || result.error?.message || "").trim()}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
function initApp() {
|
|
222
|
+
const appRoot = process.cwd();
|
|
223
|
+
const appManifestPath = import_node_path.default.join(appRoot, "package.json");
|
|
224
|
+
if (!import_node_fs.default.existsSync(appManifestPath)) throw new Error("Run `react-native-rust init --app` from a React Native app root.");
|
|
225
|
+
const appManifest = (0, import_react_native_rust_lib.readJson)(appManifestPath);
|
|
226
|
+
if (!(appManifest.dependencies || {})["react-native"]) throw new Error("This project does not declare react-native as a dependency.");
|
|
227
|
+
if (!import_node_fs.default.existsSync(import_node_path.default.join(appRoot, "android")) || !import_node_fs.default.existsSync(import_node_path.default.join(appRoot, "ios"))) {
|
|
228
|
+
throw new Error("App-local Rust currently requires both Android and iOS native project folders.");
|
|
229
|
+
}
|
|
230
|
+
const moduleRoot = import_node_path.default.join(appRoot, moduleDirectory);
|
|
231
|
+
if (import_node_fs.default.existsSync(moduleRoot)) throw new Error(`${moduleDirectory} already exists; no files were changed.`);
|
|
232
|
+
import_node_fs.default.mkdirSync(moduleRoot, { recursive: true });
|
|
233
|
+
try {
|
|
234
|
+
writeFiles(moduleRoot, scaffoldFiles(appManifest.name || "rust-app"));
|
|
235
|
+
inDirectory(moduleRoot, import_react_native_rust_lib.init);
|
|
236
|
+
const cliManifest = (0, import_react_native_rust_lib.readJson)(import_node_path.default.join(__dirname, "..", "package.json"));
|
|
237
|
+
const cliRoot = import_node_path.default.resolve(__dirname, "..");
|
|
238
|
+
const cliIsInstalled = cliRoot.split(import_node_path.default.sep).includes("node_modules");
|
|
239
|
+
const cliDependency = cliIsInstalled ? `^${cliManifest.version}` : `file:${import_node_path.default.relative(appRoot, cliRoot).split(import_node_path.default.sep).join("/") || "."}`;
|
|
240
|
+
const moduleManifestPath = import_node_path.default.join(moduleRoot, "package.json");
|
|
241
|
+
const moduleManifest = (0, import_react_native_rust_lib.readJson)(moduleManifestPath);
|
|
242
|
+
delete moduleManifest.devDependencies?.[cliManifest.name];
|
|
243
|
+
import_node_fs.default.writeFileSync(moduleManifestPath, `${JSON.stringify(moduleManifest, null, 2)}
|
|
244
|
+
`);
|
|
245
|
+
appManifest.dependencies = {
|
|
246
|
+
...appManifest.dependencies,
|
|
247
|
+
[packageName]: `file:./${moduleDirectory.split(import_node_path.default.sep).join("/")}`
|
|
248
|
+
};
|
|
249
|
+
appManifest.devDependencies = {
|
|
250
|
+
...appManifest.devDependencies,
|
|
251
|
+
[cliManifest.name]: appManifest.devDependencies?.[cliManifest.name] || cliDependency
|
|
252
|
+
};
|
|
253
|
+
appManifest.scripts = {
|
|
254
|
+
...appManifest.scripts,
|
|
255
|
+
"rust:generate": "react-native-rust generate",
|
|
256
|
+
"rust:build:ios": "react-native-rust build ios",
|
|
257
|
+
"rust:build:android": "react-native-rust build android",
|
|
258
|
+
"rust:test": "cargo test --manifest-path rust/Cargo.toml"
|
|
259
|
+
};
|
|
260
|
+
runCodegen(appRoot, moduleRoot);
|
|
261
|
+
import_node_fs.default.writeFileSync(appManifestPath, `${JSON.stringify(appManifest, null, 2)}
|
|
262
|
+
`);
|
|
263
|
+
const gitignorePath = import_node_path.default.join(appRoot, ".gitignore");
|
|
264
|
+
const gitignore = import_node_fs.default.existsSync(gitignorePath) ? import_node_fs.default.readFileSync(gitignorePath, "utf8") : "";
|
|
265
|
+
const ignoredRustOutputs = ["rust/target/", "rust/build/"].filter((entry) => !gitignore.split(/\r?\n/).includes(entry));
|
|
266
|
+
if (ignoredRustOutputs.length > 0) {
|
|
267
|
+
const separator = gitignore.length > 0 && !gitignore.endsWith("\n") ? "\n" : "";
|
|
268
|
+
import_node_fs.default.writeFileSync(gitignorePath, `${gitignore}${separator}${ignoredRustOutputs.join("\n")}
|
|
269
|
+
`);
|
|
270
|
+
}
|
|
271
|
+
} catch (error) {
|
|
272
|
+
import_node_fs.default.rmSync(moduleRoot, { recursive: true, force: true });
|
|
273
|
+
import_node_fs.default.rmSync(import_node_path.default.join(appRoot, "rust"), { recursive: true, force: true });
|
|
274
|
+
throw error;
|
|
275
|
+
}
|
|
276
|
+
console.log("Created app-local Rust TurboModule: edit rust/src/api/, glue lives in .rust-native/ (generated, do not edit).");
|
|
277
|
+
console.log("Install app dependencies, then run `npm run rust:build:android` or `npm run rust:build:ios`.");
|
|
278
|
+
}
|
|
279
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
280
|
+
0 && (module.exports = {
|
|
281
|
+
appModuleRoot,
|
|
282
|
+
inDirectory,
|
|
283
|
+
initApp,
|
|
284
|
+
runCodegen
|
|
285
|
+
});
|
|
@@ -0,0 +1,168 @@
|
|
|
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 constants_exports = {};
|
|
30
|
+
__export(constants_exports, {
|
|
31
|
+
HEADER_END: () => HEADER_END,
|
|
32
|
+
HEADER_START: () => HEADER_START,
|
|
33
|
+
SOURCE_END: () => SOURCE_END,
|
|
34
|
+
SOURCE_START: () => SOURCE_START,
|
|
35
|
+
cppKeywords: () => cppKeywords,
|
|
36
|
+
rustKeywords: () => rustKeywords,
|
|
37
|
+
supportedTypes: () => supportedTypes,
|
|
38
|
+
ts: () => import_typescript.default
|
|
39
|
+
});
|
|
40
|
+
module.exports = __toCommonJS(constants_exports);
|
|
41
|
+
var import_typescript = __toESM(require("typescript"));
|
|
42
|
+
const HEADER_START = "// react-native-rust:generated-methods:start";
|
|
43
|
+
const HEADER_END = "// react-native-rust:generated-methods:end";
|
|
44
|
+
const SOURCE_START = "// react-native-rust:generated-methods:start";
|
|
45
|
+
const SOURCE_END = "// react-native-rust:generated-methods:end";
|
|
46
|
+
const rustKeywords = /* @__PURE__ */ new Set([
|
|
47
|
+
"as",
|
|
48
|
+
"async",
|
|
49
|
+
"await",
|
|
50
|
+
"break",
|
|
51
|
+
"const",
|
|
52
|
+
"continue",
|
|
53
|
+
"crate",
|
|
54
|
+
"dyn",
|
|
55
|
+
"else",
|
|
56
|
+
"enum",
|
|
57
|
+
"extern",
|
|
58
|
+
"false",
|
|
59
|
+
"fn",
|
|
60
|
+
"for",
|
|
61
|
+
"if",
|
|
62
|
+
"impl",
|
|
63
|
+
"in",
|
|
64
|
+
"let",
|
|
65
|
+
"loop",
|
|
66
|
+
"match",
|
|
67
|
+
"mod",
|
|
68
|
+
"move",
|
|
69
|
+
"mut",
|
|
70
|
+
"pub",
|
|
71
|
+
"ref",
|
|
72
|
+
"return",
|
|
73
|
+
"self",
|
|
74
|
+
"Self",
|
|
75
|
+
"static",
|
|
76
|
+
"struct",
|
|
77
|
+
"super",
|
|
78
|
+
"trait",
|
|
79
|
+
"true",
|
|
80
|
+
"type",
|
|
81
|
+
"unsafe",
|
|
82
|
+
"use",
|
|
83
|
+
"where",
|
|
84
|
+
"while"
|
|
85
|
+
]);
|
|
86
|
+
const cppKeywords = /* @__PURE__ */ new Set([
|
|
87
|
+
"alignas",
|
|
88
|
+
"alignof",
|
|
89
|
+
"and",
|
|
90
|
+
"asm",
|
|
91
|
+
"auto",
|
|
92
|
+
"bool",
|
|
93
|
+
"break",
|
|
94
|
+
"case",
|
|
95
|
+
"catch",
|
|
96
|
+
"char",
|
|
97
|
+
"class",
|
|
98
|
+
"const",
|
|
99
|
+
"continue",
|
|
100
|
+
"default",
|
|
101
|
+
"delete",
|
|
102
|
+
"do",
|
|
103
|
+
"double",
|
|
104
|
+
"else",
|
|
105
|
+
"enum",
|
|
106
|
+
"explicit",
|
|
107
|
+
"export",
|
|
108
|
+
"extern",
|
|
109
|
+
"false",
|
|
110
|
+
"float",
|
|
111
|
+
"for",
|
|
112
|
+
"friend",
|
|
113
|
+
"goto",
|
|
114
|
+
"if",
|
|
115
|
+
"inline",
|
|
116
|
+
"int",
|
|
117
|
+
"long",
|
|
118
|
+
"namespace",
|
|
119
|
+
"new",
|
|
120
|
+
"noexcept",
|
|
121
|
+
"not",
|
|
122
|
+
"nullptr",
|
|
123
|
+
"operator",
|
|
124
|
+
"or",
|
|
125
|
+
"private",
|
|
126
|
+
"protected",
|
|
127
|
+
"public",
|
|
128
|
+
"register",
|
|
129
|
+
"return",
|
|
130
|
+
"short",
|
|
131
|
+
"signed",
|
|
132
|
+
"sizeof",
|
|
133
|
+
"static",
|
|
134
|
+
"struct",
|
|
135
|
+
"switch",
|
|
136
|
+
"template",
|
|
137
|
+
"this",
|
|
138
|
+
"throw",
|
|
139
|
+
"true",
|
|
140
|
+
"try",
|
|
141
|
+
"typedef",
|
|
142
|
+
"typename",
|
|
143
|
+
"union",
|
|
144
|
+
"unsigned",
|
|
145
|
+
"using",
|
|
146
|
+
"virtual",
|
|
147
|
+
"void",
|
|
148
|
+
"volatile",
|
|
149
|
+
"while",
|
|
150
|
+
"xor"
|
|
151
|
+
]);
|
|
152
|
+
const supportedTypes = /* @__PURE__ */ new Map([
|
|
153
|
+
[import_typescript.default.SyntaxKind.NumberKeyword, { kind: "number", typescript: "number", rust: "f64", cpp: "double", defaultValue: "0.0" }],
|
|
154
|
+
[import_typescript.default.SyntaxKind.BooleanKeyword, { kind: "boolean", typescript: "boolean", rust: "bool", cpp: "bool", defaultValue: "false" }],
|
|
155
|
+
[import_typescript.default.SyntaxKind.StringKeyword, { kind: "json", typescript: "string", rust: "String", cpp: "jsi::String", defaultValue: "String::new()" }],
|
|
156
|
+
[import_typescript.default.SyntaxKind.VoidKeyword, { kind: "void", typescript: "void", rust: "()", cpp: "void", defaultValue: null }]
|
|
157
|
+
]);
|
|
158
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
159
|
+
0 && (module.exports = {
|
|
160
|
+
HEADER_END,
|
|
161
|
+
HEADER_START,
|
|
162
|
+
SOURCE_END,
|
|
163
|
+
SOURCE_START,
|
|
164
|
+
cppKeywords,
|
|
165
|
+
rustKeywords,
|
|
166
|
+
supportedTypes,
|
|
167
|
+
ts
|
|
168
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
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 __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var codegen_exports = {};
|
|
20
|
+
__export(codegen_exports, {
|
|
21
|
+
parseSpec: () => import_spec.parseSpec,
|
|
22
|
+
renderProjectBindings: () => import_project.renderProjectBindings,
|
|
23
|
+
renderRustFfiModule: () => import_renderers.renderRustFfiModule,
|
|
24
|
+
resolveOutsideReference: () => import_project.resolveOutsideReference,
|
|
25
|
+
toPosixRelative: () => import_project.toPosixRelative
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(codegen_exports);
|
|
28
|
+
var import_spec = require("./spec");
|
|
29
|
+
var import_renderers = require("./renderers");
|
|
30
|
+
var import_project = require("./project");
|
|
31
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
32
|
+
0 && (module.exports = {
|
|
33
|
+
parseSpec,
|
|
34
|
+
renderProjectBindings,
|
|
35
|
+
renderRustFfiModule,
|
|
36
|
+
resolveOutsideReference,
|
|
37
|
+
toPosixRelative
|
|
38
|
+
});
|