LogoBirchdocs

Windows

Creating a simple JSI module for Windows.

Initialising the library

First, initialise a library for a native module as follows:

npx react-native init-windows --template cpp-lib --name MyModule

The cpp-lib template explicitly supports both Old Architecture and New Architecture.

This boilerplate will carry on from that step. By the end, we'll have a TurboModule that installs a JSI HostObject onto the global object upon initialisation.

Making our JSI module

① The native module

MyModule.ts

This file provides the TypeScript interface, and an installation command, for the native module.

import { type TurboModule, TurboModuleRegistry } from "react-native";

declare global {
  /** Will be defined once you've called install(). */
  let MyHostObject: MyHostObjectType | undefined;
}

interface MyHostObjectType {
  /** Returns a UUID. */
  makeUUID(): string;
}

/**
 * A convenience function for looking up MyModule and calling install() on it.
 *
 * Call this in your entrypoint file in order to populate global.MyHostObject.
 */
export function install() {
  const turboModule = TurboModuleRegistry.get<MyModule>("MyModule");
  if (!turboModule) {
    throw new Error(`Failed to find "MyModule" in TurboModuleRegistry.`);
  }
  turboModule.install();
}

interface MyModule extends TurboModule {
  install(): void;
}

ReactPackageProvider.{h,cpp}

These two C++ files implement the native module's package provider. We'll take the default ReactPackagerProvider.h and ReactPackagerProvider.cpp files created by react-native init-windows; no need to change them.

By default, this native module is set up to be a TurboModule, as ReactPackagerProvider.cpp passes true to the useTurboModules arg.

ReactNativeModule.h

This is the C++/WinRT header for the native module. We'll make just some small additions to the default ReactNativeModule.h file created by react-native init-windows. Upon initialisation, we'll make our native module set up an instance of MyHostObject on the global.

  #pragma once

+ #include <JSI/JsiApiContext.h>
  #include "NativeModules.h"
+ #include "MyHostObject.h"

  using namespace winrt::Microsoft::ReactNative;

  namespace winrt::MyModule
  {

  REACT_MODULE(ReactNativeModule, L"MyModule")
  struct ReactNativeModule
  {
    // See https://microsoft.github.io/react-native-windows/docs/native-modules
    // for details on writing native modules

    REACT_INIT(Initialize)
    void Initialize(ReactContext const &reactContext) noexcept
    {
      m_reactContext = reactContext;

+     ExecuteJsi(
+       reactContext,
+       [reactContext](jsi::Runtime &rt)
+       {
+         // On New Architecture, grab the callInvoker like this instead:
+         // auto callInvoker = ReactContext{reactContext}.CallInvoker();
+         auto callInvoker = winrt::Microsoft::ReactNative::MakeAbiCallInvoker(reactContext.JSDispatcher().Handle());
+
+         rt.global().setProperty(
+           rt,
+           "MyHostObject",
+           jsi::Object::createFromHostObject(rt, std::make_shared<MyHostObject>(callInvoker))
+         );
+       }
+     );
    }

   private:
    ReactContext m_reactContext{nullptr};
  };

  } // namespace winrt::MyModule

② The JSI Host Object

MyHostObject.h

This is the C++/WinRT implementation for the jsi::HostObject. It implements a makeUUID() function.

#pragma once

// TODO: Investigate which of these headers can be removed.
#include <JSI/JsiApiContext.h>
#include <ReactCommon/TurboModule.h>
#include <ReactCommon/TurboModuleUtils.h>
#include <Windows.h>
#include <jsi/jsi.h>
#include <winrt/Windows.Foundation.h>
#include <string>
#include "JSValue.h"
#include "NativeModules.h"

using namespace winrt::Microsoft::ReactNative;
using namespace winrt::Windows::Foundation;
using namespace facebook;

namespace winrt::MyModule
{
/// The library of platform-agnostic APIs.
class MyHostObject : public jsi::HostObject
{
 public:
  MyHostObject(std::shared_ptr<react::CallInvoker> jsInvoker) : jsi::HostObject()
  {
    m_jsInvoker = jsInvoker;
  }

 private:
  std::shared_ptr<react::CallInvoker> m_jsInvoker{nullptr};

 public:
  jsi::Value get(jsi::Runtime &rt, const jsi::PropNameID &propName) override
  {
    std::string name = propName.utf8(rt);

    if (name == "makeUUID")
    {
      return jsi::Function::createFromHostFunction(
        rt,
        jsi::PropNameID::forAscii(rt, name),
        0,
        [this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *, size_t) -> jsi::Value
        {
          winrt::guid newGuid = winrt::Windows::Foundation::GuidHelper::CreateNewGuid();
          std::string guidStr = winrt::to_string(winrt::to_hstring(newGuid));

          // Remove the surrounding braces.
          if (!guidStr.empty() && guidStr.front() == '{' && guidStr.back() == '}')
          {
            guidStr = guidStr.substr(1, guidStr.size() - 2);
          }

          return jsi::String::createFromAscii(rt, guidStr);
        }
      );
    }

    return jsi::Value::undefined();
  }

  void set(jsi::Runtime &, const jsi::PropNameID &, const jsi::Value &) override {}
};

} // namespace winrt::MyModule

Remember to add MyHostObject.h to the Visual Studio project. I usually do this by directly editing MyModule.vcxproj and MyModule.vcxproj.filters.