Skip to content

Add TurboModule and CodegenNativeComponent bindings for React Native New Architecture #840

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

ricku44
Copy link

@ricku44 ricku44 commented Aug 16, 2025

Changes Made

  • src/apis/TurboModule.res: Core TurboModule type definitions with proper inheritance enforcement
  • src/apis/CodegenNativeComponent.res: Bindings for native component code generation

Testing

  • All code compiles successfully

Copy link
Member

@Freddy03h Freddy03h left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO, this should be enough:

module CodegenNativeComponent = {
  type options = {
    interfaceOnly?: bool,
    paperComponentName?: string,
    paperComponentNameDeprecated?: string,
    excludedPlatforms?: array<[#iOS | #android]>,
  }

  type nativeComponentType<'props> = React.component<'props>

  @module("react-native")
  external codegenNativeComponent: (
    string,
    ~options: options=?,
  ) => nativeComponentType<'props> = "codegenNativeComponent"
}

Codegen Rescript Playground

module TurboModuleRegistry = {
  @module("react-native") @scope("TurboModuleRegistry")
  external get: string => nullable<'t> = "get"

  @module("react-native") @scope("TurboModuleRegistry")
  external getEnforcing: string => 't = "getEnforcing"
}

TurboModuleRegistry Rescript Playground

@ricku44
Copy link
Author

ricku44 commented Aug 17, 2025

hi @Freddy03h,

Thanks for reviewing the PR. Let me give you more insights.

This PR introduces ReScript support for React Native's codegen system. Currently, React Native codegen works by parsing TypeScript or Flow files using respective AST parsers (like react-native-codegen/src/parsers/flow) and converting them to a standardized Flow AST representation for native code generation. The system has strict prerequisites including TurboModule extension, proper module registration patterns, and HostComponent checks that must be satisfied for successful codegen.

I've implemented a Flow-like AST parser for ReScript that translates ReScript syntax into the same Flow AST format that the existing codegen pipeline expects. This allows developers to write native module and component specifications in ReScript while maintaining full compatibility with the existing codegen infrastructure.

Could you please review the implementation and suggest any improvements? I'm particularly interested in feedback on the best Rescript syntax for this for AST transformation logic and whether the ReScript-to-Flow AST mapping aligns well with the existing codegen expectations.

NativeTurboModule.res

open ReactNative
open TurboModule

type spec = {
  ...turboModule,
  initialise: ((option<string>, option<string>) => unit) => unit,
  getSession: (
    Js.Dict.t<string>,
    Js.Dict.t<string>,
    array<Js.Json.t>,
    (option<string>, option<string>) => unit,
  ) => unit,
  exit: string => unit,
}

let hyperHeadlessModule: get<spec> = get("HyperHeadlessModule")

NativeButtonComponent.res

open ReactNative
open CodegenNativeComponent

module NativeButton = {
  type props = {
    ...View.viewProps,
    title: string,
    color?: string,
  }

  let make: nativeComponentType<props> = codegenNativeComponent("NativeButton", ~options=makeOptions(~interfaceOnly=true, ()))
}

@Freddy03h
Copy link
Member

hi @ricku44
Can you tell me what's missing from the playground examples I provided?

@ricku44
Copy link
Author

ricku44 commented Aug 18, 2025

Shared code works well, I'm wondering how should we add ReScript-specific validations that React Native enforces.

Codegen supports TypeScript and Flow primarily and not JS maybe for type enforcement at the JavaScript-native boundary, preventing runtime crashes and ensuring performance.

NativeModule and NativeComponent registration patterns involves getConstants and ViewProps enforcement.

Works ✅

import type {TurboModule} from 'react-native';
import {TurboModuleRegistry} from 'react-native';

export interface Spec extends TurboModule {}
export default TurboModuleRegistry.getEnforcing<Spec>('NativeLocalStorage');

Fails ❌

import type {TurboModule} from 'react-native';
import {TurboModuleRegistry} from 'react-native';

export interface Spec {}
export default TurboModuleRegistry.getEnforcing<Spec>('NativeLocalStorage');

Fails ❌

import type {TurboModule} from 'react-native';
import {TurboModuleRegistry} from 'react-native';

export interface Spec extends TurboModule {}
export default TurboModuleRegistry.getEnforcing('NativeLocalStorage');

Fails ❌

import {TurboModuleRegistry} from 'react-native';

export interface Spec {
   getConstants(): {};
}
export default TurboModuleRegistry.getEnforcing('NativeLocalStorage');

Works ✅

import type {ViewProps} from 'react-native';
import {codegenNativeComponent} from 'react-native';

export interface NativeProps extends ViewProps {
   sourceURL?: string;
}
export default codegenNativeComponent<NativeProps>('CustomWebView');

Fails ❌

import {codegenNativeComponent} from 'react-native';

export interface NativeProps {
   sourceURL?: string;
}
export default codegenNativeComponent<NativeProps>('CustomWebView');

Fails ❌

import type {ViewProps} from 'react-native';
import {codegenNativeComponent} from 'react-native';

export interface NativeProps extends ViewProps {
   sourceURL?: string;
}
export default codegenNativeComponent('CustomWebView');

@Freddy03h
Copy link
Member

Freddy03h commented Aug 19, 2025

I think this is more than just bindings.
It should be on an other project in the organization (maybe rescript-react-native-codegen ?).

I can't help you with the AST Parser or even if you want to use genType (I never used it).

The only things I can't tell about your bindings is that you should prefer nullable instead of option for values that cames from JS https://rescript-lang.org/docs/manual/v11.0.0/interop-cheatsheet#nullable in NativeTurboModule.res
And in NativeButtonComponent.res, you should provide directly the record instead of using non-necessary makeOptions func.

@ricku44
Copy link
Author

ricku44 commented Aug 19, 2025

Should we make the flow consistent in NativeModule.res as well?

@ricku44
Copy link
Author

ricku44 commented Aug 22, 2025

@Freddy03h PR is ready for review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants