Skip to content
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

Support for federated community modules #615

Open
2 tasks
acoates-ms opened this issue Mar 14, 2023 · 2 comments
Open
2 tasks

Support for federated community modules #615

acoates-ms opened this issue Mar 14, 2023 · 2 comments

Comments

@acoates-ms
Copy link
Collaborator

acoates-ms commented Mar 14, 2023

Introduction

I’ve been looking at what would be required to enable “federated” community modules, similar to how flutter plugins work: https://docs.flutter.dev/development/packages-and-plugins/developing-packages#federated-plugins. The basic idea is that while there could be a main package that provides the interface, and one or more platform implementations. Additional platforms could implement the interface outside of the main community modules package.

Details

As the number of react-native platforms has increased, it has put increasing strain on community modules to support more platforms, which leads to trade-offs on either not supporting many of the newer react-native platforms, such as macOS and Windows to keep their package and workflows simple. Or greatly complicating the community module by making its maintainers have to support many more platforms, some of which the core maintainers of that module may not have expertise, or even be able to build locally.

Federated packages is a way to solve this problem. Instead of forcing a single package to implement all the platforms, the base community package (Ex: react-native-foobar) would provide the shared JS - which uses a NativeComponent spec, or TurboModule spec file. The base package could also include implementations for one or more of the platforms depending on the maintainers wishes. Additional platforms could then create platform specific implementations of that package, Ex react-native-foobar-windows. The react-native-foobar-windows package should be able to reuse the spec files inside react-native-foobar, to create a native implementation that is compatible with the JS code in react-native-foobar. Then the JS code can be written referencing only react-native-foobar, without forking having to fork for windows.

Running codegen to support federated modules

Currently codegen runs against a path specified in package.json under codegenConfig.jsSrcsDir, which provides a relative path to the location of the spec files. In order for react-native-foobar-windows to be able to use the spec files from react-native-foobar, we need a way to tell the codegen to use spec files from another package. We cannot just use a relative path, since different repo setups would install react-native-foobar at different relative paths from react-native-foobar-windows (Is it hoisted - how many levels up is it hoisted etc). This could be done by adding a new property in the codegenConfig say moduleInterfacePackage: "react-native-foobar" , then treating the jsSrcsDir as a relative path from that package instead of from react-native-foobar’s root.

More complex scenarios

Unfortunately when looking for good examples of packages that could follow this system, it appears that many modules do not use the native specs and JS for even iOS and android, let alone other platforms. Some of this may be due to a lack of rigor on the modules part. — maybe more modules could have a shared interface if they put some work into it. That would be the ideal solution for a Multiplatform story.

Some packages do not even support the same public JS interface per platform, there is little need to attempt some kind of module federation, since they are basically different modules at that point. (Note having a shared interface, where some properties are ignored on some platforms still counts as the same interface, and could be federated and shared across platforms).

But there are a few middle ground scenarios between having a shared native interface and being completely divergent. One is having a shared JS interface, but different internal JS and different native interfaces within the module. We can enforce that the packages have the same interface relatively easily if both packages are TypeScript. The remaining platform issue is having a good way to redirect metro to the correct package based on which platform is being compiled. There is already a custom metro resolver as part of the CLI https://github.com/react-native-community/cli/blob/main/packages/cli-plugin-metro/src/tools/metroPlatformResolver.ts, which redirects imports to react-native to different packages depending on the platform being bundled. This resolver and the react-native.config.js files could be expanded to allow packages to specify that they are platform specific overrides for specific packages and have he CLI automatically configure metro to do those redirects.

The final scenario is packages where there is a bunch of shared JS, and some forked JS, and forked native interfaces. Writing tooling and infra to support such a hybrid solution is likely messy, and overly complex. I suggest that rather than trying to solve for that case, packages with shared logic should split the shared logic into sub packages, such that the shared logic can be reused, and the forked parts can be replaced by federated modules.

Discussion points

Work items to support federated modules:

  • Ability to run codegen on specs from another package
  • Ability to declare that metro should redirect certain imports to specific platform overrides when building certain platforms.
@tido64
Copy link

tido64 commented Mar 15, 2023

What are your thoughts on:

  • Sharing infrastructure like build, testing, validation?
  • How do platform specific packages validate that they've implemented the API correctly?
  • You mentioned hoisting as a potential issue. I'm wondering if this can cause total chaos if you have multiple packages depending on different versions of a package. For instance, could a package pull in react-native-foobar-windows@1.0 accidentally pull in an incompatible react-native-foobar@2.0 from another package?

@cortinico
Copy link
Member

This could be done by adding a new property in the codegenConfig say moduleInterfacePackage: "react-native-foobar" , then treating the jsSrcsDir as a relative path from that package instead of from react-native-foobar’s root.

This is doable, and is actually partially involved by this other RFC:

Unfortunately when looking for good examples of packages that could follow this system, it appears that many modules do not use the native specs and JS for even iOS and android, let alone other platforms

This is a problem. My personal take is that, with the New Architecture adoption, folks will take a stance at migrating their packages to use unified specs which can be consumed by the codegen. Obviously that's not always possible, there are packages which offer platform specific capabilities that just don't make sense in the other platform (e.g. foldable APIs on Android which are missing on iOS).

I believe the whole 'federated module' ecosystem could be achieved by leveraging create-react-native-library and making sure the library that get created from taht template can get federated. Potentially having the android and ios integration follow the same mechanism would be preferable, to avoid having windows/macos as one-off.

As @tido64 also mentioned, there are a lot of implications on this. I guess we'd love to read and discuss a RFC is someone is willing to investigate what are the challenges and the implications of such a solution.

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

No branches or pull requests

3 participants