From 70c74d97ebae1d823b1a428e99033a0b0f9fc408 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Mon, 3 Nov 2025 17:16:36 +0100 Subject: [PATCH 01/14] Don't export weakNodeApiPath from host --- .changeset/spotty-beers-repeat.md | 5 +++++ packages/host/src/node/index.ts | 2 -- 2 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 .changeset/spotty-beers-repeat.md diff --git a/.changeset/spotty-beers-repeat.md b/.changeset/spotty-beers-repeat.md new file mode 100644 index 00000000..cfa21deb --- /dev/null +++ b/.changeset/spotty-beers-repeat.md @@ -0,0 +1,5 @@ +--- +"react-native-node-api": minor +--- + +No longer exporting weakNodeApiPath, import from "weak-node-api" instead diff --git a/packages/host/src/node/index.ts b/packages/host/src/node/index.ts index 3071f233..1c4c69a9 100644 --- a/packages/host/src/node/index.ts +++ b/packages/host/src/node/index.ts @@ -26,5 +26,3 @@ export { determineLibraryBasename, dereferenceDirectory, } from "./path-utils.js"; - -export { weakNodeApiPath } from "./weak-node-api.js"; From 194dd4aec7cde17313ac89568cb58c499d314e7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Mon, 3 Nov 2025 17:51:01 +0100 Subject: [PATCH 02/14] Move weak-node-api into its own package --- .changeset/eight-walls-push.md | 5 ++ .changeset/tired-words-relate.md | 5 ++ .github/workflows/check.yml | 18 +++---- .gitignore | 3 ++ package-lock.json | 21 ++++++-- package.json | 3 +- packages/cmake-rn/src/weak-node-api.ts | 22 ++++---- packages/ferric/src/cargo.ts | 13 ++--- packages/host/.gitignore | 7 --- packages/host/package.json | 24 ++------- ...ts => generate-weak-node-api-injector.mts} | 4 +- packages/host/src/node/cli/program.ts | 12 ++--- packages/host/src/node/weak-node-api.ts | 10 ---- packages/host/tsconfig.node-scripts.json | 9 +++- packages/host/tsconfig.node.json | 7 ++- packages/weak-node-api/.gitignore | 11 ++++ .../{host => }/weak-node-api/CMakeLists.txt | 2 +- packages/weak-node-api/package.json | 52 +++++++++++++++++++ .../scripts/copy-node-api-headers.ts | 2 +- .../scripts/generate-weak-node-api.ts | 13 +++-- packages/weak-node-api/src/index.ts | 2 + .../src}/node-api-functions.ts | 0 packages/weak-node-api/src/weak-node-api.ts | 21 ++++++++ packages/weak-node-api/tsconfig.json | 10 ++++ .../weak-node-api/tsconfig.node-scripts.json | 13 +++++ packages/weak-node-api/tsconfig.node.json | 12 +++++ .../types/node-api-headers/index.d.ts | 0 .../weak-node-api/weak-node-api.cmake | 0 tsconfig.json | 3 +- 29 files changed, 212 insertions(+), 92 deletions(-) create mode 100644 .changeset/eight-walls-push.md create mode 100644 .changeset/tired-words-relate.md rename packages/host/scripts/{generate-weak-node-api-injector.ts => generate-weak-node-api-injector.mts} (94%) delete mode 100644 packages/host/src/node/weak-node-api.ts create mode 100644 packages/weak-node-api/.gitignore rename packages/{host => }/weak-node-api/CMakeLists.txt (96%) create mode 100644 packages/weak-node-api/package.json rename packages/{host => weak-node-api}/scripts/copy-node-api-headers.ts (82%) rename packages/{host => weak-node-api}/scripts/generate-weak-node-api.ts (87%) create mode 100644 packages/weak-node-api/src/index.ts rename packages/{host/scripts => weak-node-api/src}/node-api-functions.ts (100%) create mode 100644 packages/weak-node-api/src/weak-node-api.ts create mode 100644 packages/weak-node-api/tsconfig.json create mode 100644 packages/weak-node-api/tsconfig.node-scripts.json create mode 100644 packages/weak-node-api/tsconfig.node.json rename packages/{host => weak-node-api}/types/node-api-headers/index.d.ts (100%) rename packages/{host => }/weak-node-api/weak-node-api.cmake (100%) diff --git a/.changeset/eight-walls-push.md b/.changeset/eight-walls-push.md new file mode 100644 index 00000000..58793ae4 --- /dev/null +++ b/.changeset/eight-walls-push.md @@ -0,0 +1,5 @@ +--- +"react-native-node-api": patch +--- + +Moved weak-node-api into a separate "weak-node-api" package. diff --git a/.changeset/tired-words-relate.md b/.changeset/tired-words-relate.md new file mode 100644 index 00000000..e1a3ec02 --- /dev/null +++ b/.changeset/tired-words-relate.md @@ -0,0 +1,5 @@ +--- +"weak-node-api": patch +--- + +Initial release! diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index a221cfc4..f24d9c04 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -40,9 +40,9 @@ jobs: - run: rustup target add x86_64-linux-android - run: npm ci - run: npm run build - # Bootstrap host package to get weak-node-api and ferric-example to get types + # Bootstrap weak-node-api and ferric-example to get types # TODO: Solve this by adding an option to ferric to build only types or by committing the types into the repo as a fixture for an "init" command - - run: npm run bootstrap --workspace react-native-node-api + - run: npm run bootstrap --workspace weak-node-api - run: npm run bootstrap --workspace @react-native-node-api/ferric-example - run: npm run lint env: @@ -184,9 +184,8 @@ jobs: echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules sudo udevadm control --reload-rules sudo udevadm trigger --name-match=kvm - - name: Build weak-node-api for all architectures - run: npm run build-weak-node-api:android - working-directory: packages/host + - name: Build weak-node-api for all Android architectures + run: npm run build-weak-node-api:android --workspace weak-node-api - name: Build ferric-example for all architectures run: npm run build -- --android working-directory: packages/ferric-example @@ -239,11 +238,10 @@ jobs: - run: rustup toolchain install nightly --component rust-src - run: npm ci - run: npm run build - # Build weak-node-api for all Apple architectures - - run: | - npm run prepare-weak-node-api - npm run build-weak-node-api:apple - working-directory: packages/host + - name: Build weak-node-api for all Apple architectures + run: | + npm run prepare-weak-node-api --workspace weak-node-api + npm run build-weak-node-api:apple --workspace weak-node-api # Build Ferric example for all Apple architectures - run: npx ferric --apple working-directory: packages/ferric-example diff --git a/.gitignore b/.gitignore index 6c7c7bf3..6a69674c 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ dist/ # Treading the MacOS app as ephemeral apps/macos-test-app + +# Cache used by the rust analyzer +target/rust-analyzer/ diff --git a/package-lock.json b/package-lock.json index 9604cb48..6baca096 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "packages/node-addon-examples", "packages/node-tests", "packages/ferric-example", + "packages/weak-node-api", "apps/test-app" ], "devDependencies": { @@ -32,7 +33,7 @@ "prettier": "^3.6.2", "react-native": "0.81.4", "read-pkg": "^9.0.1", - "tsx": "^4.20.5", + "tsx": "^4.20.6", "typescript": "^5.8.0", "typescript-eslint": "^8.38.0" } @@ -14006,9 +14007,9 @@ "license": "0BSD" }, "node_modules/tsx": { - "version": "4.20.5", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.5.tgz", - "integrity": "sha512-+wKjMNU9w/EaQayHXb7WA7ZaHY6hN8WgfvHNQ3t1PnU91/7O8TcTnIhCDYTZwnt8JsO9IBqZ30Ln1r7pPF52Aw==", + "version": "4.20.6", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz", + "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", "dev": true, "license": "MIT", "dependencies": { @@ -14355,6 +14356,10 @@ "defaults": "^1.0.3" } }, + "node_modules/weak-node-api": { + "resolved": "packages/weak-node-api", + "link": true + }, "node_modules/whatwg-fetch": { "version": "3.6.20", "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", @@ -14880,6 +14885,7 @@ "@react-native-node-api/cli-utils": "0.1.1", "pkg-dir": "^8.0.0", "read-pkg": "^9.0.1", + "weak-node-api": "0.1.0", "zod": "^4.1.11" }, "bin": { @@ -14920,6 +14926,13 @@ "read-pkg": "^9.0.1", "rolldown": "1.0.0-beta.29" } + }, + "packages/weak-node-api": { + "version": "0.1.0", + "devDependencies": { + "node-api-headers": "^1.5.0", + "zod": "^4.1.11" + } } } } diff --git a/package.json b/package.json index be2dbf31..668f6c31 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "workspaces": [ "packages/cli-utils", "packages/cmake-file-api", + "packages/weak-node-api", "packages/cmake-rn", "packages/ferric", "packages/gyp-to-cmake", @@ -66,7 +67,7 @@ "prettier": "^3.6.2", "react-native": "0.81.4", "read-pkg": "^9.0.1", - "tsx": "^4.20.5", + "tsx": "^4.20.6", "typescript": "^5.8.0", "typescript-eslint": "^8.38.0" } diff --git a/packages/cmake-rn/src/weak-node-api.ts b/packages/cmake-rn/src/weak-node-api.ts index ee6cb154..6eea7876 100644 --- a/packages/cmake-rn/src/weak-node-api.ts +++ b/packages/cmake-rn/src/weak-node-api.ts @@ -6,9 +6,14 @@ import { isAndroidTriplet, isAppleTriplet, SupportedTriplet, - weakNodeApiPath, } from "react-native-node-api"; +import { + applePrebuildPath, + androidPrebuildPath, + weakNodeApiCmakePath, +} from "weak-node-api"; + import { ANDROID_ARCHITECTURES } from "./platforms/android.js"; import { getNodeAddonHeadersPath, getNodeApiHeadersPath } from "./headers.js"; @@ -20,19 +25,14 @@ export function getWeakNodeApiPath( triplet: SupportedTriplet | "apple", ): string { if (triplet === "apple" || isAppleTriplet(triplet)) { - const xcframeworkPath = path.join( - weakNodeApiPath, - "weak-node-api.xcframework", - ); assert( - fs.existsSync(xcframeworkPath), - `Expected an XCFramework at ${xcframeworkPath}`, + fs.existsSync(applePrebuildPath), + `Expected an XCFramework at ${applePrebuildPath}`, ); - return xcframeworkPath; + return applePrebuildPath; } else if (isAndroidTriplet(triplet)) { const libraryPath = path.join( - weakNodeApiPath, - "weak-node-api.android.node", + androidPrebuildPath, ANDROID_ARCHITECTURES[triplet], "libweak-node-api.so", ); @@ -59,7 +59,7 @@ export function getWeakNodeApiVariables( ): Record { return { // Expose an includable CMake config file declaring the weak-node-api target - WEAK_NODE_API_CONFIG: path.join(weakNodeApiPath, "weak-node-api.cmake"), + WEAK_NODE_API_CONFIG: weakNodeApiCmakePath, WEAK_NODE_API_INC: getNodeApiIncludePaths().join(";"), WEAK_NODE_API_LIB: getWeakNodeApiPath(triplet), }; diff --git a/packages/ferric/src/cargo.ts b/packages/ferric/src/cargo.ts index 4671a47c..4eee45b2 100644 --- a/packages/ferric/src/cargo.ts +++ b/packages/ferric/src/cargo.ts @@ -9,7 +9,7 @@ import { UsageError, spawn, } from "@react-native-node-api/cli-utils"; -import { weakNodeApiPath } from "react-native-node-api"; +import { applePrebuildPath, androidPrebuildPath } from "weak-node-api"; import { AndroidTargetName, @@ -169,25 +169,20 @@ export function getTargetAndroidPlatform(target: AndroidTargetName) { } export function getWeakNodeApiFrameworkPath(target: AppleTargetName) { - const xcframeworkPath = joinPathAndAssertExistence( - weakNodeApiPath, - "weak-node-api.xcframework", - ); const result = APPLE_XCFRAMEWORK_SLICES_PER_TARGET[target].find((slice) => { - const candidatePath = path.join(xcframeworkPath, slice); + const candidatePath = path.join(applePrebuildPath, slice); return fs.existsSync(candidatePath); }); assert( result, `No matching slice found in weak-node-api.xcframework for target ${target}`, ); - return joinPathAndAssertExistence(xcframeworkPath, result); + return joinPathAndAssertExistence(applePrebuildPath, result); } export function getWeakNodeApiAndroidLibraryPath(target: AndroidTargetName) { return joinPathAndAssertExistence( - weakNodeApiPath, - "weak-node-api.android.node", + androidPrebuildPath, ANDROID_ARCH_PR_TARGET[target], ); } diff --git a/packages/host/.gitignore b/packages/host/.gitignore index b3f12e5b..5ba3e2fe 100644 --- a/packages/host/.gitignore +++ b/packages/host/.gitignore @@ -16,12 +16,5 @@ include/ android/.cxx/ android/build/ -# Everything in weak-node-api is generated, except for the configurations -# Generated and built bia `npm run build-weak-node-api-injector` -/weak-node-api/build/ -/weak-node-api/*.xcframework -/weak-node-api/*.android.node -/weak-node-api/weak_node_api.cpp -/weak-node-api/weak_node_api.hpp # Generated via `npm run generate-weak-node-api-injector` /cpp/WeakNodeApiInjector.cpp diff --git a/packages/host/package.json b/packages/host/package.json index 1edcc6ba..c13c030b 100644 --- a/packages/host/package.json +++ b/packages/host/package.json @@ -43,33 +43,18 @@ ], "scripts": { "build": "tsc --build", - "copy-node-api-headers": "tsx scripts/copy-node-api-headers.ts", - "generate-weak-node-api": "tsx scripts/generate-weak-node-api.ts", - "generate-weak-node-api-injector": "tsx scripts/generate-weak-node-api-injector.ts", - "prepare-weak-node-api": "node --run copy-node-api-headers && node --run generate-weak-node-api-injector && node --run generate-weak-node-api", - "build-weak-node-api": "cmake-rn --no-auto-link --no-weak-node-api-linkage --xcframework-extension --source ./weak-node-api --out ./weak-node-api", - "build-weak-node-api:android": "node --run build-weak-node-api -- --android", - "build-weak-node-api:apple": "node --run build-weak-node-api -- --apple", - "build-weak-node-api:all": "node --run build-weak-node-api -- --android --apple", + "generate-weak-node-api-injector": "node scripts/generate-weak-node-api-injector.mts", "test": "tsx --test --test-reporter=@reporters/github --test-reporter-destination=stdout --test-reporter=spec --test-reporter-destination=stdout src/node/**/*.test.ts src/node/*.test.ts", "test:gradle": "ENABLE_GRADLE_TESTS=true node --run test", - "bootstrap": "node --run prepare-weak-node-api && node --run build-weak-node-api", - "prerelease": "node --run prepare-weak-node-api && node --run build-weak-node-api:all" + "bootstrap": "node --run generate-weak-node-api-injector", + "prerelease": "node --run generate-weak-node-api-injector" }, "keywords": [ - "react-native", "node-api", "napi", - "node-api", "node-addon-api", "native", - "addon", - "module", - "c", - "c++", - "bindings", - "buildtools", - "cmake" + "addon" ], "author": { "name": "Callstack", @@ -87,6 +72,7 @@ "@react-native-node-api/cli-utils": "0.1.1", "pkg-dir": "^8.0.0", "read-pkg": "^9.0.1", + "weak-node-api": "0.1.0", "zod": "^4.1.11" }, "devDependencies": { diff --git a/packages/host/scripts/generate-weak-node-api-injector.ts b/packages/host/scripts/generate-weak-node-api-injector.mts similarity index 94% rename from packages/host/scripts/generate-weak-node-api-injector.ts rename to packages/host/scripts/generate-weak-node-api-injector.mts index d5adfd83..5981f2e1 100644 --- a/packages/host/scripts/generate-weak-node-api-injector.ts +++ b/packages/host/scripts/generate-weak-node-api-injector.mts @@ -2,9 +2,9 @@ import fs from "node:fs"; import path from "node:path"; import cp from "node:child_process"; -import { FunctionDecl, getNodeApiFunctions } from "./node-api-functions"; +import { type FunctionDecl, getNodeApiFunctions } from "weak-node-api"; -export const CPP_SOURCE_PATH = path.join(__dirname, "../cpp"); +export const CPP_SOURCE_PATH = path.join(import.meta.dirname, "../cpp"); // TODO: Remove when all runtime Node API functions are implemented const IMPLEMENTED_RUNTIME_FUNCTIONS = [ diff --git a/packages/host/src/node/cli/program.ts b/packages/host/src/node/cli/program.ts index 85b9016f..7a75258d 100644 --- a/packages/host/src/node/cli/program.ts +++ b/packages/host/src/node/cli/program.ts @@ -29,7 +29,7 @@ import { packageNameOption, pathSuffixOption } from "./options"; import { linkModules, pruneLinkedModules, ModuleLinker } from "./link-modules"; import { linkXcframework, restoreFrameworkLinks } from "./apple"; import { linkAndroidDir } from "./android"; -import { weakNodeApiPath } from "../weak-node-api"; +import { applePrebuildPath } from "weak-node-api"; // We're attaching a lot of listeners when spawning in parallel EventEmitter.defaultMaxListeners = 100; @@ -175,15 +175,11 @@ program if (apple) { await oraPromise( async () => { - const xcframeworkPath = path.join( - weakNodeApiPath, - "weak-node-api.xcframework", - ); await Promise.all( [ - path.join(xcframeworkPath, "macos-x86_64"), - path.join(xcframeworkPath, "macos-arm64"), - path.join(xcframeworkPath, "macos-arm64_x86_64"), + path.join(applePrebuildPath, "macos-x86_64"), + path.join(applePrebuildPath, "macos-arm64"), + path.join(applePrebuildPath, "macos-arm64_x86_64"), ].map(async (slicePath) => { const frameworkPath = path.join( slicePath, diff --git a/packages/host/src/node/weak-node-api.ts b/packages/host/src/node/weak-node-api.ts deleted file mode 100644 index 02e3befe..00000000 --- a/packages/host/src/node/weak-node-api.ts +++ /dev/null @@ -1,10 +0,0 @@ -import assert from "node:assert/strict"; -import fs from "node:fs"; -import path from "node:path"; - -export const weakNodeApiPath = path.resolve(__dirname, "../../weak-node-api"); - -assert( - fs.existsSync(weakNodeApiPath), - `Expected Weak Node API path to exist: ${weakNodeApiPath}`, -); diff --git a/packages/host/tsconfig.node-scripts.json b/packages/host/tsconfig.node-scripts.json index 4e11d816..b3771a38 100644 --- a/packages/host/tsconfig.node-scripts.json +++ b/packages/host/tsconfig.node-scripts.json @@ -7,6 +7,11 @@ "rootDir": "scripts", "types": ["node"] }, - "include": ["scripts/**/*.ts", "types/**/*.d.ts"], - "exclude": [] + "include": ["scripts/**/*.mts", "types/**/*.d.ts"], + "exclude": [], + "references": [ + { + "path": "../weak-node-api/tsconfig.node.json" + } + ] } diff --git a/packages/host/tsconfig.node.json b/packages/host/tsconfig.node.json index bf847c8c..e0982db2 100644 --- a/packages/host/tsconfig.node.json +++ b/packages/host/tsconfig.node.json @@ -8,5 +8,10 @@ "types": ["node"] }, "include": ["src/node/**/*.ts", "types/**/*.d.ts"], - "exclude": ["**/*.test.ts"] + "exclude": ["**/*.test.ts"], + "references": [ + { + "path": "../weak-node-api/tsconfig.node.json" + } + ] } diff --git a/packages/weak-node-api/.gitignore b/packages/weak-node-api/.gitignore new file mode 100644 index 00000000..30ea0716 --- /dev/null +++ b/packages/weak-node-api/.gitignore @@ -0,0 +1,11 @@ + +# Everything in weak-node-api is generated, except for the configurations +# Generated and built via `npm run bootstrap` +/build/ +/*.xcframework +/*.android.node +/generated/weak_node_api.cpp +/generated/weak_node_api.hpp + +# Copied from node-api-headers by scripts/copy-node-api-headers.ts +/include/ diff --git a/packages/host/weak-node-api/CMakeLists.txt b/packages/weak-node-api/CMakeLists.txt similarity index 96% rename from packages/host/weak-node-api/CMakeLists.txt rename to packages/weak-node-api/CMakeLists.txt index 08422d62..de2784e0 100644 --- a/packages/host/weak-node-api/CMakeLists.txt +++ b/packages/weak-node-api/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.15) project(weak-node-api) add_library(${PROJECT_NAME} SHARED - weak_node_api.cpp + generated/weak_node_api.cpp ) # Stripping the prefix from the library name diff --git a/packages/weak-node-api/package.json b/packages/weak-node-api/package.json new file mode 100644 index 00000000..86566548 --- /dev/null +++ b/packages/weak-node-api/package.json @@ -0,0 +1,52 @@ +{ + "name": "weak-node-api", + "version": "0.1.0", + "description": "Node-API for React Native", + "homepage": "https://github.com/callstackincubator/react-native-node-api", + "repository": { + "type": "git", + "url": "git+https://github.com/callstackincubator/react-native-node-api.git", + "directory": "packages/weak-node-api" + }, + "main": "dist/index.js", + "type": "module", + "files": [ + "dist", + "!dist/**/*.test.d.ts", + "!dist/**/*.test.d.ts.map", + "include", + "weak-node-api/**", + "!weak-node-api/build/" + ], + "scripts": { + "build": "tsc --build", + "copy-node-api-headers": "tsx scripts/copy-node-api-headers.ts", + "generate-weak-node-api": "tsx scripts/generate-weak-node-api.ts", + "prepare-weak-node-api": "node --run copy-node-api-headers && node --run generate-weak-node-api", + "build-weak-node-api": "cmake-rn --no-auto-link --no-weak-node-api-linkage --xcframework-extension", + "build-weak-node-api:android": "node --run build-weak-node-api -- --android", + "build-weak-node-api:apple": "node --run build-weak-node-api -- --apple", + "build-weak-node-api:all": "node --run build-weak-node-api -- --android --apple", + "test": "tsx --test --test-reporter=@reporters/github --test-reporter-destination=stdout --test-reporter=spec --test-reporter-destination=stdout src/node/**/*.test.ts src/node/*.test.ts", + "bootstrap": "node --run prepare-weak-node-api && node --run build-weak-node-api", + "prerelease": "node --run prepare-weak-node-api && node --run build-weak-node-api:all" + }, + "keywords": [ + "react-native", + "napi", + "node-api", + "node-addon-api", + "native", + "addon", + "module", + "c", + "c++", + "bindings", + "buildtools", + "cmake" + ], + "devDependencies": { + "node-api-headers": "^1.5.0", + "zod": "^4.1.11" + } +} diff --git a/packages/host/scripts/copy-node-api-headers.ts b/packages/weak-node-api/scripts/copy-node-api-headers.ts similarity index 82% rename from packages/host/scripts/copy-node-api-headers.ts rename to packages/weak-node-api/scripts/copy-node-api-headers.ts index 9632627e..2bfd43dc 100644 --- a/packages/host/scripts/copy-node-api-headers.ts +++ b/packages/weak-node-api/scripts/copy-node-api-headers.ts @@ -3,7 +3,7 @@ import fs from "node:fs"; import path from "node:path"; import { include_dir as includeSourcePath } from "node-api-headers"; -const includeDestinationPath = path.join(__dirname, "../weak-node-api/include"); +const includeDestinationPath = path.join(import.meta.dirname, "../include"); assert(fs.existsSync(includeSourcePath), `Expected ${includeSourcePath}`); console.log(`Copying ${includeSourcePath} to ${includeDestinationPath}`); fs.cpSync(includeSourcePath, includeDestinationPath, { recursive: true }); diff --git a/packages/host/scripts/generate-weak-node-api.ts b/packages/weak-node-api/scripts/generate-weak-node-api.ts similarity index 87% rename from packages/host/scripts/generate-weak-node-api.ts rename to packages/weak-node-api/scripts/generate-weak-node-api.ts index 1090de41..b9a8736c 100644 --- a/packages/host/scripts/generate-weak-node-api.ts +++ b/packages/weak-node-api/scripts/generate-weak-node-api.ts @@ -2,9 +2,12 @@ import fs from "node:fs"; import path from "node:path"; import cp from "node:child_process"; -import { FunctionDecl, getNodeApiFunctions } from "./node-api-functions"; +import { + FunctionDecl, + getNodeApiFunctions, +} from "../src/node-api-functions.js"; -export const WEAK_NODE_API_PATH = path.join(__dirname, "../weak-node-api"); +export const OUTPUT_PATH = path.join(import.meta.dirname, "../generated"); /** * Generates source code for a version script for the given Node API version. @@ -67,17 +70,17 @@ export function generateSource(functions: FunctionDecl[]) { } async function run() { - await fs.promises.mkdir(WEAK_NODE_API_PATH, { recursive: true }); + await fs.promises.mkdir(OUTPUT_PATH, { recursive: true }); const nodeApiFunctions = getNodeApiFunctions(); const header = generateHeader(nodeApiFunctions); - const headerPath = path.join(WEAK_NODE_API_PATH, "weak_node_api.hpp"); + const headerPath = path.join(OUTPUT_PATH, "weak_node_api.hpp"); await fs.promises.writeFile(headerPath, header, "utf-8"); cp.spawnSync("clang-format", ["-i", headerPath], { stdio: "inherit" }); const source = generateSource(nodeApiFunctions); - const sourcePath = path.join(WEAK_NODE_API_PATH, "weak_node_api.cpp"); + const sourcePath = path.join(OUTPUT_PATH, "weak_node_api.cpp"); await fs.promises.writeFile(sourcePath, source, "utf-8"); cp.spawnSync("clang-format", ["-i", sourcePath], { stdio: "inherit" }); } diff --git a/packages/weak-node-api/src/index.ts b/packages/weak-node-api/src/index.ts new file mode 100644 index 00000000..fbd5337a --- /dev/null +++ b/packages/weak-node-api/src/index.ts @@ -0,0 +1,2 @@ +export * from "./weak-node-api.js"; +export * from "./node-api-functions.js"; diff --git a/packages/host/scripts/node-api-functions.ts b/packages/weak-node-api/src/node-api-functions.ts similarity index 100% rename from packages/host/scripts/node-api-functions.ts rename to packages/weak-node-api/src/node-api-functions.ts diff --git a/packages/weak-node-api/src/weak-node-api.ts b/packages/weak-node-api/src/weak-node-api.ts new file mode 100644 index 00000000..51ddb296 --- /dev/null +++ b/packages/weak-node-api/src/weak-node-api.ts @@ -0,0 +1,21 @@ +import path from "node:path"; + +export const weakNodeApiPath = path.resolve(import.meta.dirname, ".."); + +// TODO: Support multiple configurations +export const outputPath = path.resolve(weakNodeApiPath, "build", "Release"); + +export const applePrebuildPath = path.resolve( + outputPath, + "weak-node-api.xcframework", +); + +export const androidPrebuildPath = path.resolve( + outputPath, + "weak-node-api.android.node", +); + +export const weakNodeApiCmakePath = path.resolve( + weakNodeApiPath, + "weak-node-api.cmake", +); diff --git a/packages/weak-node-api/tsconfig.json b/packages/weak-node-api/tsconfig.json new file mode 100644 index 00000000..f08015f8 --- /dev/null +++ b/packages/weak-node-api/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true + }, + "files": [], + "references": [ + { "path": "./tsconfig.node.json" }, + { "path": "./tsconfig.node-scripts.json" } + ] +} diff --git a/packages/weak-node-api/tsconfig.node-scripts.json b/packages/weak-node-api/tsconfig.node-scripts.json new file mode 100644 index 00000000..4bc586fa --- /dev/null +++ b/packages/weak-node-api/tsconfig.node-scripts.json @@ -0,0 +1,13 @@ +{ + "extends": "./tsconfig.node.json", + "compilerOptions": { + "composite": true, + "emitDeclarationOnly": true, + "outDir": "dist", + "rootDir": "scripts", + "types": ["node"] + }, + "include": ["scripts/**/*.ts", "types/**/*.d.ts"], + "exclude": [], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/packages/weak-node-api/tsconfig.node.json b/packages/weak-node-api/tsconfig.node.json new file mode 100644 index 00000000..0028899f --- /dev/null +++ b/packages/weak-node-api/tsconfig.node.json @@ -0,0 +1,12 @@ +{ + "extends": "@tsconfig/node22/tsconfig.json", + "compilerOptions": { + "composite": true, + "declarationMap": true, + "outDir": "dist", + "rootDir": "src", + "types": ["node"] + }, + "include": ["src/**/*.ts", "types/**/*.d.ts"], + "exclude": ["**/*.test.ts"] +} diff --git a/packages/host/types/node-api-headers/index.d.ts b/packages/weak-node-api/types/node-api-headers/index.d.ts similarity index 100% rename from packages/host/types/node-api-headers/index.d.ts rename to packages/weak-node-api/types/node-api-headers/index.d.ts diff --git a/packages/host/weak-node-api/weak-node-api.cmake b/packages/weak-node-api/weak-node-api.cmake similarity index 100% rename from packages/host/weak-node-api/weak-node-api.cmake rename to packages/weak-node-api/weak-node-api.cmake diff --git a/tsconfig.json b/tsconfig.json index 4ca25b74..2d49bdb1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,6 +15,7 @@ { "path": "./packages/cmake-rn/tsconfig.json" }, { "path": "./packages/ferric/tsconfig.json" }, { "path": "./packages/node-addon-examples/tsconfig.json" }, - { "path": "./packages/node-tests/tsconfig.json" } + { "path": "./packages/node-tests/tsconfig.json" }, + { "path": "./packages/weak-node-api/tsconfig.json" } ] } From f14800a0467c0e49d292334a63e943055b1752b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Mon, 3 Nov 2025 22:50:06 +0100 Subject: [PATCH 03/14] Add a podspec to weak-node-api --- apps/test-app/package.json | 3 +- package-lock.json | 9 ++-- .../generate-weak-node-api-injector.mts | 3 +- packages/weak-node-api/package.json | 16 ++++++- .../src/restore-xcframework-symlinks.ts | 48 +++++++++++++++++++ packages/weak-node-api/weak-node-api.podspec | 34 +++++++++++++ 6 files changed, 105 insertions(+), 8 deletions(-) create mode 100644 packages/weak-node-api/src/restore-xcframework-symlinks.ts create mode 100644 packages/weak-node-api/weak-node-api.podspec diff --git a/apps/test-app/package.json b/apps/test-app/package.json index 08f55a35..2f41e8c4 100644 --- a/apps/test-app/package.json +++ b/apps/test-app/package.json @@ -42,6 +42,7 @@ "react": "19.1.0", "react-native": "0.81.4", "react-native-node-api": "*", - "react-native-test-app": "^4.4.7" + "react-native-test-app": "^4.4.7", + "weak-node-api": "*" } } diff --git a/package-lock.json b/package-lock.json index 6baca096..98ac72db 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "workspaces": [ "packages/cli-utils", "packages/cmake-file-api", + "packages/weak-node-api", "packages/cmake-rn", "packages/ferric", "packages/gyp-to-cmake", @@ -16,7 +17,6 @@ "packages/node-addon-examples", "packages/node-tests", "packages/ferric-example", - "packages/weak-node-api", "apps/test-app" ], "devDependencies": { @@ -64,7 +64,8 @@ "react": "19.1.0", "react-native": "0.81.4", "react-native-node-api": "*", - "react-native-test-app": "^4.4.7" + "react-native-test-app": "^4.4.7", + "weak-node-api": "*" } }, "node_modules/@actions/core": { @@ -14894,8 +14895,7 @@ "devDependencies": { "@babel/core": "^7.26.10", "@babel/types": "^7.27.0", - "fswin": "^3.24.829", - "node-api-headers": "^1.5.0" + "fswin": "^3.24.829" }, "peerDependencies": { "@babel/core": "^7.26.10", @@ -14929,6 +14929,7 @@ }, "packages/weak-node-api": { "version": "0.1.0", + "license": "MIT", "devDependencies": { "node-api-headers": "^1.5.0", "zod": "^4.1.11" diff --git a/packages/host/scripts/generate-weak-node-api-injector.mts b/packages/host/scripts/generate-weak-node-api-injector.mts index 5981f2e1..71acfe86 100644 --- a/packages/host/scripts/generate-weak-node-api-injector.mts +++ b/packages/host/scripts/generate-weak-node-api-injector.mts @@ -28,9 +28,10 @@ const IMPLEMENTED_RUNTIME_FUNCTIONS = [ export function generateSource(functions: FunctionDecl[]) { return ` // This file is generated by react-native-node-api - #include #include #include + + #include #include #include diff --git a/packages/weak-node-api/package.json b/packages/weak-node-api/package.json index 86566548..1bbd77dc 100644 --- a/packages/weak-node-api/package.json +++ b/packages/weak-node-api/package.json @@ -15,8 +15,9 @@ "!dist/**/*.test.d.ts", "!dist/**/*.test.d.ts.map", "include", - "weak-node-api/**", - "!weak-node-api/build/" + "build/Release", + "*.podspec", + "*.cmake" ], "scripts": { "build": "tsc --build", @@ -45,6 +46,17 @@ "buildtools", "cmake" ], + "author": { + "name": "Callstack", + "url": "https://github.com/callstackincubator" + }, + "contributors": [ + { + "name": "Kræn Hansen", + "url": "https://github.com/kraenhansen" + } + ], + "license": "MIT", "devDependencies": { "node-api-headers": "^1.5.0", "zod": "^4.1.11" diff --git a/packages/weak-node-api/src/restore-xcframework-symlinks.ts b/packages/weak-node-api/src/restore-xcframework-symlinks.ts new file mode 100644 index 00000000..3feba29b --- /dev/null +++ b/packages/weak-node-api/src/restore-xcframework-symlinks.ts @@ -0,0 +1,48 @@ +import assert from "node:assert/strict"; +import fs from "node:fs"; +import path from "node:path"; + +async function restoreVersionedFrameworkSymlinks(frameworkPath: string) { + const currentLinkPath = path.join(frameworkPath, "Versions", "Current"); + + if (!fs.existsSync(currentLinkPath)) { + await fs.promises.symlink("A", currentLinkPath); + } + + const binaryLinkPath = path.join(frameworkPath, "weak-node-api"); + + if (!fs.existsSync(binaryLinkPath)) { + await fs.promises.symlink("Versions/Current/weak-node-api", binaryLinkPath); + } + + const resourcesLinkPath = path.join(frameworkPath, "Resources"); + + if (!fs.existsSync(resourcesLinkPath)) { + await fs.promises.symlink("Versions/Current/Resources", resourcesLinkPath); + } +} + +if (process.platform === "darwin") { + const xcframeworkPath = path.join( + import.meta.dirname, + "..", + "build", + "Release", + "weak-node-api.xcframework", + ); + + assert( + fs.existsSync(xcframeworkPath), + `Expected an Xcframework at ${xcframeworkPath}`, + ); + + const macosFrameworkPath = path.join( + xcframeworkPath, + "macos-arm64_x86_64", + "weak-node-api.framework", + ); + + if (fs.existsSync(macosFrameworkPath)) { + await restoreVersionedFrameworkSymlinks(macosFrameworkPath); + } +} diff --git a/packages/weak-node-api/weak-node-api.podspec b/packages/weak-node-api/weak-node-api.podspec new file mode 100644 index 00000000..ae527e9e --- /dev/null +++ b/packages/weak-node-api/weak-node-api.podspec @@ -0,0 +1,34 @@ +require "json" + +package = JSON.parse(File.read(File.join(__dir__, "package.json"))) + +# We need to restore symlinks in the versioned framework directories, +# as these are not preserved when in the archive uploaded to NPM +unless defined?(@restored) + RESTORE_COMMAND = "node '#{File.join(__dir__, "dist/restore-xcframework-symlinks.js")}'" + Pod::UI.info("[weak-node-api] ".green + "Restoring symbolic links in Xcframework") + system(RESTORE_COMMAND) or raise "Failed to restore symlinks in Xcframework" + # Setting a flag to avoid running this command on every require + @restored = true +end + +Pod::Spec.new do |s| + s.name = package["name"] + s.version = package["version"] + s.summary = package["description"] + s.homepage = package["homepage"] + s.license = package["license"] + s.authors = package["author"] + + s.source = { :git => "https://github.com/callstackincubator/react-native-node-api.git", :tag => "#{s.version}" } + + # TODO: These headers could be included in the Xcframework? + # (tracked by https://github.com/callstackincubator/react-native-node-api/issues/315) + s.source_files = "generated/*.hpp", "include/*.h" + s.public_header_files = "generated/*.hpp", "include/*.h" + + s.vendored_frameworks = "build/Release/weak-node-api.xcframework" + + # Avoiding the header dir to allow for idiomatic Node-API includes + s.header_dir = nil +end \ No newline at end of file From 92b6634c9bfc879451108896dfe5fe81841d65ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Mon, 3 Nov 2025 22:50:49 +0100 Subject: [PATCH 04/14] Delete node-api headers from the host package --- packages/host/package.json | 3 +-- packages/host/react-native-node-api.podspec | 7 ++++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/host/package.json b/packages/host/package.json index c13c030b..890cb858 100644 --- a/packages/host/package.json +++ b/packages/host/package.json @@ -78,8 +78,7 @@ "devDependencies": { "@babel/core": "^7.26.10", "@babel/types": "^7.27.0", - "fswin": "^3.24.829", - "node-api-headers": "^1.5.0" + "fswin": "^3.24.829" }, "peerDependencies": { "@babel/core": "^7.26.10", diff --git a/packages/host/react-native-node-api.podspec b/packages/host/react-native-node-api.podspec index 5ed4072a..ab4b0e75 100644 --- a/packages/host/react-native-node-api.podspec +++ b/packages/host/react-native-node-api.podspec @@ -31,10 +31,11 @@ Pod::Spec.new do |s| s.source = { :git => "https://github.com/callstackincubator/react-native-node-api.git", :tag => "#{s.version}" } - s.source_files = "apple/**/*.{h,m,mm}", "cpp/**/*.{hpp,cpp,c,h}", "weak-node-api/include/*.h", "weak-node-api/*.hpp" - s.public_header_files = "weak-node-api/include/*.h" + s.source_files = "apple/**/*.{h,m,mm}", "cpp/**/*.{hpp,cpp,c,h}" - s.vendored_frameworks = "auto-linked/apple/*.xcframework", "weak-node-api/weak-node-api.xcframework" + s.dependency "weak-node-api" + + s.vendored_frameworks = "auto-linked/apple/*.xcframework" s.script_phase = { :name => 'Copy Node-API xcframeworks', :execution_position => :before_compile, From b4c563e87b64cd304435d795b5d7ba989fe35d1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Mon, 3 Nov 2025 22:51:15 +0100 Subject: [PATCH 05/14] Remove symlink restoring of weak-node-api from host --- packages/host/src/node/cli/program.ts | 32 +-------------------------- 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/packages/host/src/node/cli/program.ts b/packages/host/src/node/cli/program.ts index 7a75258d..169ca85b 100644 --- a/packages/host/src/node/cli/program.ts +++ b/packages/host/src/node/cli/program.ts @@ -1,7 +1,6 @@ import assert from "node:assert/strict"; import path from "node:path"; import { EventEmitter } from "node:stream"; -import fs from "node:fs"; import { Command, @@ -27,9 +26,8 @@ import { import { command as vendorHermes } from "./hermes"; import { packageNameOption, pathSuffixOption } from "./options"; import { linkModules, pruneLinkedModules, ModuleLinker } from "./link-modules"; -import { linkXcframework, restoreFrameworkLinks } from "./apple"; +import { linkXcframework } from "./apple"; import { linkAndroidDir } from "./android"; -import { applePrebuildPath } from "weak-node-api"; // We're attaching a lot of listeners when spawning in parallel EventEmitter.defaultMaxListeners = 100; @@ -171,34 +169,6 @@ program await pruneLinkedModules(platform, modules); } } - - if (apple) { - await oraPromise( - async () => { - await Promise.all( - [ - path.join(applePrebuildPath, "macos-x86_64"), - path.join(applePrebuildPath, "macos-arm64"), - path.join(applePrebuildPath, "macos-arm64_x86_64"), - ].map(async (slicePath) => { - const frameworkPath = path.join( - slicePath, - "weak-node-api.framework", - ); - if (fs.existsSync(frameworkPath)) { - await restoreFrameworkLinks(frameworkPath); - } - }), - ); - }, - { - text: "Restoring weak-node-api symlinks", - successText: "Restored weak-node-api symlinks", - failText: (error) => - `Failed to restore weak-node-api symlinks: ${error.message}`, - }, - ); - } }, ), ); From 405baafaaeba9adb97c2ef7ef106c0313e4260b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 4 Nov 2025 06:18:51 +0100 Subject: [PATCH 06/14] Give preference to a Debug build of weak-node-api --- .../src/restore-xcframework-symlinks.ts | 16 +++++----------- packages/weak-node-api/src/weak-node-api.ts | 9 +++++++-- packages/weak-node-api/weak-node-api.podspec | 2 +- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/packages/weak-node-api/src/restore-xcframework-symlinks.ts b/packages/weak-node-api/src/restore-xcframework-symlinks.ts index 3feba29b..41373809 100644 --- a/packages/weak-node-api/src/restore-xcframework-symlinks.ts +++ b/packages/weak-node-api/src/restore-xcframework-symlinks.ts @@ -2,6 +2,8 @@ import assert from "node:assert/strict"; import fs from "node:fs"; import path from "node:path"; +import { applePrebuildPath } from "./weak-node-api.js"; + async function restoreVersionedFrameworkSymlinks(frameworkPath: string) { const currentLinkPath = path.join(frameworkPath, "Versions", "Current"); @@ -23,21 +25,13 @@ async function restoreVersionedFrameworkSymlinks(frameworkPath: string) { } if (process.platform === "darwin") { - const xcframeworkPath = path.join( - import.meta.dirname, - "..", - "build", - "Release", - "weak-node-api.xcframework", - ); - assert( - fs.existsSync(xcframeworkPath), - `Expected an Xcframework at ${xcframeworkPath}`, + fs.existsSync(applePrebuildPath), + `Expected an Xcframework at ${applePrebuildPath}`, ); const macosFrameworkPath = path.join( - xcframeworkPath, + applePrebuildPath, "macos-arm64_x86_64", "weak-node-api.framework", ); diff --git a/packages/weak-node-api/src/weak-node-api.ts b/packages/weak-node-api/src/weak-node-api.ts index 51ddb296..db38a0ba 100644 --- a/packages/weak-node-api/src/weak-node-api.ts +++ b/packages/weak-node-api/src/weak-node-api.ts @@ -1,9 +1,14 @@ import path from "node:path"; +import fs from "node:fs"; export const weakNodeApiPath = path.resolve(import.meta.dirname, ".."); -// TODO: Support multiple configurations -export const outputPath = path.resolve(weakNodeApiPath, "build", "Release"); +const debugOutputPath = path.resolve(weakNodeApiPath, "build", "Debug"); +const releaseOutputPath = path.resolve(weakNodeApiPath, "build", "Release"); + +export const outputPath = fs.existsSync(debugOutputPath) + ? debugOutputPath + : releaseOutputPath; export const applePrebuildPath = path.resolve( outputPath, diff --git a/packages/weak-node-api/weak-node-api.podspec b/packages/weak-node-api/weak-node-api.podspec index ae527e9e..236c6ec2 100644 --- a/packages/weak-node-api/weak-node-api.podspec +++ b/packages/weak-node-api/weak-node-api.podspec @@ -27,7 +27,7 @@ Pod::Spec.new do |s| s.source_files = "generated/*.hpp", "include/*.h" s.public_header_files = "generated/*.hpp", "include/*.h" - s.vendored_frameworks = "build/Release/weak-node-api.xcframework" + s.vendored_frameworks = "build/*/weak-node-api.xcframework" # Avoiding the header dir to allow for idiomatic Node-API includes s.header_dir = nil From 12d0cf77f89a3647f0ded2f8553a7042b003d92f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 4 Nov 2025 09:22:57 +0100 Subject: [PATCH 07/14] Combined two buildFeatures --- packages/host/android/build.gradle | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/host/android/build.gradle b/packages/host/android/build.gradle index 41c7b7bd..03bfc3fe 100644 --- a/packages/host/android/build.gradle +++ b/packages/host/android/build.gradle @@ -71,7 +71,8 @@ android { compileSdkVersion getExtOrIntegerDefault("compileSdkVersion") buildFeatures { - prefab = true + buildConfig true + prefab true } defaultConfig { @@ -103,10 +104,6 @@ android { } } - buildFeatures { - buildConfig true - } - buildTypes { debug { jniDebuggable true From 723c83beaa34cd3d148f923e8980b2e020156f41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 4 Nov 2025 09:31:58 +0100 Subject: [PATCH 08/14] Error if weak-node-api is included outside of cmake-rn (for now) --- packages/weak-node-api/weak-node-api.cmake | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/weak-node-api/weak-node-api.cmake b/packages/weak-node-api/weak-node-api.cmake index 2fda647e..1621a978 100644 --- a/packages/weak-node-api/weak-node-api.cmake +++ b/packages/weak-node-api/weak-node-api.cmake @@ -1,3 +1,13 @@ +if(NOT DEFINED WEAK_NODE_API_LIB) + # TODO: Set this to an Android and Apple specific path to the dynamic library + message(FATAL_ERROR "WEAK_NODE_API_LIB is not set") +endif() + +if(NOT DEFINED WEAK_NODE_API_INC) + # TODO: Set this to ./include and ./generated + message(FATAL_ERROR "WEAK_NODE_API_INC is not set") +endif() + add_library(weak-node-api SHARED IMPORTED) set_target_properties(weak-node-api PROPERTIES From 3de9ac639d86a7b8203ccd90c3cc8a2f7206f99f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 4 Nov 2025 09:33:00 +0100 Subject: [PATCH 09/14] Temporary work-around deep-referencing into weak-node-api --- packages/host/android/CMakeLists.txt | 4 ++-- packages/host/android/build.gradle | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/host/android/CMakeLists.txt b/packages/host/android/CMakeLists.txt index 3e9fd392..912068d2 100644 --- a/packages/host/android/CMakeLists.txt +++ b/packages/host/android/CMakeLists.txt @@ -8,8 +8,8 @@ find_package(hermes-engine REQUIRED CONFIG) add_library(weak-node-api INTERFACE) target_include_directories(weak-node-api INTERFACE - ../weak-node-api - ../weak-node-api/include + ../../weak-node-api/generated + ../../weak-node-api/include ) add_library(node-api-host SHARED diff --git a/packages/host/android/build.gradle b/packages/host/android/build.gradle index 03bfc3fe..f4c35663 100644 --- a/packages/host/android/build.gradle +++ b/packages/host/android/build.gradle @@ -61,8 +61,11 @@ android { sourceSets { main { manifest.srcFile "src/main/AndroidManifestNew.xml" - // Include the weak-node-api to enable a dynamic load - jniLibs.srcDirs += ["../weak-node-api/weak-node-api.android.node"] + // Include the weak-node-api native libraries directly + jniLibs.srcDirs += [ + "../../weak-node-api/build/Debug/weak-node-api.android.node", + "../../weak-node-api/build/Release/weak-node-api.android.node" + ] } } } From 42a93673ec8d565cafcb6a9b41522cd0bb36f885 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 4 Nov 2025 16:29:45 +0100 Subject: [PATCH 10/14] Use `find_package` instead of `include` to locate "weak-node-api" --- .changeset/quick-poets-greet.md | 7 ++++ packages/cmake-rn/README.md | 4 +- packages/cmake-rn/src/weak-node-api.ts | 4 +- packages/gyp-to-cmake/src/transformer.ts | 2 +- packages/host/android/CMakeLists.txt | 7 +--- packages/host/android/build.gradle | 13 ++++++- .../tests/async/CMakeLists.txt | 2 +- .../tests/buffers/CMakeLists.txt | 2 +- packages/weak-node-api/src/weak-node-api.ts | 2 +- .../weak-node-api/weak-node-api-config.cmake | 39 +++++++++++++++++++ packages/weak-node-api/weak-node-api.cmake | 16 -------- 11 files changed, 68 insertions(+), 30 deletions(-) create mode 100644 .changeset/quick-poets-greet.md create mode 100644 packages/weak-node-api/weak-node-api-config.cmake delete mode 100644 packages/weak-node-api/weak-node-api.cmake diff --git a/.changeset/quick-poets-greet.md b/.changeset/quick-poets-greet.md new file mode 100644 index 00000000..4b53b523 --- /dev/null +++ b/.changeset/quick-poets-greet.md @@ -0,0 +1,7 @@ +--- +"gyp-to-cmake": minor +"cmake-rn": minor +"react-native-node-api": minor +--- + +Use `find_package` instead of `include` to locate "weak-node-api" diff --git a/packages/cmake-rn/README.md b/packages/cmake-rn/README.md index ba41aecb..61772ce2 100644 --- a/packages/cmake-rn/README.md +++ b/packages/cmake-rn/README.md @@ -10,14 +10,14 @@ Android's dynamic linker imposes restrictions on the access to global symbols (s The implementation of Node-API is split between Hermes and our host package and to avoid addons having to explicitly link against either, we've introduced a `weak-node-api` library (published in `react-native-node-api` package). This library exposes only Node-API and will have its implementation injected by the host. -To link against `weak-node-api` just include the CMake config exposed through `WEAK_NODE_API_CONFIG` and add `weak-node-api` to the `target_link_libraries` of the addon's library target. +To link against `weak-node-api` just use `find_package` to import the `weak-node-api` target and add it to the `target_link_libraries` of the addon's library target. ```cmake cmake_minimum_required(VERSION 3.15...3.31) project(tests-buffers) # Defines the "weak-node-api" target -include(${WEAK_NODE_API_CONFIG}) +find_package(weak-node-api REQUIRED CONFIG) add_library(addon SHARED addon.c) target_link_libraries(addon PRIVATE weak-node-api) diff --git a/packages/cmake-rn/src/weak-node-api.ts b/packages/cmake-rn/src/weak-node-api.ts index 6eea7876..44b2e79c 100644 --- a/packages/cmake-rn/src/weak-node-api.ts +++ b/packages/cmake-rn/src/weak-node-api.ts @@ -58,7 +58,9 @@ export function getWeakNodeApiVariables( triplet: SupportedTriplet | "apple", ): Record { return { - // Expose an includable CMake config file declaring the weak-node-api target + // Enable use of `find_package(weak-node-api REQUIRED CONFIG)` + "weak-node-api_DIR": path.dirname(weakNodeApiCmakePath), + // Enable use of `include(${WEAK_NODE_API_CONFIG})` WEAK_NODE_API_CONFIG: weakNodeApiCmakePath, WEAK_NODE_API_INC: getNodeApiIncludePaths().join(";"), WEAK_NODE_API_LIB: getWeakNodeApiPath(triplet), diff --git a/packages/gyp-to-cmake/src/transformer.ts b/packages/gyp-to-cmake/src/transformer.ts index 3c9b65d8..a5660eba 100644 --- a/packages/gyp-to-cmake/src/transformer.ts +++ b/packages/gyp-to-cmake/src/transformer.ts @@ -85,7 +85,7 @@ export function bindingGypToCmakeLists({ ]; if (weakNodeApi) { - lines.push(`include(\${WEAK_NODE_API_CONFIG})`, ""); + lines.push(`find_package(weak-node-api REQUIRED CONFIG)`, ""); } for (const target of gyp.targets) { diff --git a/packages/host/android/CMakeLists.txt b/packages/host/android/CMakeLists.txt index 912068d2..e4c183f7 100644 --- a/packages/host/android/CMakeLists.txt +++ b/packages/host/android/CMakeLists.txt @@ -5,12 +5,7 @@ set(CMAKE_CXX_STANDARD 20) find_package(ReactAndroid REQUIRED CONFIG) find_package(hermes-engine REQUIRED CONFIG) - -add_library(weak-node-api INTERFACE) -target_include_directories(weak-node-api INTERFACE - ../../weak-node-api/generated - ../../weak-node-api/include -) +find_package(weak-node-api REQUIRED CONFIG) add_library(node-api-host SHARED src/main/cpp/OnLoad.cpp diff --git a/packages/host/android/build.gradle b/packages/host/android/build.gradle index f4c35663..2f6b6be8 100644 --- a/packages/host/android/build.gradle +++ b/packages/host/android/build.gradle @@ -14,6 +14,17 @@ if (!System.getenv("REACT_NATIVE_OVERRIDE_HERMES_DIR")) { ].join('\n')) } +def findWeakNodeApiDir() { + def searchDir = rootDir.toPath() + do { + def p = searchDir.resolve("node_modules/weak-node-api") + if (p.toFile().exists()) { + return p.toRealPath().toString() + } + } while (searchDir = searchDir.getParent()) + throw new GradleException("Could not find `weak-node-api`"); +} + buildscript { ext.getExtOrDefault = {name -> return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['NodeApiModules_' + name] @@ -86,7 +97,7 @@ android { cmake { targets "node-api-host" cppFlags "-frtti -fexceptions -Wall -fstack-protector-all" - arguments "-DANDROID_STL=c++_shared" + arguments "-DANDROID_STL=c++_shared", "-Dweak-node-api_DIR=${findWeakNodeApiDir()}" abiFilters (*reactNativeArchitectures()) buildTypes { diff --git a/packages/node-addon-examples/tests/async/CMakeLists.txt b/packages/node-addon-examples/tests/async/CMakeLists.txt index 2b6b2b81..67e5448b 100644 --- a/packages/node-addon-examples/tests/async/CMakeLists.txt +++ b/packages/node-addon-examples/tests/async/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.15...3.31) project(async-test) -include(${WEAK_NODE_API_CONFIG}) +find_package(weak-node-api REQUIRED CONFIG) add_library(addon SHARED addon.c) diff --git a/packages/node-addon-examples/tests/buffers/CMakeLists.txt b/packages/node-addon-examples/tests/buffers/CMakeLists.txt index 8d7ac2d2..da615db2 100644 --- a/packages/node-addon-examples/tests/buffers/CMakeLists.txt +++ b/packages/node-addon-examples/tests/buffers/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.15...3.31) project(buffers-test) -include(${WEAK_NODE_API_CONFIG}) +find_package(weak-node-api REQUIRED CONFIG) add_library(addon SHARED addon.c) diff --git a/packages/weak-node-api/src/weak-node-api.ts b/packages/weak-node-api/src/weak-node-api.ts index db38a0ba..87802461 100644 --- a/packages/weak-node-api/src/weak-node-api.ts +++ b/packages/weak-node-api/src/weak-node-api.ts @@ -22,5 +22,5 @@ export const androidPrebuildPath = path.resolve( export const weakNodeApiCmakePath = path.resolve( weakNodeApiPath, - "weak-node-api.cmake", + "weak-node-api-config.cmake", ); diff --git a/packages/weak-node-api/weak-node-api-config.cmake b/packages/weak-node-api/weak-node-api-config.cmake new file mode 100644 index 00000000..7d4d4a05 --- /dev/null +++ b/packages/weak-node-api/weak-node-api-config.cmake @@ -0,0 +1,39 @@ + +# Get the current file directory +get_filename_component(WEAK_NODE_API_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" DIRECTORY) + +if(NOT DEFINED WEAK_NODE_API_LIB) + # Auto-detect library path for Android NDK builds + if(ANDROID) + # Define the library path pattern for Android + set(WEAK_NODE_API_LIB_PATH "weak-node-api.android.node/${ANDROID_ABI}/libweak-node-api.so") + + # Try Debug first, then Release using the packaged Android node structure + set(WEAK_NODE_API_LIB_DEBUG "${WEAK_NODE_API_CMAKE_DIR}/build/Debug/${WEAK_NODE_API_LIB_PATH}") + set(WEAK_NODE_API_LIB_RELEASE "${WEAK_NODE_API_CMAKE_DIR}/build/Release/${WEAK_NODE_API_LIB_PATH}") + + if(EXISTS "${WEAK_NODE_API_LIB_DEBUG}") + set(WEAK_NODE_API_LIB "${WEAK_NODE_API_LIB_DEBUG}") + message(STATUS "Using Debug weak-node-api library: ${WEAK_NODE_API_LIB}") + elseif(EXISTS "${WEAK_NODE_API_LIB_RELEASE}") + set(WEAK_NODE_API_LIB "${WEAK_NODE_API_LIB_RELEASE}") + message(STATUS "Using Release weak-node-api library: ${WEAK_NODE_API_LIB}") + else() + message(FATAL_ERROR "Could not find weak-node-api library for Android ABI ${ANDROID_ABI}. Expected at:\n ${WEAK_NODE_API_LIB_DEBUG}\n ${WEAK_NODE_API_LIB_RELEASE}") + endif() + else() + message(FATAL_ERROR "WEAK_NODE_API_LIB is not set") + endif() +endif() + +if(NOT DEFINED WEAK_NODE_API_INC) + set(WEAK_NODE_API_INC "${WEAK_NODE_API_CMAKE_DIR}/include;${WEAK_NODE_API_CMAKE_DIR}/generated") + message(STATUS "Using weak-node-api include directories: ${WEAK_NODE_API_INC}") +endif() + +add_library(weak-node-api SHARED IMPORTED) + +set_target_properties(weak-node-api PROPERTIES + IMPORTED_LOCATION "${WEAK_NODE_API_LIB}" + INTERFACE_INCLUDE_DIRECTORIES "${WEAK_NODE_API_INC}" +) diff --git a/packages/weak-node-api/weak-node-api.cmake b/packages/weak-node-api/weak-node-api.cmake deleted file mode 100644 index 1621a978..00000000 --- a/packages/weak-node-api/weak-node-api.cmake +++ /dev/null @@ -1,16 +0,0 @@ -if(NOT DEFINED WEAK_NODE_API_LIB) - # TODO: Set this to an Android and Apple specific path to the dynamic library - message(FATAL_ERROR "WEAK_NODE_API_LIB is not set") -endif() - -if(NOT DEFINED WEAK_NODE_API_INC) - # TODO: Set this to ./include and ./generated - message(FATAL_ERROR "WEAK_NODE_API_INC is not set") -endif() - -add_library(weak-node-api SHARED IMPORTED) - -set_target_properties(weak-node-api PROPERTIES - IMPORTED_LOCATION "${WEAK_NODE_API_LIB}" - INTERFACE_INCLUDE_DIRECTORIES "${WEAK_NODE_API_INC}" -) From 514805233a6690781fdde512b69055583dd3291a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 4 Nov 2025 16:30:15 +0100 Subject: [PATCH 11/14] Updates to the package lock --- package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 98ac72db..8e3f895f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14831,11 +14831,11 @@ } }, "packages/cmake-rn": { - "version": "0.5.1", + "version": "0.5.2", "dependencies": { "@react-native-node-api/cli-utils": "0.1.1", "cmake-file-api": "0.1.0", - "react-native-node-api": "0.6.1", + "react-native-node-api": "0.6.2", "zod": "^4.1.11" }, "bin": { @@ -14848,11 +14848,11 @@ }, "packages/ferric": { "name": "ferric-cli", - "version": "0.3.6", + "version": "0.3.7", "dependencies": { "@napi-rs/cli": "~3.0.3", "@react-native-node-api/cli-utils": "0.1.1", - "react-native-node-api": "0.6.1" + "react-native-node-api": "0.6.2" }, "bin": { "ferric": "bin/ferric.js" @@ -14879,7 +14879,7 @@ }, "packages/host": { "name": "react-native-node-api", - "version": "0.6.1", + "version": "0.6.2", "license": "MIT", "dependencies": { "@expo/plist": "^0.4.7", @@ -14899,7 +14899,7 @@ }, "peerDependencies": { "@babel/core": "^7.26.10", - "react-native": "0.79.1 || 0.79.2 || 0.79.3 || 0.79.4 || 0.79.5 || 0.79.6 || 0.79.7 || 0.80.0 || 0.80.1 || 0.80.2 || 0.81.0 || 0.81.1 || 0.81.2 || 0.81.3 || 0.81.4" + "react-native": "0.79.1 || 0.79.2 || 0.79.3 || 0.79.4 || 0.79.5 || 0.79.6 || 0.79.7 || 0.80.0 || 0.80.1 || 0.80.2 || 0.81.0 || 0.81.1 || 0.81.2 || 0.81.3 || 0.81.4 || 0.81.5" } }, "packages/node-addon-examples": { From 966db1f3b61f9fb69e9425e9b47fc5115eee2579 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 4 Nov 2025 19:12:19 +0100 Subject: [PATCH 12/14] Moved weak-node-api into a peer dependency as it's supposed to be installed by the app --- packages/host/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/host/package.json b/packages/host/package.json index 890cb858..e97d20d2 100644 --- a/packages/host/package.json +++ b/packages/host/package.json @@ -72,7 +72,6 @@ "@react-native-node-api/cli-utils": "0.1.1", "pkg-dir": "^8.0.0", "read-pkg": "^9.0.1", - "weak-node-api": "0.1.0", "zod": "^4.1.11" }, "devDependencies": { @@ -82,6 +81,7 @@ }, "peerDependencies": { "@babel/core": "^7.26.10", - "react-native": "0.79.1 || 0.79.2 || 0.79.3 || 0.79.4 || 0.79.5 || 0.79.6 || 0.79.7 || 0.80.0 || 0.80.1 || 0.80.2 || 0.81.0 || 0.81.1 || 0.81.2 || 0.81.3 || 0.81.4 || 0.81.5" + "react-native": "0.79.1 || 0.79.2 || 0.79.3 || 0.79.4 || 0.79.5 || 0.79.6 || 0.79.7 || 0.80.0 || 0.80.1 || 0.80.2 || 0.81.0 || 0.81.1 || 0.81.2 || 0.81.3 || 0.81.4 || 0.81.5", + "weak-node-api": "0.1.0" } } From f46c5956ac64ab419bf17433b594acafe2c6432f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 4 Nov 2025 23:24:57 +0100 Subject: [PATCH 13/14] Reset weak-node-api version to 0.0.1 as published --- package-lock.json | 6 +++--- packages/host/package.json | 2 +- packages/weak-node-api/package.json | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8e3f895f..845f156c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14886,7 +14886,6 @@ "@react-native-node-api/cli-utils": "0.1.1", "pkg-dir": "^8.0.0", "read-pkg": "^9.0.1", - "weak-node-api": "0.1.0", "zod": "^4.1.11" }, "bin": { @@ -14899,7 +14898,8 @@ }, "peerDependencies": { "@babel/core": "^7.26.10", - "react-native": "0.79.1 || 0.79.2 || 0.79.3 || 0.79.4 || 0.79.5 || 0.79.6 || 0.79.7 || 0.80.0 || 0.80.1 || 0.80.2 || 0.81.0 || 0.81.1 || 0.81.2 || 0.81.3 || 0.81.4 || 0.81.5" + "react-native": "0.79.1 || 0.79.2 || 0.79.3 || 0.79.4 || 0.79.5 || 0.79.6 || 0.79.7 || 0.80.0 || 0.80.1 || 0.80.2 || 0.81.0 || 0.81.1 || 0.81.2 || 0.81.3 || 0.81.4 || 0.81.5", + "weak-node-api": "0.0.1" } }, "packages/node-addon-examples": { @@ -14928,7 +14928,7 @@ } }, "packages/weak-node-api": { - "version": "0.1.0", + "version": "0.0.1", "license": "MIT", "devDependencies": { "node-api-headers": "^1.5.0", diff --git a/packages/host/package.json b/packages/host/package.json index e97d20d2..2f1199bf 100644 --- a/packages/host/package.json +++ b/packages/host/package.json @@ -82,6 +82,6 @@ "peerDependencies": { "@babel/core": "^7.26.10", "react-native": "0.79.1 || 0.79.2 || 0.79.3 || 0.79.4 || 0.79.5 || 0.79.6 || 0.79.7 || 0.80.0 || 0.80.1 || 0.80.2 || 0.81.0 || 0.81.1 || 0.81.2 || 0.81.3 || 0.81.4 || 0.81.5", - "weak-node-api": "0.1.0" + "weak-node-api": "0.0.1" } } diff --git a/packages/weak-node-api/package.json b/packages/weak-node-api/package.json index 1bbd77dc..2f132208 100644 --- a/packages/weak-node-api/package.json +++ b/packages/weak-node-api/package.json @@ -1,6 +1,6 @@ { "name": "weak-node-api", - "version": "0.1.0", + "version": "0.0.1", "description": "Node-API for React Native", "homepage": "https://github.com/callstackincubator/react-native-node-api", "repository": { @@ -15,6 +15,7 @@ "!dist/**/*.test.d.ts", "!dist/**/*.test.d.ts.map", "include", + "build/Debug", "build/Release", "*.podspec", "*.cmake" From 18fbd5aeb7c548887296aa613a48bbae97fc3e5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Wed, 5 Nov 2025 08:18:27 +0100 Subject: [PATCH 14/14] Update readme and package description --- packages/weak-node-api/README.md | 19 +++++++++++++++++++ packages/weak-node-api/package.json | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 packages/weak-node-api/README.md diff --git a/packages/weak-node-api/README.md b/packages/weak-node-api/README.md new file mode 100644 index 00000000..073c9121 --- /dev/null +++ b/packages/weak-node-api/README.md @@ -0,0 +1,19 @@ +# Weak Node-API + +A clean linkable interface for Node-API and with runtime-injectable implementation. + +This package is part of the [Node-API for React Native](https://github.com/callstackincubator/react-native-node-api) project, which brings Node-API support to React Native applications. However, it can be used independently in any context where an indirect / weak Node-API implementation is needed. + +## Why is this needed? + +Android's dynamic linker restricts access to global symbols—dynamic libraries must explicitly declare dependencies as `DT_NEEDED` to access symbols. In the context of React Native, the Node-API implementation is split between Hermes and a host runtime, native addons built for Android would otherwise need to explicitly link against both - which is not ideal for multiple reasons. + +This library provides a solution by: + +- Exposing only Node-API functions without implementation +- Allowing runtime injection of the actual implementation by the host +- Eliminating the need for addons to suppress undefined symbol errors + +## Is this usable in the context of Node.js? + +While originally designed for React Native's split Node-API implementation, this approach could potentially be adapted for Node.js scenarios where addons need to link with undefined symbols allowed. Usage patterns and examples for Node.js contexts are being explored and this pattern could eventually be upstreamed to Node.js itself, benefiting the broader Node-API ecosystem. diff --git a/packages/weak-node-api/package.json b/packages/weak-node-api/package.json index 2f132208..63cfb410 100644 --- a/packages/weak-node-api/package.json +++ b/packages/weak-node-api/package.json @@ -1,7 +1,7 @@ { "name": "weak-node-api", "version": "0.0.1", - "description": "Node-API for React Native", + "description": "A linkable and runtime-injectable Node-API", "homepage": "https://github.com/callstackincubator/react-native-node-api", "repository": { "type": "git",