From 147ac68d6891e4d87a6e1a1a9bcb91a78c4835ec Mon Sep 17 00:00:00 2001 From: Ritesh Shukla Date: Sat, 15 Aug 2026 19:34:27 +0530 Subject: [PATCH 1/5] fix(core): make the shared C++ headers self-contained and MSVC-friendly The core headers compiled on iOS and Android only because libc++ and libstdc++ leak transitive includes; MSVC does not, so a large number of translation units failed to compile on Windows with missing std symbols. Every header now includes what it uses. Also: * guard the GCC-only diagnostic pragmas so MSVC does not see them * treat `_DEBUG` as a debug configuration, which is what MSVC defines instead of `DEBUG` * hoist `PromiseTask` out of the function scope it was declared in No behaviour changes - this is includes and preprocessor guards only. Co-Authored-By: Claude Opus 5 --- .../react-native-nitro-modules/cpp/core/AnyMap.cpp | 2 ++ .../react-native-nitro-modules/cpp/core/AnyMap.hpp | 2 ++ .../cpp/core/ArrayBuffer.cpp | 4 ++++ .../cpp/core/ArrayBuffer.hpp | 3 +++ .../cpp/core/HybridFunction.hpp | 1 + .../react-native-nitro-modules/cpp/core/Promise.cpp | 1 + .../react-native-nitro-modules/cpp/core/Promise.hpp | 2 ++ .../react-native-nitro-modules/cpp/jsi/JSICache.cpp | 1 + .../react-native-nitro-modules/cpp/jsi/JSICache.hpp | 1 + .../cpp/jsi/JSIConverter+AnyMap.hpp | 1 + .../cpp/jsi/JSIConverter+Function.hpp | 1 + .../cpp/jsi/JSIConverter+Promise.hpp | 1 + .../cpp/jsi/JSIConverter+UnorderedMap.hpp | 1 + .../cpp/jsi/JSIConverter+Vector.hpp | 1 + .../cpp/jsi/JSIConverter.hpp | 1 + .../cpp/platform/NitroLogger.hpp | 10 ++++++++++ .../cpp/prototype/HybridObjectPrototype.cpp | 1 + .../cpp/registry/HybridObjectRegistry.cpp | 1 + .../cpp/templates/IsSharedPtrTo.hpp | 3 +-- .../cpp/templates/TypeIndex.hpp | 1 + .../cpp/threading/CallInvokerDispatcher.hpp | 1 + .../cpp/threading/Dispatcher.cpp | 1 + .../cpp/threading/Dispatcher.hpp | 1 + .../cpp/threading/ThreadPool.cpp | 1 + .../cpp/utils/AssertPromiseState.hpp | 5 +++-- .../cpp/utils/CommonGlobals.cpp | 1 + .../cpp/utils/CommonGlobals.hpp | 1 + .../cpp/utils/FastVectorCopy.hpp | 3 +++ .../cpp/utils/JSCallback.hpp | 2 ++ .../cpp/utils/NitroDefines.hpp | 3 ++- .../cpp/utils/NitroTypeInfo.cpp | 4 ++++ .../cpp/utils/NitroTypeInfo.hpp | 1 + .../cpp/utils/PropNameIDCache.cpp | 1 + .../cpp/utils/PropNameIDCache.hpp | 2 ++ .../cpp/utils/WeakReference.hpp | 3 +++ .../cpp/views/CachedProp.hpp | 1 + 36 files changed, 65 insertions(+), 5 deletions(-) diff --git a/packages/react-native-nitro-modules/cpp/core/AnyMap.cpp b/packages/react-native-nitro-modules/cpp/core/AnyMap.cpp index bad74084aa..0e20e87f37 100644 --- a/packages/react-native-nitro-modules/cpp/core/AnyMap.cpp +++ b/packages/react-native-nitro-modules/cpp/core/AnyMap.cpp @@ -3,6 +3,8 @@ // #include "AnyMap.hpp" +#include +#include namespace margelo::nitro { diff --git a/packages/react-native-nitro-modules/cpp/core/AnyMap.hpp b/packages/react-native-nitro-modules/cpp/core/AnyMap.hpp index 8aaf16137f..520c3eea0c 100644 --- a/packages/react-native-nitro-modules/cpp/core/AnyMap.hpp +++ b/packages/react-native-nitro-modules/cpp/core/AnyMap.hpp @@ -6,10 +6,12 @@ #include "NitroDefines.hpp" #include "Null.hpp" +#include #include #include #include #include +#include #include #include diff --git a/packages/react-native-nitro-modules/cpp/core/ArrayBuffer.cpp b/packages/react-native-nitro-modules/cpp/core/ArrayBuffer.cpp index 181b1c34a6..7daaafe72e 100644 --- a/packages/react-native-nitro-modules/cpp/core/ArrayBuffer.cpp +++ b/packages/react-native-nitro-modules/cpp/core/ArrayBuffer.cpp @@ -8,9 +8,13 @@ #include "ArrayBuffer.hpp" #include "BorrowingReference.hpp" #include "NitroDefines.hpp" +#include +#include #include #include +#include #include +#include namespace margelo::nitro { diff --git a/packages/react-native-nitro-modules/cpp/core/ArrayBuffer.hpp b/packages/react-native-nitro-modules/cpp/core/ArrayBuffer.hpp index 5121a6fc45..cc522fdd9e 100644 --- a/packages/react-native-nitro-modules/cpp/core/ArrayBuffer.hpp +++ b/packages/react-native-nitro-modules/cpp/core/ArrayBuffer.hpp @@ -9,7 +9,10 @@ #include "BorrowingReference.hpp" #include "NitroDefines.hpp" +#include +#include #include +#include #include #include diff --git a/packages/react-native-nitro-modules/cpp/core/HybridFunction.hpp b/packages/react-native-nitro-modules/cpp/core/HybridFunction.hpp index c6c88aa21b..052fd4f25d 100644 --- a/packages/react-native-nitro-modules/cpp/core/HybridFunction.hpp +++ b/packages/react-native-nitro-modules/cpp/core/HybridFunction.hpp @@ -24,6 +24,7 @@ struct JSIConverter; #include #include #include +#include namespace margelo::nitro { diff --git a/packages/react-native-nitro-modules/cpp/core/Promise.cpp b/packages/react-native-nitro-modules/cpp/core/Promise.cpp index 8f80ea9471..6d4808ecde 100644 --- a/packages/react-native-nitro-modules/cpp/core/Promise.cpp +++ b/packages/react-native-nitro-modules/cpp/core/Promise.cpp @@ -6,6 +6,7 @@ // #include "Promise.hpp" +#include namespace margelo::nitro { diff --git a/packages/react-native-nitro-modules/cpp/core/Promise.hpp b/packages/react-native-nitro-modules/cpp/core/Promise.hpp index 07eac1180a..9b35d0e86c 100644 --- a/packages/react-native-nitro-modules/cpp/core/Promise.hpp +++ b/packages/react-native-nitro-modules/cpp/core/Promise.hpp @@ -13,6 +13,8 @@ #include #include #include +#include +#include #include namespace margelo::nitro { diff --git a/packages/react-native-nitro-modules/cpp/jsi/JSICache.cpp b/packages/react-native-nitro-modules/cpp/jsi/JSICache.cpp index 45739e3e8f..0894652c40 100644 --- a/packages/react-native-nitro-modules/cpp/jsi/JSICache.cpp +++ b/packages/react-native-nitro-modules/cpp/jsi/JSICache.cpp @@ -9,6 +9,7 @@ #include "CommonGlobals.hpp" #include "JSIHelpers.hpp" #include "NitroDefines.hpp" +#include namespace margelo::nitro { diff --git a/packages/react-native-nitro-modules/cpp/jsi/JSICache.hpp b/packages/react-native-nitro-modules/cpp/jsi/JSICache.hpp index 75785796aa..3db7ce862e 100644 --- a/packages/react-native-nitro-modules/cpp/jsi/JSICache.hpp +++ b/packages/react-native-nitro-modules/cpp/jsi/JSICache.hpp @@ -14,6 +14,7 @@ #include #include #include +#include #include namespace margelo::nitro { diff --git a/packages/react-native-nitro-modules/cpp/jsi/JSIConverter+AnyMap.hpp b/packages/react-native-nitro-modules/cpp/jsi/JSIConverter+AnyMap.hpp index 0e4d9ab022..1bd8d0ff92 100644 --- a/packages/react-native-nitro-modules/cpp/jsi/JSIConverter+AnyMap.hpp +++ b/packages/react-native-nitro-modules/cpp/jsi/JSIConverter+AnyMap.hpp @@ -21,6 +21,7 @@ struct JSIConverter; #include "PropNameIDCache.hpp" #include #include +#include namespace margelo::nitro { diff --git a/packages/react-native-nitro-modules/cpp/jsi/JSIConverter+Function.hpp b/packages/react-native-nitro-modules/cpp/jsi/JSIConverter+Function.hpp index 2f06cf3c6e..7e2f4f51f8 100644 --- a/packages/react-native-nitro-modules/cpp/jsi/JSIConverter+Function.hpp +++ b/packages/react-native-nitro-modules/cpp/jsi/JSIConverter+Function.hpp @@ -18,6 +18,7 @@ struct JSIConverter; #include "PromiseType.hpp" #include "PropNameIDCache.hpp" #include +#include namespace margelo::nitro { diff --git a/packages/react-native-nitro-modules/cpp/jsi/JSIConverter+Promise.hpp b/packages/react-native-nitro-modules/cpp/jsi/JSIConverter+Promise.hpp index 215a19cd1d..c06ce6dd6e 100644 --- a/packages/react-native-nitro-modules/cpp/jsi/JSIConverter+Promise.hpp +++ b/packages/react-native-nitro-modules/cpp/jsi/JSIConverter+Promise.hpp @@ -20,6 +20,7 @@ struct JSIConverter; #include #include #include +#include namespace margelo::nitro { diff --git a/packages/react-native-nitro-modules/cpp/jsi/JSIConverter+UnorderedMap.hpp b/packages/react-native-nitro-modules/cpp/jsi/JSIConverter+UnorderedMap.hpp index cc8b51924b..b30aa0d2bf 100644 --- a/packages/react-native-nitro-modules/cpp/jsi/JSIConverter+UnorderedMap.hpp +++ b/packages/react-native-nitro-modules/cpp/jsi/JSIConverter+UnorderedMap.hpp @@ -17,6 +17,7 @@ struct JSIConverter; #include "PropNameIDCache.hpp" #include #include +#include namespace margelo::nitro { diff --git a/packages/react-native-nitro-modules/cpp/jsi/JSIConverter+Vector.hpp b/packages/react-native-nitro-modules/cpp/jsi/JSIConverter+Vector.hpp index 4cefdb0e0c..a2ffb1b551 100644 --- a/packages/react-native-nitro-modules/cpp/jsi/JSIConverter+Vector.hpp +++ b/packages/react-native-nitro-modules/cpp/jsi/JSIConverter+Vector.hpp @@ -14,6 +14,7 @@ struct JSIConverter; #include "AnyMap.hpp" #include +#include #include namespace margelo::nitro { diff --git a/packages/react-native-nitro-modules/cpp/jsi/JSIConverter.hpp b/packages/react-native-nitro-modules/cpp/jsi/JSIConverter.hpp index 85487b13d1..fda382e0c7 100644 --- a/packages/react-native-nitro-modules/cpp/jsi/JSIConverter.hpp +++ b/packages/react-native-nitro-modules/cpp/jsi/JSIConverter.hpp @@ -10,6 +10,7 @@ template struct JSIConverter; } // namespace margelo::nitro +#include #include #include #include diff --git a/packages/react-native-nitro-modules/cpp/platform/NitroLogger.hpp b/packages/react-native-nitro-modules/cpp/platform/NitroLogger.hpp index 59daefb828..965f0bed12 100644 --- a/packages/react-native-nitro-modules/cpp/platform/NitroLogger.hpp +++ b/packages/react-native-nitro-modules/cpp/platform/NitroLogger.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -44,8 +45,13 @@ class Logger final { private: template static std::string formatString(const char* NON_NULL format, Args... args) { +#if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wformat-security" +#elif defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-security" +#endif int size = snprintf(nullptr, 0, format, args...) + 1; // Extra space for '\0' if (size <= 0) { return "Error during formatting."; @@ -53,7 +59,11 @@ class Logger final { std::unique_ptr buf(new char[size]); snprintf(buf.get(), size, format, args...); return std::string(buf.get(), buf.get() + size - 1); // We don't want the '\0' inside +#if defined(__clang__) #pragma clang diagnostic pop +#elif defined(__GNUC__) +#pragma GCC diagnostic pop +#endif } // Overloaded functions to convert std::string to C-style string diff --git a/packages/react-native-nitro-modules/cpp/prototype/HybridObjectPrototype.cpp b/packages/react-native-nitro-modules/cpp/prototype/HybridObjectPrototype.cpp index 62ab82a6a9..ffa122bbf7 100644 --- a/packages/react-native-nitro-modules/cpp/prototype/HybridObjectPrototype.cpp +++ b/packages/react-native-nitro-modules/cpp/prototype/HybridObjectPrototype.cpp @@ -10,6 +10,7 @@ #include "NitroDefines.hpp" #include "NitroLogger.hpp" #include "NitroTypeInfo.hpp" +#include namespace margelo::nitro { diff --git a/packages/react-native-nitro-modules/cpp/registry/HybridObjectRegistry.cpp b/packages/react-native-nitro-modules/cpp/registry/HybridObjectRegistry.cpp index 1002ae0b98..f1af2ee515 100644 --- a/packages/react-native-nitro-modules/cpp/registry/HybridObjectRegistry.cpp +++ b/packages/react-native-nitro-modules/cpp/registry/HybridObjectRegistry.cpp @@ -9,6 +9,7 @@ #include "NitroDefines.hpp" #include "NitroLogger.hpp" #include +#include namespace margelo::nitro { diff --git a/packages/react-native-nitro-modules/cpp/templates/IsSharedPtrTo.hpp b/packages/react-native-nitro-modules/cpp/templates/IsSharedPtrTo.hpp index 9ba0987dc8..771c9e73a7 100644 --- a/packages/react-native-nitro-modules/cpp/templates/IsSharedPtrTo.hpp +++ b/packages/react-native-nitro-modules/cpp/templates/IsSharedPtrTo.hpp @@ -7,12 +7,11 @@ #pragma once +#include #include namespace margelo::nitro { -using namespace facebook; - // Returns whether the given type T is a shared_ptr to type P. template struct is_shared_ptr_to : std::false_type {}; diff --git a/packages/react-native-nitro-modules/cpp/templates/TypeIndex.hpp b/packages/react-native-nitro-modules/cpp/templates/TypeIndex.hpp index a4114b8713..262e7da978 100644 --- a/packages/react-native-nitro-modules/cpp/templates/TypeIndex.hpp +++ b/packages/react-native-nitro-modules/cpp/templates/TypeIndex.hpp @@ -7,6 +7,7 @@ #pragma once +#include #include #include diff --git a/packages/react-native-nitro-modules/cpp/threading/CallInvokerDispatcher.hpp b/packages/react-native-nitro-modules/cpp/threading/CallInvokerDispatcher.hpp index d55f692163..86181308b2 100644 --- a/packages/react-native-nitro-modules/cpp/threading/CallInvokerDispatcher.hpp +++ b/packages/react-native-nitro-modules/cpp/threading/CallInvokerDispatcher.hpp @@ -9,6 +9,7 @@ // This is react-native specific #if __has_include() #include +#include namespace margelo::nitro { diff --git a/packages/react-native-nitro-modules/cpp/threading/Dispatcher.cpp b/packages/react-native-nitro-modules/cpp/threading/Dispatcher.cpp index 84fc2bb33c..c4105e73f7 100644 --- a/packages/react-native-nitro-modules/cpp/threading/Dispatcher.cpp +++ b/packages/react-native-nitro-modules/cpp/threading/Dispatcher.cpp @@ -11,6 +11,7 @@ #include "JSIHelpers.hpp" #include "NitroDefines.hpp" #include "NitroLogger.hpp" +#include namespace margelo::nitro { diff --git a/packages/react-native-nitro-modules/cpp/threading/Dispatcher.hpp b/packages/react-native-nitro-modules/cpp/threading/Dispatcher.hpp index dfe307086b..445a5d2a84 100644 --- a/packages/react-native-nitro-modules/cpp/threading/Dispatcher.hpp +++ b/packages/react-native-nitro-modules/cpp/threading/Dispatcher.hpp @@ -10,6 +10,7 @@ #include #include #include +#include namespace margelo::nitro { diff --git a/packages/react-native-nitro-modules/cpp/threading/ThreadPool.cpp b/packages/react-native-nitro-modules/cpp/threading/ThreadPool.cpp index b195f72092..ca55c266b2 100644 --- a/packages/react-native-nitro-modules/cpp/threading/ThreadPool.cpp +++ b/packages/react-native-nitro-modules/cpp/threading/ThreadPool.cpp @@ -8,6 +8,7 @@ #include "ThreadPool.hpp" #include "NitroLogger.hpp" #include "ThreadUtils.hpp" +#include namespace margelo::nitro { diff --git a/packages/react-native-nitro-modules/cpp/utils/AssertPromiseState.hpp b/packages/react-native-nitro-modules/cpp/utils/AssertPromiseState.hpp index c89cc029aa..6f613af833 100644 --- a/packages/react-native-nitro-modules/cpp/utils/AssertPromiseState.hpp +++ b/packages/react-native-nitro-modules/cpp/utils/AssertPromiseState.hpp @@ -10,17 +10,18 @@ namespace margelo::nitro { template class Promise; + +enum PromiseTask { WANTS_TO_RESOLVE, WANTS_TO_REJECT }; } // namespace margelo::nitro #include "NitroTypeInfo.hpp" #include "Promise.hpp" #include +#include #include namespace margelo::nitro { -enum PromiseTask { WANTS_TO_RESOLVE, WANTS_TO_REJECT }; - template void assertPromiseState(Promise& promise, PromiseTask task) { if (!promise.isPending()) [[unlikely]] { diff --git a/packages/react-native-nitro-modules/cpp/utils/CommonGlobals.cpp b/packages/react-native-nitro-modules/cpp/utils/CommonGlobals.cpp index 126fe49bec..7396245fbe 100644 --- a/packages/react-native-nitro-modules/cpp/utils/CommonGlobals.cpp +++ b/packages/react-native-nitro-modules/cpp/utils/CommonGlobals.cpp @@ -13,6 +13,7 @@ #if __has_include() #include +#include #if REACT_NATIVE_VERSION_MINOR >= 78 #define ENABLE_NATIVE_OBJECT_CREATE #endif diff --git a/packages/react-native-nitro-modules/cpp/utils/CommonGlobals.hpp b/packages/react-native-nitro-modules/cpp/utils/CommonGlobals.hpp index 388e74b514..65492df439 100644 --- a/packages/react-native-nitro-modules/cpp/utils/CommonGlobals.hpp +++ b/packages/react-native-nitro-modules/cpp/utils/CommonGlobals.hpp @@ -8,6 +8,7 @@ #pragma once #include "BorrowingReference.hpp" +#include #include #include diff --git a/packages/react-native-nitro-modules/cpp/utils/FastVectorCopy.hpp b/packages/react-native-nitro-modules/cpp/utils/FastVectorCopy.hpp index f8c0778260..dbfaa0e367 100644 --- a/packages/react-native-nitro-modules/cpp/utils/FastVectorCopy.hpp +++ b/packages/react-native-nitro-modules/cpp/utils/FastVectorCopy.hpp @@ -8,6 +8,9 @@ #pragma once #include "NitroDefines.hpp" +#include +#include +#include #include #include #include diff --git a/packages/react-native-nitro-modules/cpp/utils/JSCallback.hpp b/packages/react-native-nitro-modules/cpp/utils/JSCallback.hpp index 3d7f39bbaf..cd426f2a84 100644 --- a/packages/react-native-nitro-modules/cpp/utils/JSCallback.hpp +++ b/packages/react-native-nitro-modules/cpp/utils/JSCallback.hpp @@ -15,10 +15,12 @@ struct JSIConverter; #include "BorrowingReference.hpp" #include "JSIConverter.hpp" #include "NitroDefines.hpp" +#include "NitroLogger.hpp" #include "NitroTypeInfo.hpp" #include "Promise.hpp" #include #include +#include namespace margelo::nitro { diff --git a/packages/react-native-nitro-modules/cpp/utils/NitroDefines.hpp b/packages/react-native-nitro-modules/cpp/utils/NitroDefines.hpp index f4b1548df4..04502e6a93 100644 --- a/packages/react-native-nitro-modules/cpp/utils/NitroDefines.hpp +++ b/packages/react-native-nitro-modules/cpp/utils/NitroDefines.hpp @@ -12,7 +12,8 @@ #define NITRO_VERSION "0.36.5" // Sets whether to use debug or optimized production build flags -#ifdef DEBUG +// (`_DEBUG` is what MSVC defines for debug configurations) +#if defined(DEBUG) || defined(_DEBUG) #define NITRO_DEBUG #endif #ifdef NDEBUG diff --git a/packages/react-native-nitro-modules/cpp/utils/NitroTypeInfo.cpp b/packages/react-native-nitro-modules/cpp/utils/NitroTypeInfo.cpp index 85c711a31f..2cae6a3acc 100644 --- a/packages/react-native-nitro-modules/cpp/utils/NitroTypeInfo.cpp +++ b/packages/react-native-nitro-modules/cpp/utils/NitroTypeInfo.cpp @@ -7,6 +7,10 @@ #include "NitroTypeInfo.hpp" +#include +#include +#include + #if __has_include() #include #endif diff --git a/packages/react-native-nitro-modules/cpp/utils/NitroTypeInfo.hpp b/packages/react-native-nitro-modules/cpp/utils/NitroTypeInfo.hpp index 128b807e3f..38b624a2d6 100644 --- a/packages/react-native-nitro-modules/cpp/utils/NitroTypeInfo.hpp +++ b/packages/react-native-nitro-modules/cpp/utils/NitroTypeInfo.hpp @@ -13,6 +13,7 @@ #include #include #include +#include namespace margelo::nitro { diff --git a/packages/react-native-nitro-modules/cpp/utils/PropNameIDCache.cpp b/packages/react-native-nitro-modules/cpp/utils/PropNameIDCache.cpp index e238bd7e5a..595c9c43f4 100644 --- a/packages/react-native-nitro-modules/cpp/utils/PropNameIDCache.cpp +++ b/packages/react-native-nitro-modules/cpp/utils/PropNameIDCache.cpp @@ -7,6 +7,7 @@ #include "PropNameIDCache.hpp" #include "JSICache.hpp" +#include namespace margelo::nitro { diff --git a/packages/react-native-nitro-modules/cpp/utils/PropNameIDCache.hpp b/packages/react-native-nitro-modules/cpp/utils/PropNameIDCache.hpp index 1867ec9072..f25ba2d371 100644 --- a/packages/react-native-nitro-modules/cpp/utils/PropNameIDCache.hpp +++ b/packages/react-native-nitro-modules/cpp/utils/PropNameIDCache.hpp @@ -10,6 +10,8 @@ #include "BorrowingReference.hpp" #include #include +#include +#include namespace margelo::nitro { diff --git a/packages/react-native-nitro-modules/cpp/utils/WeakReference.hpp b/packages/react-native-nitro-modules/cpp/utils/WeakReference.hpp index 22ca5c77fb..9c5cd204af 100644 --- a/packages/react-native-nitro-modules/cpp/utils/WeakReference.hpp +++ b/packages/react-native-nitro-modules/cpp/utils/WeakReference.hpp @@ -8,10 +8,13 @@ #pragma once #include "NitroDefines.hpp" +#include "NitroTypeInfo.hpp" #include "ReferenceState.hpp" #include #include #include +#include +#include namespace margelo::nitro { diff --git a/packages/react-native-nitro-modules/cpp/views/CachedProp.hpp b/packages/react-native-nitro-modules/cpp/views/CachedProp.hpp index 744324d70d..5b30628124 100644 --- a/packages/react-native-nitro-modules/cpp/views/CachedProp.hpp +++ b/packages/react-native-nitro-modules/cpp/views/CachedProp.hpp @@ -8,6 +8,7 @@ #include "JSIConverter.hpp" #include "NitroDefines.hpp" #include +#include namespace margelo::nitro { From 2914fc719b49c5ac6666b6ab58d5c78aba749908 Mon Sep 17 00:00:00 2001 From: Ritesh Shukla Date: Sat, 15 Aug 2026 19:34:27 +0530 Subject: [PATCH 2/5] feat(nitro-modules): add Windows (react-native-windows) support Adds the Windows platform layer and the `NitroModules` TurboModule that installs Nitro into the JS runtime, so `HybridObject<{ windows: "c++" }>` works on react-native-windows. * `NativeNitroModules` installs Nitro from a two-argument `REACT_INIT`, which RNW dispatches through `AddJsiInitializer` on the JS thread. That is the only JSI entry-point that still works under the New Architecture - `ExecuteJsi`, `TryGetOrCreateContextRuntime` and `ReactContext::JSRuntime` are deprecated there and either fail-fast in Debug or no-op in Release. * `UIThreadDispatcher` backs `ThreadUtils`. Windows has no process-wide handle to the UI thread queue the way `dispatch_get_main_queue()` and the main `Looper` are, so it is taken from the `ReactContext` at install time. `runSync` throws, because `IReactDispatcher` only exposes `Post`. * `NitroLogger` writes to the debugger output window; Windows has no logcat. * `NitroModules.vcxproj` builds a **StaticLibrary**. RNW gives every module image its own `JsiAbiRuntime` wrapper, so Nitro in a separate DLL would see a different `jsi::Runtime*` than the Hybrid Objects linked into the app, while `JSICache`, `PropNameIDCache` and `Dispatcher` are keyed on the runtime and `HybridObjectRegistry` has to be a single registry. * `windows/include/NitroModules/` forwards the core headers so `#include ` resolves the same way it does through the CocoaPods module on iOS. `PublicIncludeDirectories` puts them on the include path of every project that references this one. * `ReactPackageProvider` registers the module explicitly rather than through `AddAttributedModules`. The linker does not pull an object file out of a static library unless one of its symbols is referenced, so the `REACT_MODULE` registration would silently never run. Co-Authored-By: Claude Opus 5 --- .../react-native-nitro-modules/package.json | 2 + .../react-native.config.js | 11 ++ .../src/HybridObject.ts | 1 + .../windows/NitroModules/NitroModules.vcxproj | 170 ++++++++++++++++++ .../NitroModules/ReactPackageProvider.cpp | 29 +++ .../NitroModules/ReactPackageProvider.h | 11 ++ .../windows/NitroModules/pch.h | 15 ++ .../windows/include/NitroModules/AnyMap.hpp | 3 + .../include/NitroModules/ArrayBuffer.hpp | 3 + .../include/NitroModules/CachedProp.hpp | 3 + .../include/NitroModules/Dispatcher.hpp | 3 + .../include/NitroModules/FastVectorCopy.hpp | 3 + .../NitroModules/HybridNitroModulesProxy.hpp | 3 + .../include/NitroModules/HybridObject.hpp | 3 + .../NitroModules/HybridObjectRegistry.hpp | 3 + .../include/NitroModules/InstallNitro.hpp | 3 + .../include/NitroModules/JSCallback.hpp | 3 + .../include/NitroModules/JSIConverter.hpp | 3 + .../include/NitroModules/JSIHelpers.hpp | 3 + .../include/NitroModules/NitroDefines.hpp | 3 + .../include/NitroModules/NitroHash.hpp | 3 + .../include/NitroModules/NitroLogger.hpp | 3 + .../windows/include/NitroModules/Null.hpp | 3 + .../windows/include/NitroModules/Promise.hpp | 3 + .../include/NitroModules/PropNameIDCache.hpp | 3 + .../NitroModules/ReactPackageProvider.h | 3 + .../windows/platform/NitroLogger.cpp | 37 ++++ .../windows/platform/ThreadUtils.cpp | 76 ++++++++ .../windows/threading/UIThreadDispatcher.cpp | 55 ++++++ .../windows/threading/UIThreadDispatcher.hpp | 30 ++++ .../turbomodule/NativeNitroModules.cpp | 39 ++++ .../windows/turbomodule/NativeNitroModules.h | 25 +++ 32 files changed, 558 insertions(+) create mode 100644 packages/react-native-nitro-modules/windows/NitroModules/NitroModules.vcxproj create mode 100644 packages/react-native-nitro-modules/windows/NitroModules/ReactPackageProvider.cpp create mode 100644 packages/react-native-nitro-modules/windows/NitroModules/ReactPackageProvider.h create mode 100644 packages/react-native-nitro-modules/windows/NitroModules/pch.h create mode 100644 packages/react-native-nitro-modules/windows/include/NitroModules/AnyMap.hpp create mode 100644 packages/react-native-nitro-modules/windows/include/NitroModules/ArrayBuffer.hpp create mode 100644 packages/react-native-nitro-modules/windows/include/NitroModules/CachedProp.hpp create mode 100644 packages/react-native-nitro-modules/windows/include/NitroModules/Dispatcher.hpp create mode 100644 packages/react-native-nitro-modules/windows/include/NitroModules/FastVectorCopy.hpp create mode 100644 packages/react-native-nitro-modules/windows/include/NitroModules/HybridNitroModulesProxy.hpp create mode 100644 packages/react-native-nitro-modules/windows/include/NitroModules/HybridObject.hpp create mode 100644 packages/react-native-nitro-modules/windows/include/NitroModules/HybridObjectRegistry.hpp create mode 100644 packages/react-native-nitro-modules/windows/include/NitroModules/InstallNitro.hpp create mode 100644 packages/react-native-nitro-modules/windows/include/NitroModules/JSCallback.hpp create mode 100644 packages/react-native-nitro-modules/windows/include/NitroModules/JSIConverter.hpp create mode 100644 packages/react-native-nitro-modules/windows/include/NitroModules/JSIHelpers.hpp create mode 100644 packages/react-native-nitro-modules/windows/include/NitroModules/NitroDefines.hpp create mode 100644 packages/react-native-nitro-modules/windows/include/NitroModules/NitroHash.hpp create mode 100644 packages/react-native-nitro-modules/windows/include/NitroModules/NitroLogger.hpp create mode 100644 packages/react-native-nitro-modules/windows/include/NitroModules/Null.hpp create mode 100644 packages/react-native-nitro-modules/windows/include/NitroModules/Promise.hpp create mode 100644 packages/react-native-nitro-modules/windows/include/NitroModules/PropNameIDCache.hpp create mode 100644 packages/react-native-nitro-modules/windows/include/NitroModules/ReactPackageProvider.h create mode 100644 packages/react-native-nitro-modules/windows/platform/NitroLogger.cpp create mode 100644 packages/react-native-nitro-modules/windows/platform/ThreadUtils.cpp create mode 100644 packages/react-native-nitro-modules/windows/threading/UIThreadDispatcher.cpp create mode 100644 packages/react-native-nitro-modules/windows/threading/UIThreadDispatcher.hpp create mode 100644 packages/react-native-nitro-modules/windows/turbomodule/NativeNitroModules.cpp create mode 100644 packages/react-native-nitro-modules/windows/turbomodule/NativeNitroModules.h diff --git a/packages/react-native-nitro-modules/package.json b/packages/react-native-nitro-modules/package.json index fc3f32a99d..4cb9f45361 100644 --- a/packages/react-native-nitro-modules/package.json +++ b/packages/react-native-nitro-modules/package.json @@ -18,6 +18,7 @@ "android/CMakeLists.txt", "android/src/", "ios/", + "windows/", "cpp/", "app.plugin.js", "*.podspec", @@ -30,6 +31,7 @@ "ios", "android", "visionOS", + "windows", "tvOS", "macOS", "cpp", diff --git a/packages/react-native-nitro-modules/react-native.config.js b/packages/react-native-nitro-modules/react-native.config.js index 3fdf8eaadc..ba69562db4 100644 --- a/packages/react-native-nitro-modules/react-native.config.js +++ b/packages/react-native-nitro-modules/react-native.config.js @@ -11,6 +11,17 @@ module.exports = { * @type {import('@react-native-community/cli-types').AndroidDependencyParams} */ android: {}, + windows: { + sourceDir: 'windows', + projects: [ + { + projectFile: 'NitroModules\\NitroModules.vcxproj', + directDependency: true, + cppHeaders: ['NitroModules/ReactPackageProvider.h'], + cppPackageProviders: ['NitroModules::ReactPackageProvider'], + }, + ], + }, }, }, } diff --git a/packages/react-native-nitro-modules/src/HybridObject.ts b/packages/react-native-nitro-modules/src/HybridObject.ts index b5c7f807fc..d80d98191b 100644 --- a/packages/react-native-nitro-modules/src/HybridObject.ts +++ b/packages/react-native-nitro-modules/src/HybridObject.ts @@ -7,6 +7,7 @@ export interface PlatformSpec { ios?: 'c++' | 'swift' android?: 'c++' | 'kotlin' + windows?: 'c++' } /** diff --git a/packages/react-native-nitro-modules/windows/NitroModules/NitroModules.vcxproj b/packages/react-native-nitro-modules/windows/NitroModules/NitroModules.vcxproj new file mode 100644 index 0000000000..1193b44cc8 --- /dev/null +++ b/packages/react-native-nitro-modules/windows/NitroModules/NitroModules.vcxproj @@ -0,0 +1,170 @@ + + + + + true + true + {C6E3E6E4-3D5F-4C0B-9A5E-5D2C1B9F7A10} + NitroModules + NitroModules + Win32Proj + 10.0 + en-US + 17.0 + false + false + false + false + + + + true + $([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), 'node_modules\react-native-windows\package.json'))\node_modules\react-native-windows\ + + + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + Debug + ARM64 + + + Release + ARM64 + + + + + StaticLibrary + Unicode + v143 + + + true + + + false + true + + + + + + + + + + + + + + MultiThreadedDebug + MultiThreaded + + + + + $(MSBuildThisFileDirectory)..\include; + $(MSBuildThisFileDirectory)..\..\cpp\core; + $(MSBuildThisFileDirectory)..\..\cpp\entrypoint; + $(MSBuildThisFileDirectory)..\..\cpp\jsi; + $(MSBuildThisFileDirectory)..\..\cpp\platform; + $(MSBuildThisFileDirectory)..\..\cpp\prototype; + $(MSBuildThisFileDirectory)..\..\cpp\registry; + $(MSBuildThisFileDirectory)..\..\cpp\templates; + $(MSBuildThisFileDirectory)..\..\cpp\threading; + $(MSBuildThisFileDirectory)..\..\cpp\utils; + $(MSBuildThisFileDirectory)..\..\cpp\views; + $(PublicIncludeDirectories) + + + + + + NotUsing + stdcpp20 + %(AdditionalOptions) /bigobj + Sync + $(NitroRuntimeLibrary) + Level3 + $(MSBuildThisFileDirectory);$(MSBuildThisFileDirectory)..;$(MSBuildThisFileDirectory)..\platform;$(MSBuildThisFileDirectory)..\threading;$(MSBuildThisFileDirectory)..\turbomodule;$(PublicIncludeDirectories);%(AdditionalIncludeDirectories) + NOMINMAX;WIN32_LEAN_AND_MEAN;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions) + + + + + _DEBUG;%(PreprocessorDefinitions) + + + + + NDEBUG;%(PreprocessorDefinitions) + + + + + RNW_NEW_ARCH;%(PreprocessorDefinitions) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This project references targets in your node_modules\react-native-windows folder. The missing file is {0}. + + + + + diff --git a/packages/react-native-nitro-modules/windows/NitroModules/ReactPackageProvider.cpp b/packages/react-native-nitro-modules/windows/NitroModules/ReactPackageProvider.cpp new file mode 100644 index 0000000000..fadd5cad84 --- /dev/null +++ b/packages/react-native-nitro-modules/windows/NitroModules/ReactPackageProvider.cpp @@ -0,0 +1,29 @@ +#include "ReactPackageProvider.h" + +#include "NativeNitroModules.h" + +#include + +namespace winrt::NitroModules { + +using namespace winrt::Microsoft::ReactNative; + +namespace { + +struct NitroReactPackageProvider : winrt::implements { + void CreatePackage(IReactPackageBuilder const& packageBuilder) noexcept { +#ifdef RNW_NEW_ARCH + packageBuilder.AddTurboModule(L"NitroModules", MakeModuleProvider()); +#else + packageBuilder.AddModule(L"NitroModules", MakeModuleProvider()); +#endif + } +}; + +} // namespace + +IReactPackageProvider ReactPackageProvider() { + return winrt::make(); +} + +} // namespace winrt::NitroModules diff --git a/packages/react-native-nitro-modules/windows/NitroModules/ReactPackageProvider.h b/packages/react-native-nitro-modules/windows/NitroModules/ReactPackageProvider.h new file mode 100644 index 0000000000..bb59cf729f --- /dev/null +++ b/packages/react-native-nitro-modules/windows/NitroModules/ReactPackageProvider.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +#include + +namespace winrt::NitroModules { + +winrt::Microsoft::ReactNative::IReactPackageProvider ReactPackageProvider(); + +} // namespace winrt::NitroModules diff --git a/packages/react-native-nitro-modules/windows/NitroModules/pch.h b/packages/react-native-nitro-modules/windows/NitroModules/pch.h new file mode 100644 index 0000000000..a5cc0c9838 --- /dev/null +++ b/packages/react-native-nitro-modules/windows/NitroModules/pch.h @@ -0,0 +1,15 @@ +#pragma once + +#define NOMINMAX 1 +#define WIN32_LEAN_AND_MEAN 1 +#define WINRT_LEAN_AND_MEAN 1 + +#include +#undef GetCurrentTime + +#include + +#include + +#include +#include diff --git a/packages/react-native-nitro-modules/windows/include/NitroModules/AnyMap.hpp b/packages/react-native-nitro-modules/windows/include/NitroModules/AnyMap.hpp new file mode 100644 index 0000000000..069219f000 --- /dev/null +++ b/packages/react-native-nitro-modules/windows/include/NitroModules/AnyMap.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "../../../cpp/core/AnyMap.hpp" diff --git a/packages/react-native-nitro-modules/windows/include/NitroModules/ArrayBuffer.hpp b/packages/react-native-nitro-modules/windows/include/NitroModules/ArrayBuffer.hpp new file mode 100644 index 0000000000..39c09e3c26 --- /dev/null +++ b/packages/react-native-nitro-modules/windows/include/NitroModules/ArrayBuffer.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "../../../cpp/core/ArrayBuffer.hpp" diff --git a/packages/react-native-nitro-modules/windows/include/NitroModules/CachedProp.hpp b/packages/react-native-nitro-modules/windows/include/NitroModules/CachedProp.hpp new file mode 100644 index 0000000000..ef8e4c0a11 --- /dev/null +++ b/packages/react-native-nitro-modules/windows/include/NitroModules/CachedProp.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "../../../cpp/views/CachedProp.hpp" diff --git a/packages/react-native-nitro-modules/windows/include/NitroModules/Dispatcher.hpp b/packages/react-native-nitro-modules/windows/include/NitroModules/Dispatcher.hpp new file mode 100644 index 0000000000..c7c0d7cd74 --- /dev/null +++ b/packages/react-native-nitro-modules/windows/include/NitroModules/Dispatcher.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "../../../cpp/threading/Dispatcher.hpp" diff --git a/packages/react-native-nitro-modules/windows/include/NitroModules/FastVectorCopy.hpp b/packages/react-native-nitro-modules/windows/include/NitroModules/FastVectorCopy.hpp new file mode 100644 index 0000000000..becbde1d95 --- /dev/null +++ b/packages/react-native-nitro-modules/windows/include/NitroModules/FastVectorCopy.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "../../../cpp/utils/FastVectorCopy.hpp" diff --git a/packages/react-native-nitro-modules/windows/include/NitroModules/HybridNitroModulesProxy.hpp b/packages/react-native-nitro-modules/windows/include/NitroModules/HybridNitroModulesProxy.hpp new file mode 100644 index 0000000000..39cfde0619 --- /dev/null +++ b/packages/react-native-nitro-modules/windows/include/NitroModules/HybridNitroModulesProxy.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "../../../cpp/entrypoint/HybridNitroModulesProxy.hpp" diff --git a/packages/react-native-nitro-modules/windows/include/NitroModules/HybridObject.hpp b/packages/react-native-nitro-modules/windows/include/NitroModules/HybridObject.hpp new file mode 100644 index 0000000000..bf5298b806 --- /dev/null +++ b/packages/react-native-nitro-modules/windows/include/NitroModules/HybridObject.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "../../../cpp/core/HybridObject.hpp" diff --git a/packages/react-native-nitro-modules/windows/include/NitroModules/HybridObjectRegistry.hpp b/packages/react-native-nitro-modules/windows/include/NitroModules/HybridObjectRegistry.hpp new file mode 100644 index 0000000000..3473165c93 --- /dev/null +++ b/packages/react-native-nitro-modules/windows/include/NitroModules/HybridObjectRegistry.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "../../../cpp/registry/HybridObjectRegistry.hpp" diff --git a/packages/react-native-nitro-modules/windows/include/NitroModules/InstallNitro.hpp b/packages/react-native-nitro-modules/windows/include/NitroModules/InstallNitro.hpp new file mode 100644 index 0000000000..1251f1193a --- /dev/null +++ b/packages/react-native-nitro-modules/windows/include/NitroModules/InstallNitro.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "../../../cpp/entrypoint/InstallNitro.hpp" diff --git a/packages/react-native-nitro-modules/windows/include/NitroModules/JSCallback.hpp b/packages/react-native-nitro-modules/windows/include/NitroModules/JSCallback.hpp new file mode 100644 index 0000000000..a39c7c9093 --- /dev/null +++ b/packages/react-native-nitro-modules/windows/include/NitroModules/JSCallback.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "../../../cpp/utils/JSCallback.hpp" diff --git a/packages/react-native-nitro-modules/windows/include/NitroModules/JSIConverter.hpp b/packages/react-native-nitro-modules/windows/include/NitroModules/JSIConverter.hpp new file mode 100644 index 0000000000..e18456eb6c --- /dev/null +++ b/packages/react-native-nitro-modules/windows/include/NitroModules/JSIConverter.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "../../../cpp/jsi/JSIConverter.hpp" diff --git a/packages/react-native-nitro-modules/windows/include/NitroModules/JSIHelpers.hpp b/packages/react-native-nitro-modules/windows/include/NitroModules/JSIHelpers.hpp new file mode 100644 index 0000000000..3c1a3019fa --- /dev/null +++ b/packages/react-native-nitro-modules/windows/include/NitroModules/JSIHelpers.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "../../../cpp/jsi/JSIHelpers.hpp" diff --git a/packages/react-native-nitro-modules/windows/include/NitroModules/NitroDefines.hpp b/packages/react-native-nitro-modules/windows/include/NitroModules/NitroDefines.hpp new file mode 100644 index 0000000000..e8a6259768 --- /dev/null +++ b/packages/react-native-nitro-modules/windows/include/NitroModules/NitroDefines.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "../../../cpp/utils/NitroDefines.hpp" diff --git a/packages/react-native-nitro-modules/windows/include/NitroModules/NitroHash.hpp b/packages/react-native-nitro-modules/windows/include/NitroModules/NitroHash.hpp new file mode 100644 index 0000000000..4651976efe --- /dev/null +++ b/packages/react-native-nitro-modules/windows/include/NitroModules/NitroHash.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "../../../cpp/utils/NitroHash.hpp" diff --git a/packages/react-native-nitro-modules/windows/include/NitroModules/NitroLogger.hpp b/packages/react-native-nitro-modules/windows/include/NitroModules/NitroLogger.hpp new file mode 100644 index 0000000000..48bb235e36 --- /dev/null +++ b/packages/react-native-nitro-modules/windows/include/NitroModules/NitroLogger.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "../../../cpp/platform/NitroLogger.hpp" diff --git a/packages/react-native-nitro-modules/windows/include/NitroModules/Null.hpp b/packages/react-native-nitro-modules/windows/include/NitroModules/Null.hpp new file mode 100644 index 0000000000..dfa22820f6 --- /dev/null +++ b/packages/react-native-nitro-modules/windows/include/NitroModules/Null.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "../../../cpp/core/Null.hpp" diff --git a/packages/react-native-nitro-modules/windows/include/NitroModules/Promise.hpp b/packages/react-native-nitro-modules/windows/include/NitroModules/Promise.hpp new file mode 100644 index 0000000000..5c108829e9 --- /dev/null +++ b/packages/react-native-nitro-modules/windows/include/NitroModules/Promise.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "../../../cpp/core/Promise.hpp" diff --git a/packages/react-native-nitro-modules/windows/include/NitroModules/PropNameIDCache.hpp b/packages/react-native-nitro-modules/windows/include/NitroModules/PropNameIDCache.hpp new file mode 100644 index 0000000000..8833a38914 --- /dev/null +++ b/packages/react-native-nitro-modules/windows/include/NitroModules/PropNameIDCache.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "../../../cpp/utils/PropNameIDCache.hpp" diff --git a/packages/react-native-nitro-modules/windows/include/NitroModules/ReactPackageProvider.h b/packages/react-native-nitro-modules/windows/include/NitroModules/ReactPackageProvider.h new file mode 100644 index 0000000000..167a1034ed --- /dev/null +++ b/packages/react-native-nitro-modules/windows/include/NitroModules/ReactPackageProvider.h @@ -0,0 +1,3 @@ +#pragma once + +#include "../../NitroModules/ReactPackageProvider.h" diff --git a/packages/react-native-nitro-modules/windows/platform/NitroLogger.cpp b/packages/react-native-nitro-modules/windows/platform/NitroLogger.cpp new file mode 100644 index 0000000000..af3759dd9d --- /dev/null +++ b/packages/react-native-nitro-modules/windows/platform/NitroLogger.cpp @@ -0,0 +1,37 @@ +#include "NitroLogger.hpp" + +#include + +#include + +namespace margelo::nitro { + +void Logger::nativeLog([[maybe_unused]] LogLevel level, // + [[maybe_unused]] const char* NON_NULL tag, // + [[maybe_unused]] const std::string& message) { +#ifdef NITRO_DEBUG + const char* levelTag; + switch (level) { + case LogLevel::Debug: + levelTag = "[D]"; + break; + case LogLevel::Info: + levelTag = "[I]"; + break; + case LogLevel::Warning: + levelTag = "[W]"; + break; + case LogLevel::Error: + levelTag = "[E]"; + break; + default: + levelTag = "[?]"; + break; + } + + std::string line = std::string(levelTag) + "[" + tag + "] " + message + "\n"; + OutputDebugStringA(line.c_str()); +#endif +} + +} // namespace margelo::nitro diff --git a/packages/react-native-nitro-modules/windows/platform/ThreadUtils.cpp b/packages/react-native-nitro-modules/windows/platform/ThreadUtils.cpp new file mode 100644 index 0000000000..eef7251f5a --- /dev/null +++ b/packages/react-native-nitro-modules/windows/platform/ThreadUtils.cpp @@ -0,0 +1,76 @@ +#include "ThreadUtils.hpp" +#include "UIThreadDispatcher.hpp" + +#include +#include +#include +#include + +#include + +#include + +namespace margelo::nitro { + +namespace { + +std::string toUtf8(PCWSTR wide) { + if (wide == nullptr || wide[0] == L'\0') { + return {}; + } + int size = WideCharToMultiByte(CP_UTF8, 0, wide, -1, nullptr, 0, nullptr, nullptr); + if (size <= 1) { + return {}; + } + std::string result(static_cast(size) - 1, '\0'); + WideCharToMultiByte(CP_UTF8, 0, wide, -1, result.data(), size, nullptr, nullptr); + return result; +} + +std::wstring toUtf16(const std::string& narrow) { + if (narrow.empty()) { + return {}; + } + int size = MultiByteToWideChar(CP_UTF8, 0, narrow.c_str(), -1, nullptr, 0); + if (size <= 1) { + return {}; + } + std::wstring result(static_cast(size) - 1, L'\0'); + MultiByteToWideChar(CP_UTF8, 0, narrow.c_str(), -1, result.data(), size); + return result; +} + +} // namespace + +std::string ThreadUtils::getThreadName() { + PWSTR description = nullptr; + if (SUCCEEDED(GetThreadDescription(GetCurrentThread(), &description))) { + std::string name = toUtf8(description); + LocalFree(description); + if (!name.empty()) { + return name; + } + } + + std::stringstream stream; + stream << std::this_thread::get_id(); + return std::string("Thread #") + stream.str(); +} + +void ThreadUtils::setThreadName(const std::string& name) { + std::wstring wideName = toUtf16(name); + if (wideName.empty()) { + return; + } + SetThreadDescription(GetCurrentThread(), wideName.c_str()); +} + +bool ThreadUtils::isUIThread() { + return UIThreadDispatcher::isUIThread(); +} + +std::shared_ptr ThreadUtils::createUIThreadDispatcher() { + return std::make_shared(UIThreadDispatcher::getUIDispatcher()); +} + +} // namespace margelo::nitro diff --git a/packages/react-native-nitro-modules/windows/threading/UIThreadDispatcher.cpp b/packages/react-native-nitro-modules/windows/threading/UIThreadDispatcher.cpp new file mode 100644 index 0000000000..f7de09fc1b --- /dev/null +++ b/packages/react-native-nitro-modules/windows/threading/UIThreadDispatcher.cpp @@ -0,0 +1,55 @@ +#include "UIThreadDispatcher.hpp" + +#include +#include +#include + +namespace margelo::nitro { + +using namespace winrt::Microsoft::ReactNative; + +namespace { + +std::mutex& uiDispatcherMutex() { + static std::mutex mutex; + return mutex; +} + +ReactDispatcher& uiDispatcherStorage() { + static ReactDispatcher dispatcher{nullptr}; + return dispatcher; +} + +} // namespace + +void UIThreadDispatcher::setUIDispatcher(const ReactDispatcher& dispatcher) { + std::lock_guard lock(uiDispatcherMutex()); + uiDispatcherStorage() = dispatcher; +} + +ReactDispatcher UIThreadDispatcher::getUIDispatcher() { + std::lock_guard lock(uiDispatcherMutex()); + ReactDispatcher dispatcher = uiDispatcherStorage(); + if (!dispatcher) { + throw std::runtime_error("The UI Thread Dispatcher is not available yet - Nitro has not been installed! " + "Make sure `NitroModules` is autolinked into your app."); + } + return dispatcher; +} + +bool UIThreadDispatcher::isUIThread() { + std::lock_guard lock(uiDispatcherMutex()); + ReactDispatcher dispatcher = uiDispatcherStorage(); + return dispatcher ? dispatcher.HasThreadAccess() : false; +} + +void UIThreadDispatcher::runSync(std::function&& /* function */) { + throw std::runtime_error("UIThreadDispatcher::runSync() is not implemented on Windows!"); +} + +void UIThreadDispatcher::runAsync(std::function&& function) { + auto shared = std::make_shared>(std::move(function)); + _dispatcher.Post([shared]() noexcept { (*shared)(); }); +} + +} // namespace margelo::nitro diff --git a/packages/react-native-nitro-modules/windows/threading/UIThreadDispatcher.hpp b/packages/react-native-nitro-modules/windows/threading/UIThreadDispatcher.hpp new file mode 100644 index 0000000000..8728a77432 --- /dev/null +++ b/packages/react-native-nitro-modules/windows/threading/UIThreadDispatcher.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include "Dispatcher.hpp" + +#include +#include +#include + +#include + +namespace margelo::nitro { + +class UIThreadDispatcher final : public Dispatcher { +public: + explicit UIThreadDispatcher(winrt::Microsoft::ReactNative::ReactDispatcher dispatcher) : _dispatcher(std::move(dispatcher)) {} + + void runSync(std::function&& function) override; + void runAsync(std::function&& function) override; + + static bool isUIThread(); + + static void setUIDispatcher(const winrt::Microsoft::ReactNative::ReactDispatcher& dispatcher); + + static winrt::Microsoft::ReactNative::ReactDispatcher getUIDispatcher(); + +private: + winrt::Microsoft::ReactNative::ReactDispatcher _dispatcher; +}; + +} // namespace margelo::nitro diff --git a/packages/react-native-nitro-modules/windows/turbomodule/NativeNitroModules.cpp b/packages/react-native-nitro-modules/windows/turbomodule/NativeNitroModules.cpp new file mode 100644 index 0000000000..0e47c9d593 --- /dev/null +++ b/packages/react-native-nitro-modules/windows/turbomodule/NativeNitroModules.cpp @@ -0,0 +1,39 @@ +#include "NativeNitroModules.h" + +#include "CallInvokerDispatcher.hpp" +#include "InstallNitro.hpp" +#include "NitroLogger.hpp" +#include "UIThreadDispatcher.hpp" + +#include +#include + +namespace winrt::NitroModules { + +using namespace winrt::Microsoft::ReactNative; + +void NativeNitroModules::Initialize(ReactContext const& reactContext, facebook::jsi::Runtime& runtime) noexcept { + try { + margelo::nitro::UIThreadDispatcher::setUIDispatcher(reactContext.UIDispatcher()); + + std::shared_ptr callInvoker = reactContext.CallInvoker(); + if (callInvoker == nullptr) { + throw std::runtime_error("React Native's CallInvoker is null!"); + } + auto dispatcher = std::make_shared(callInvoker); + + margelo::nitro::install(runtime, dispatcher); + } catch (const std::exception& exception) { + _errorMessage = exception.what(); + margelo::nitro::Logger::log(margelo::nitro::LogLevel::Error, "NitroModules", "Failed to install Nitro: %s", exception.what()); + } catch (...) { + _errorMessage = "An unknown error occurred."; + margelo::nitro::Logger::log(margelo::nitro::LogLevel::Error, "NitroModules", "Failed to install Nitro - unknown error!"); + } +} + +std::optional NativeNitroModules::install() noexcept { + return _errorMessage; +} + +} // namespace winrt::NitroModules diff --git a/packages/react-native-nitro-modules/windows/turbomodule/NativeNitroModules.h b/packages/react-native-nitro-modules/windows/turbomodule/NativeNitroModules.h new file mode 100644 index 0000000000..33138c8eb7 --- /dev/null +++ b/packages/react-native-nitro-modules/windows/turbomodule/NativeNitroModules.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + +#include + +#include +#include + +namespace winrt::NitroModules { + +REACT_MODULE_NOREG(NativeNitroModules, L"NitroModules") +struct NativeNitroModules { + REACT_INIT(Initialize) + void Initialize(winrt::Microsoft::ReactNative::ReactContext const& reactContext, facebook::jsi::Runtime& runtime) noexcept; + + REACT_SYNC_METHOD(install) + std::optional install() noexcept; + +private: + std::optional _errorMessage; +}; + +} // namespace winrt::NitroModules From f87fc07a97cb3e49bbd9a832255a531bd4770f81 Mon Sep 17 00:00:00 2001 From: Ritesh Shukla Date: Sat, 15 Aug 2026 19:34:49 +0530 Subject: [PATCH 3/5] feat(nitrogen): generate Windows autolinking Teaches nitrogen the `windows` platform, so `HybridObject<{ windows: "c++" }>` generates bindings and autolinking for react-native-windows. C++ is the only supported language there - RNW has no Swift or Kotlin equivalent - and the C++ specs are already shared with iOS and Android, so an existing C++ Hybrid Object usually needs nothing more than adding `windows: "c++"` to its platform spec. Two files are generated per module: * `+autolinking.props`, an MSBuild property sheet that adds every generated source to the consuming `.vcxproj` and exposes the generated headers through `PublicIncludeDirectories`. This is the Windows counterpart of `+autolinking.cmake` on Android. * `Autolinking.{hpp,cpp}`, an `IReactPackageProvider` that registers the autolinked Hybrid Objects. iOS uses `+load` and Android uses `JNI_OnLoad` to run registrations automatically; Windows has no such hook, so the registrations run from a package provider that `autolink-windows` wires into the app. Registration is guarded by `std::call_once`, because a package provider can be created more than once while `HybridObjectRegistry` is process-wide. The `winrt::` namespace comes from `windows.windowsModuleName` in nitro.json and falls back to `ios.iosModuleName`. Co-Authored-By: Claude Opus 5 --- .../autolinking/createWindowsAutolinking.ts | 21 +++ .../windows/createHybridObjectInitializer.ts | 130 ++++++++++++++++++ .../windows/createMsBuildExtension.ts | 101 ++++++++++++++ packages/nitrogen/src/config/NitroConfig.ts | 26 ++++ .../nitrogen/src/config/NitroUserConfig.ts | 31 +++++ packages/nitrogen/src/getPlatformSpecs.ts | 1 + packages/nitrogen/src/nitrogen.ts | 4 + packages/nitrogen/src/syntax/SourceFile.ts | 2 +- 8 files changed, 315 insertions(+), 1 deletion(-) create mode 100644 packages/nitrogen/src/autolinking/createWindowsAutolinking.ts create mode 100644 packages/nitrogen/src/autolinking/windows/createHybridObjectInitializer.ts create mode 100644 packages/nitrogen/src/autolinking/windows/createMsBuildExtension.ts diff --git a/packages/nitrogen/src/autolinking/createWindowsAutolinking.ts b/packages/nitrogen/src/autolinking/createWindowsAutolinking.ts new file mode 100644 index 0000000000..b56a698e9a --- /dev/null +++ b/packages/nitrogen/src/autolinking/createWindowsAutolinking.ts @@ -0,0 +1,21 @@ +import type { SourceFile } from '../syntax/SourceFile.js' +import type { Autolinking } from './Autolinking.js' +import { createHybridObjectIntializer } from './windows/createHybridObjectInitializer.js' +import { createMsBuildExtension } from './windows/createMsBuildExtension.js' + +interface WindowsAutolinking extends Autolinking {} + +export function createWindowsAutolinking( + allFiles: SourceFile[] +): WindowsAutolinking { + const hybridObjectInitializer = createHybridObjectIntializer() + const msBuildExtension = createMsBuildExtension( + allFiles, + hybridObjectInitializer.length > 0 + ) + + return { + platform: 'windows', + sourceFiles: [msBuildExtension, ...hybridObjectInitializer], + } +} diff --git a/packages/nitrogen/src/autolinking/windows/createHybridObjectInitializer.ts b/packages/nitrogen/src/autolinking/windows/createHybridObjectInitializer.ts new file mode 100644 index 0000000000..699205f27c --- /dev/null +++ b/packages/nitrogen/src/autolinking/windows/createHybridObjectInitializer.ts @@ -0,0 +1,130 @@ +import { NitroConfig } from '../../config/NitroConfig.js' +import { createCppHybridObjectRegistration } from '../../syntax/c++/CppHybridObjectRegistration.js' +import { includeHeader } from '../../syntax/c++/includeNitroHeader.js' +import { createFileMetadataString } from '../../syntax/helpers.js' +import type { SourceFile, SourceImport } from '../../syntax/SourceFile.js' +import { indent } from '../../utils.js' + +type CppFile = Omit & { language: 'c++' } + +export function getWindowsAutolinkingClassName(): string { + return `${NitroConfig.current.getWindowsModuleName()}Autolinking` +} + +export function createHybridObjectIntializer(): [CppFile, CppFile] | [] { + const autolinkingClassName = getWindowsAutolinkingClassName() + const moduleName = NitroConfig.current.getWindowsModuleName() + + const autolinkedHybridObjects = + NitroConfig.current.getAutolinkedHybridObjects() + + const cppRegistrations: string[] = [] + const cppImports: SourceImport[] = [] + for (const hybridObjectName of Object.keys(autolinkedHybridObjects)) { + const implementation = + NitroConfig.current.getWindowsAutolinkedImplementation(hybridObjectName) + if (implementation == null) { + continue + } + + if (implementation.language !== 'c++') { + throw new Error( + `The HybridObject "${hybridObjectName}" cannot be autolinked on Windows - Language "${implementation.language}" is not supported on Windows! ` + + `Only "c++" is supported on Windows.` + ) + } + + const { cppCode, requiredImports } = createCppHybridObjectRegistration({ + hybridObjectName: hybridObjectName, + cppClassName: implementation.implementationClassName, + }) + cppImports.push(...requiredImports) + cppRegistrations.push(cppCode) + } + + if (cppRegistrations.length === 0) { + // Nothing to autolink! + return [] + } + + const imports = cppImports.map((i) => includeHeader(i, true)).join('\n') + + const headerCode = ` +${createFileMetadataString(`${autolinkingClassName}.hpp`)} + +#pragma once + +#include + +namespace winrt::${moduleName} { + +/** + * Creates the \`IReactPackageProvider\` that registers all of ${moduleName}'s Hybrid Objects. + * + * Add this to your \`react-native.config.js\` so react-native-windows' autolinking + * calls it for you: + * \`\`\`js + * cppHeaders: ['${autolinkingClassName}.hpp'], + * cppPackageProviders: ['${moduleName}::ReactPackageProvider'], + * \`\`\` + */ +winrt::Microsoft::ReactNative::IReactPackageProvider ReactPackageProvider(); + +} // namespace winrt::${moduleName} + `.trim() + + const cppCode = ` +${createFileMetadataString(`${autolinkingClassName}.cpp`)} + +#include "${autolinkingClassName}.hpp" + +#include "HybridObjectRegistry.hpp" + +${imports} + +#include +#include +#include + +namespace winrt::${moduleName} { + +namespace { + +struct ${autolinkingClassName} : winrt::implements<${autolinkingClassName}, winrt::Microsoft::ReactNative::IReactPackageProvider> { + void CreatePackage(winrt::Microsoft::ReactNative::IReactPackageBuilder const& /* packageBuilder */) noexcept { + static std::once_flag registerOnce; + std::call_once(registerOnce, []() { + using namespace margelo::nitro; + using namespace ${NitroConfig.current.getCxxNamespace('c++')}; + + ${indent(cppRegistrations.join('\n'), ' ')} + }); + } +}; + +} // namespace + +winrt::Microsoft::ReactNative::IReactPackageProvider ReactPackageProvider() { + return winrt::make<${autolinkingClassName}>(); +} + +} // namespace winrt::${moduleName} + `.trim() + + return [ + { + content: headerCode, + language: 'c++', + name: `${autolinkingClassName}.hpp`, + platform: 'windows', + subdirectory: [], + }, + { + content: cppCode, + language: 'c++', + name: `${autolinkingClassName}.cpp`, + platform: 'windows', + subdirectory: [], + }, + ] +} diff --git a/packages/nitrogen/src/autolinking/windows/createMsBuildExtension.ts b/packages/nitrogen/src/autolinking/windows/createMsBuildExtension.ts new file mode 100644 index 0000000000..b602152f6f --- /dev/null +++ b/packages/nitrogen/src/autolinking/windows/createMsBuildExtension.ts @@ -0,0 +1,101 @@ +import { NitroConfig } from '../../config/NitroConfig.js' +import { + createFileMetadataString, + isCppFile, + isNotDuplicate, +} from '../../syntax/helpers.js' +import type { SourceFile } from '../../syntax/SourceFile.js' +import { indent } from '../../utils.js' +import { getWindowsAutolinkingClassName } from './createHybridObjectInitializer.js' + +export interface MsBuildFile extends Omit { + language: 'msbuild' +} + +function toWindowsPath(path: string): string { + return path.replace(/\//g, '\\') +} + +function generatedPath(...segments: string[]): string { + return '$(NitrogenGeneratedDir)' + toWindowsPath(segments.join('/')) +} + +function getGeneratedPath(file: SourceFile): string { + return generatedPath( + file.platform, + file.language, + ...file.subdirectory, + file.name + ) +} + +export function createMsBuildExtension( + files: SourceFile[], + hasAutolinkedHybridObjects: boolean +): MsBuildFile { + const name = NitroConfig.current.getWindowsModuleName() + const autolinkingClassName = getWindowsAutolinkingClassName() + + const sourceFiles = files + .filter((f) => f.platform === 'shared' && isCppFile(f)) + .map((f) => getGeneratedPath(f)) + .filter(isNotDuplicate) + + if (hasAutolinkedHybridObjects) { + sourceFiles.push(generatedPath('windows', `${autolinkingClassName}.cpp`)) + } + + const clCompiles = sourceFiles + .map((f) => ``) + .join('\n') + + const code = ` + + + + + $(MSBuildThisFileDirectory)..\\ + + + $(NitrogenGeneratedDir)shared\\c++; + $(NitrogenGeneratedDir)windows; + $(PublicIncludeDirectories) + + + + + + $(NitrogenGeneratedDir)shared\\c++;$(NitrogenGeneratedDir)windows;%(AdditionalIncludeDirectories) + + stdcpp20 + %(AdditionalOptions) /bigobj + Sync + + + + + + ${indent(clCompiles, ' ')} + + + `.trim() + + return { + content: code, + language: 'msbuild', + name: `${name}+autolinking.props`, + platform: 'windows', + subdirectory: [], + } +} diff --git a/packages/nitrogen/src/config/NitroConfig.ts b/packages/nitrogen/src/config/NitroConfig.ts index 658d2eee2e..bef3c2f80a 100644 --- a/packages/nitrogen/src/config/NitroConfig.ts +++ b/packages/nitrogen/src/config/NitroConfig.ts @@ -4,6 +4,7 @@ import type { AutolinkingAndroidImplementation, AutolinkingIOSImplementation, AutolinkingPlatformImplementation, + AutolinkingWindowsImplementation, NitroUserConfig, } from './NitroUserConfig.js' @@ -50,6 +51,18 @@ export class NitroConfig { return this.config.ios.iosModuleName } + /** + * Returns the Windows module name of the module that will be generated. + * This is the `winrt::` namespace of the generated autolinking `ReactPackageProvider`. + * Falls back to the iOS module name if `windows` is not configured. + * @example `NitroTest` + */ + getWindowsModuleName(): string { + return ( + this.config.windows?.windowsModuleName ?? this.config.ios.iosModuleName + ) + } + /** * Represents the C++ namespace of the module that will be generated. * This can have multiple sub-namespaces, and is always relative to `margelo::nitro`. @@ -157,6 +170,19 @@ export class NitroConfig { return objectConfig.android } + getWindowsAutolinkedImplementation( + hybridObjectName: string + ): AutolinkingWindowsImplementation | undefined { + const objectConfig = this.config.autolinking[hybridObjectName] + if (objectConfig == null) { + return undefined + } + if (objectConfig.all != null) { + return objectConfig.all + } + return objectConfig.windows + } + /** * Get the paths that will be ignored when loading the TypeScript project. * In most cases, this just contains `node_modules/`. diff --git a/packages/nitrogen/src/config/NitroUserConfig.ts b/packages/nitrogen/src/config/NitroUserConfig.ts index a82b8e74c1..b46bfcea1d 100644 --- a/packages/nitrogen/src/config/NitroUserConfig.ts +++ b/packages/nitrogen/src/config/NitroUserConfig.ts @@ -26,6 +26,11 @@ const autolinkingAndroidImplementationSchema = z.object({ implementationClassName: z.string(), }) +const autolinkingWindowsImplementationSchema = z.object({ + language: z.literal('c++'), + implementationClassName: z.string(), +}) + const autolinkingPlatformImplementationSchema = z.object({ language: autolinkingLanguageSchema, implementationClassName: z.string(), @@ -36,6 +41,7 @@ const autolinkingModernHybridObjectSchema = z all: autolinkingAllImplementationSchema.optional(), ios: autolinkingIOSImplementationSchema.optional(), android: autolinkingAndroidImplementationSchema.optional(), + windows: autolinkingWindowsImplementationSchema.optional(), }) .catchall(autolinkingPlatformImplementationSchema) .superRefine((value, ctx) => { @@ -133,6 +139,9 @@ export type AutolinkingIOSImplementation = z.infer< export type AutolinkingAndroidImplementation = z.infer< typeof autolinkingAndroidImplementationSchema > +export type AutolinkingWindowsImplementation = z.infer< + typeof autolinkingWindowsImplementationSchema +> export type AutolinkingPlatformImplementation = z.infer< typeof autolinkingPlatformImplementationSchema > @@ -201,6 +210,28 @@ export const NitroUserConfigSchema = z.object({ .regex(safeNamePattern) .refine(isNotReservedKeyword, isReservedKeywordError), }), + /** + * Windows specific options. + * + * This is optional - if it is omitted, {@linkcode ios.iosModuleName} is used as the + * Windows module name as well. + */ + windows: z + .object({ + /** + * Represents the Windows module name of the module that will be generated. + * + * This is the `winrt::` namespace nitrogen puts the generated autolinking + * `ReactPackageProvider` in, and the name of the generated MSBuild `.props` file. + * It should match the `` of your `.vcxproj`. + * @example `NitroTest` + */ + windowsModuleName: z + .string() + .regex(safeNamePattern) + .refine(isNotReservedKeyword, isReservedKeywordError), + }) + .optional(), /** * Configures the code that gets generated for autolinking (registering) * Hybrid Object constructors. diff --git a/packages/nitrogen/src/getPlatformSpecs.ts b/packages/nitrogen/src/getPlatformSpecs.ts index db78f28032..9ba41e6b5a 100644 --- a/packages/nitrogen/src/getPlatformSpecs.ts +++ b/packages/nitrogen/src/getPlatformSpecs.ts @@ -9,6 +9,7 @@ export type Language = Required[keyof PlatformSpec] const platformLanguages: { [K in Platform]: Language[] } = { ios: ['swift', 'c++'], android: ['kotlin', 'c++'], + windows: ['c++'], } const allPlatforms = Object.keys(platformLanguages) as Platform[] const allLanguages = Object.values(platformLanguages).flatMap((l) => l) diff --git a/packages/nitrogen/src/nitrogen.ts b/packages/nitrogen/src/nitrogen.ts index 3fe66d57a6..5707ac99f0 100644 --- a/packages/nitrogen/src/nitrogen.ts +++ b/packages/nitrogen/src/nitrogen.ts @@ -22,6 +22,7 @@ import { Logger } from './Logger.js' import { NitroConfig } from './config/NitroConfig.js' import { createIOSAutolinking } from './autolinking/createIOSAutolinking.js' import { createAndroidAutolinking } from './autolinking/createAndroidAutolinking.js' +import { createWindowsAutolinking } from './autolinking/createWindowsAutolinking.js' import type { Autolinking } from './autolinking/Autolinking.js' import { createGitAttributes } from './createGitAttributes.js' import type { PlatformSpec } from 'react-native-nitro-modules' @@ -203,6 +204,9 @@ export async function runNitrogen({ if (usedPlatforms.includes('android')) { autolinkingFiles.push(createAndroidAutolinking(writtenFiles)) } + if (usedPlatforms.includes('windows')) { + autolinkingFiles.push(createWindowsAutolinking(writtenFiles)) + } for (const autolinking of autolinkingFiles) { Logger.info( diff --git a/packages/nitrogen/src/syntax/SourceFile.ts b/packages/nitrogen/src/syntax/SourceFile.ts index 3ce072ff24..339118347d 100644 --- a/packages/nitrogen/src/syntax/SourceFile.ts +++ b/packages/nitrogen/src/syntax/SourceFile.ts @@ -72,7 +72,7 @@ export interface SourceImport { type GroupedFiles = Record export function groupByPlatform(files: SourceFile[]): GroupedFiles { - const result: GroupedFiles = { shared: [], ios: [], android: [] } + const result: GroupedFiles = { shared: [], ios: [], android: [], windows: [] } for (const file of files) { result[file.platform].push(file) } From e8d755c1d41b4c391e2b319e37aad48225d3f71e Mon Sep 17 00:00:00 2001 From: Ritesh Shukla Date: Sat, 15 Aug 2026 19:34:49 +0530 Subject: [PATCH 4/5] ci: build NitroModules.vcxproj on Windows Builds the Windows static library in Debug and Release on `windows-2022`, against react-native-windows 0.84.0 / react-native 0.84.1 installed into a throwaway consumer package. NitroModules.vcxproj resolves react-native-windows by walking up to the nearest node_modules and needs jsi + ReactCommon headers to compile against; the monorepo cannot provide them, because its react-native is newer than any react-native-windows release. Debug builds with `/warnaserror:C4715,C4700` so uninitialised variables and missing returns fail the build rather than accumulating. Co-Authored-By: Claude Opus 5 --- .github/workflows/build-windows.yml | 79 +++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 .github/workflows/build-windows.yml diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml new file mode 100644 index 0000000000..2f32679ade --- /dev/null +++ b/.github/workflows/build-windows.yml @@ -0,0 +1,79 @@ +name: Build Windows + +on: + push: + branches: + - main + paths: + - '.github/workflows/build-windows.yml' + - 'packages/react-native-nitro-modules/cpp/**' + - 'packages/react-native-nitro-modules/windows/**' + - '**/react-native.config.js' + pull_request: + paths: + - '.github/workflows/build-windows.yml' + - 'packages/react-native-nitro-modules/cpp/**' + - 'packages/react-native-nitro-modules/windows/**' + - '**/react-native.config.js' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + name: Build NitroModules.vcxproj + runs-on: windows-2022 + steps: + - uses: actions/checkout@v5 + + - uses: actions/setup-node@v6 + with: + node-version: 20 + + - name: Add MSBuild to PATH + uses: microsoft/setup-msbuild@v2 + + - name: Set up a react-native-windows consumer + shell: pwsh + run: | + New-Item -ItemType Directory -Force -Path consumer | Out-Null + Set-Location consumer + npm init -y | Out-Null + npm install --no-audit --no-fund ` + react-native@${{ env.RN_VERSION }} ` + react-native-windows@${{ env.RNW_VERSION }} + env: + RN_VERSION: 0.84.1 + RNW_VERSION: 0.84.0 + + - name: Restore NuGet packages + shell: pwsh + run: | + msbuild packages/react-native-nitro-modules/windows/NitroModules/NitroModules.vcxproj ` + /t:Restore ` + /p:Configuration=Debug ` + /p:Platform=x64 ` + /p:RnwNewArch=true ` + /p:ReactNativeWindowsDir=$(Resolve-Path consumer/node_modules/react-native-windows)\ + + - name: Build (Debug|x64) + shell: pwsh + run: | + msbuild packages/react-native-nitro-modules/windows/NitroModules/NitroModules.vcxproj ` + /p:Configuration=Debug ` + /p:Platform=x64 ` + /p:RnwNewArch=true ` + /p:ReactNativeWindowsDir=$(Resolve-Path consumer/node_modules/react-native-windows)\ ` + /verbosity:minimal ` + /warnaserror:C4715,C4700 + + - name: Build (Release|x64) + shell: pwsh + run: | + msbuild packages/react-native-nitro-modules/windows/NitroModules/NitroModules.vcxproj ` + /p:Configuration=Release ` + /p:Platform=x64 ` + /p:RnwNewArch=true ` + /p:ReactNativeWindowsDir=$(Resolve-Path consumer/node_modules/react-native-windows)\ ` + /verbosity:minimal From 22b44979adb43840bfba165eaeccf636e5df880f Mon Sep 17 00:00:00 2001 From: Ritesh Shukla Date: Sat, 15 Aug 2026 19:34:49 +0530 Subject: [PATCH 5/5] docs: document Windows support Adds a Windows guide covering installation, autolinking, the two app-side MSBuild settings an app needs (`CppWinRTGenerateWindowsMetadata` and `LanguageStandard`), how Nitro installs itself, and why it links statically. Also records what is not usable yet. Beyond Hybrid Views and worklets, two types are blocked on react-native-windows itself: `JsiAbiRuntime` crashes rather than throwing when a `bigint` is passed from JS into native, and when an `ArrayBuffer` created natively is returned to JS. Both work in the opposite direction. `setExternalMemoryPressure` is an empty body there, so GC pressure hints are ignored. Co-Authored-By: Claude Opus 5 --- .../getting-started/minimum-requirements.md | 7 +- docs/docs/guides/windows.md | 166 ++++++++++++++++++ docs/sidebars.ts | 1 + 3 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 docs/docs/guides/windows.md diff --git a/docs/docs/getting-started/minimum-requirements.md b/docs/docs/getting-started/minimum-requirements.md index 30135f494b..1cad13ff36 100644 --- a/docs/docs/getting-started/minimum-requirements.md +++ b/docs/docs/getting-started/minimum-requirements.md @@ -1,5 +1,5 @@ --- -description: Check the React Native, iOS, Android, Swift, Kotlin, Gradle, and C++ requirements needed to use Nitro Modules. +description: Check the React Native, iOS, Android, Windows, Swift, Kotlin, Gradle, and C++ requirements needed to use Nitro Modules. --- import Tabs from '@theme/Tabs'; @@ -21,4 +21,9 @@ To use Nitro, make sure your app meets the minimum requirements: - `compileSdkVersion` 34 or higher - `ndkVersion` 27 or higher + + - react-native-windows 0.78 or higher (`ReactContext::CallInvoker()` was added in 0.78) + - Visual Studio 2022 with the "Desktop development with C++" workload (toolset v143) + - Hybrid Objects must be implemented in C++ - see [Windows](../guides/windows.md) + diff --git a/docs/docs/guides/windows.md b/docs/docs/guides/windows.md new file mode 100644 index 0000000000..f95f844e7f --- /dev/null +++ b/docs/docs/guides/windows.md @@ -0,0 +1,166 @@ +--- +sidebar_position: 9 +--- + +# Windows + +Nitro runs on [react-native-windows](https://microsoft.github.io/react-native-windows/) (RNW). +Hybrid Objects are written in **C++** there - Windows has no equivalent of the Swift and +Kotlin bridges, so `HybridObject<{ windows: 'c++' }>` is the only supported form: + +```ts +export interface Math extends HybridObject<{ ios: 'c++', android: 'c++', windows: 'c++' }> { + add(a: number, b: number): number +} +``` + +The C++ specs nitrogen generates are shared with iOS and Android, so an existing +C++ Hybrid Object usually needs nothing more than adding `windows: 'c++'` to its +platform spec. + +## Installing + +```sh +npm i react-native-nitro-modules +npx @react-native-community/cli autolink-windows +``` + +Autolinking adds `NitroModules.vcxproj` to your app's solution, references it from +your app project, and appends Nitro's package provider to +`windows//AutolinkedNativeModules.g.cpp`. + +Two settings your app project needs, both because Nitro is a C++20 static library: + +```xml + + + + false + + + + + + stdcpp20 + + +``` + +## Adding Windows to a Nitro Module + +Point your module's `.vcxproj` at the property sheet nitrogen generates, and give +react-native-windows the package provider it generates: + +```xml + + +``` + +```js +// react-native.config.js +module.exports = { + dependency: { + platforms: { + windows: { + sourceDir: 'windows', + projects: [ + { + projectFile: 'MyModule\\MyModule.vcxproj', + directDependency: true, + cppHeaders: ['MyModuleAutolinking.hpp'], + cppPackageProviders: ['MyModule::ReactPackageProvider'], + }, + ], + }, + }, + }, +} +``` + +Your `.vcxproj` also needs a `ProjectReference` to `NitroModules.vcxproj`, which is +what puts Nitro's headers on your include path: + +```xml + + + {c6e3e6e4-3d5f-4c0b-9a5e-5d2c1b9f7a10} + + +``` + +The `winrt::` namespace of the generated `ReactPackageProvider` comes from +`windows.windowsModuleName` in [`nitro.json`](../getting-started/configuration-nitro-json.md), +and falls back to `ios.iosModuleName` when it isn't set. + +## How Nitro installs itself + +Nitro is installed from a two-argument `REACT_INIT` on the `NitroModules` module: + +```cpp +void Initialize(ReactContext const& context, facebook::jsi::Runtime& runtime) noexcept; +``` + +react-native-windows dispatches this through `AddJsiInitializer` on the JS thread, +which is the only JSI entry-point that still works once the New Architecture +(`RnwNewArch=true`) is enabled - `ExecuteJsi`, `TryGetOrCreateContextRuntime` and +`ReactContext::JSRuntime` are deprecated there and either fail-fast in Debug or +silently do nothing in Release. Nitro's `Dispatcher` is backed by +`ReactContext::CallInvoker()`, so callbacks and Promises land on the JS thread. + +## Things to know + +**Nitro links statically.** `NitroModules.vcxproj` is a `StaticLibrary`, not the DLL +that RNW modules normally are. RNW hands every module image its own `JsiAbiRuntime` +wrapper around the JS runtime, so Nitro in a separate DLL would see a different +`jsi::Runtime*` than the Hybrid Objects linked into the app - and Nitro's `JSICache`, +`PropNameIDCache` and `Dispatcher` are all keyed on the runtime, while +`HybridObjectRegistry` has to be a single registry. Building into the app binary is +what keeps them unique, so Nitro Modules should be static libraries too. + +**JSI calls cross an ABI boundary.** On Windows a native module never gets the raw +Hermes runtime; every `jsi::` call goes through `JsiAbiRuntime`, a WinRT proxy inside +`Microsoft.ReactNative.dll`. Nitro is still statically compiled and type-safe, but +individual JSI operations cost more than they do on iOS and Android. Prefer batching +work into fewer, larger calls. + +**`Dispatcher::runSync` is unavailable on the UI thread.** `IReactDispatcher` only +exposes `Post`, so `UIThreadDispatcher::runSync()` throws - the same as on iOS. + +## Not yet supported + +- **Hybrid Views** - `HybridView` has no Windows implementation yet. +- **`react-native-worklets`** - worklet contexts are not wired up on Windows. + +### Blocked on react-native-windows + +Two types are unusable in one direction because `JsiAbiRuntime` does not implement +them. Both terminate the process rather than throwing, so avoid them in Windows +Hybrid Objects until react-native-windows fixes them upstream. + +| | JS → native | native → JS | +|---|---|---| +| `bigint` (`int64_t`, `uint64_t`) | ❌ crashes | ✅ works | +| `ArrayBuffer` | ✅ works | ❌ crashes | + +- **`bigint` arguments.** `JsiAbiRuntime::ValueRef::InitValueRef` allocates a + `PointerValue` for `Symbol`, `String` and `Object`, but `JsiValueKind::BigInt` + falls through to its `default:` branch and copies the raw ABI handle into the + `jsi::Value`'s pointer field. The first dereference - `asBigInt()` - is then an + access violation. Returning a `bigint` to JS takes a different path + (`JsiAbiRuntime::MakeValue`) that handles it correctly. +- **Returning an `ArrayBuffer`.** `JsiAbiRuntime::createArrayBuffer` is an + unimplemented stub that calls `VerifyElseCrash(false)`. Reading and mutating a + buffer that JS allocated works, because `data()` and `size()` are implemented. + +`JsiAbiRuntime::setExternalMemoryPressure` is also an empty body, so the GC +pressure a Hybrid Object reports through `getExternalMemorySize()` is ignored on +Windows. diff --git a/docs/sidebars.ts b/docs/sidebars.ts index 6d70fd8db1..04f081d0bb 100644 --- a/docs/sidebars.ts +++ b/docs/sidebars.ts @@ -43,6 +43,7 @@ const sidebars: SidebarsConfig = { 'guides/entry-point', 'guides/sync-vs-async', 'guides/view-components', + 'guides/windows', 'guides/troubleshooting', 'guides/running-example-app', ]