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

Chore: migrate repack native modules to turbo modules #344

Closed
wants to merge 31 commits into from
Closed
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
4b9a9df
chore: add and use spec for ScriptManager
teneeto Apr 17, 2023
e68eb4d
chore: setup codegen to generate stubs using NativeScriptManagerSpec
teneeto Apr 17, 2023
46e4f1c
chore: configure podspec to selectively use old and new arch
teneeto Apr 17, 2023
80a85c8
chore: enable newarch support for iOS and implement stubs
teneeto Apr 17, 2023
f74ed2a
chore: configure android build.gradle to support new and old arch
teneeto Apr 17, 2023
bae8035
chore: enable newarch support for android and implement module for ne…
teneeto Apr 17, 2023
f27ae60
chore: fix lint
teneeto Apr 17, 2023
742f9fb
chore: fix lint - delete whitespace
teneeto Apr 17, 2023
1e18f7d
chore: mock test using NativeScriptManagerSpec
teneeto Apr 20, 2023
391900f
chore: new arch guide
teneeto Apr 20, 2023
5f345dc
chore: review fixes
teneeto Apr 21, 2023
6c382d4
chore: fix code style - update shared implementation to use object
teneeto Apr 24, 2023
626c32c
Merge branch 'main' into chore/migrate-repack-to-turbo-modules
teneeto Apr 24, 2023
4d26acc
chore: remove js included path on repack tsconfig
teneeto Apr 25, 2023
4a09696
chore: add proper types to NativeScriptManager.ts
teneeto Apr 25, 2023
839de63
Merge branch 'main' into chore/migrate-repack-to-turbo-modules
teneeto Apr 25, 2023
ba282bf
chore: update yarn.lock
teneeto Apr 25, 2023
9700c52
Merge branch 'main' into chore/migrate-repack-to-turbo-modules
teneeto Apr 25, 2023
3740e24
Merge branch 'main' into chore/migrate-repack-to-turbo-modules
teneeto Apr 26, 2023
f36769a
update ScriptLocator types for Spec
teneeto May 19, 2023
ae164e4
revert to using Object as config types
teneeto May 19, 2023
8be4291
Merge branch 'main' into chore/migrate-repack-to-turbo-modules
teneeto May 19, 2023
ef926f4
Merge branch 'main' into chore/migrate-repack-to-turbo-modules
teneeto May 24, 2023
de5f952
Merge branch 'main' into chore/migrate-repack-to-turbo-modules
teneeto Jun 14, 2023
154be1f
Merge branch 'main' of github.com:callstack/repack into chore/migrate…
teneeto Jul 5, 2023
98287ae
Merge branch 'chore/migrate-repack-to-turbo-modules' of github.com:ca…
teneeto Jul 5, 2023
5c68783
Merge branch 'main' into chore/migrate-repack-to-turbo-modules
teneeto Jul 31, 2023
71199f8
Merge branch 'main' into chore/migrate-repack-to-turbo-modules
teneeto Aug 16, 2023
a585956
remove irrelevant readme section
teneeto Aug 16, 2023
31e119f
Merge branch 'main' into chore/migrate-repack-to-turbo-modules
teneeto Oct 12, 2023
5f77968
Merge branch 'main' into chore/migrate-repack-to-turbo-modules
teneeto Jan 8, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,28 @@ Available at: [Projects](https://github.com/callstack/repack/projects?type=class
- [ ] Code signing and verification
- [ ] `webpack-init` command

### New Architecture Support
----
This library supports new architecture! Using [turbo modules](https://reactnative.dev/docs/next/the-new-architecture/pillars-turbomodules) offers a new way of initializing native modules.

If you are using this library in your own project or [running the example](https://github.com/callstack/repack/blob/main/CONTRIBUTING.md#running-the-example), there are some extra steps needed.

### iOS
Install pods with this flag inside `ios` folder:
```sh
RCT_NEW_ARCH_ENABLED=1 bundle exec pod install
```
and then run:

```sh
yarn ios
```

### Android
Set `newArchEnabled` to `true` inside `android/gradle.properties` and then run:
```sh
yarn android
```

### Examples

Expand Down
18 changes: 18 additions & 0 deletions packages/repack/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,17 @@ buildscript {
}
}

def isNewArchitectureEnabled() {
return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true"
}

apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'

if (isNewArchitectureEnabled()) {
apply plugin: 'com.facebook.react'
}

def getExtOrDefault(name) {
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['RePack_' + name]
}
Expand All @@ -33,7 +41,17 @@ android {
targetSdkVersion getExtOrIntegerDefault('targetSdkVersion')
versionCode 1
versionName "1.0"
buildConfigField("boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString())
}

sourceSets {
main {
if (isNewArchitectureEnabled()) {
java.srcDirs += ['src/newarch']
} else {
java.srcDirs += ['src/oldarch']
}
}
}

buildTypes {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package com.callstack.repack

import android.os.Handler
import com.facebook.react.bridge.*

object ScriptManagerModuleImpl {

const val NAME = "ScriptManager"

private fun runInBackground(fn: () -> Unit) {
val handler = Handler()
val runnable = Runnable {
fn()
}
handler.postDelayed(runnable, 0)

}

fun loadScript(scriptId: String, configMap: ReadableMap, promise: Promise, remoteLoader: RemoteScriptLoader, fileSystemLoader: FileSystemScriptLoader) {
Copy link
Member

Choose a reason for hiding this comment

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

runInBackground {
val config = ScriptConfig.fromReadableMap(scriptId, configMap)

// Currently, `loadScript` supports either `RemoteScriptLoader` or `FileSystemScriptLoader`
// but not both at the same time - it will likely change in the future.
when {
config.url.protocol.startsWith("http") -> {
if (config.fetch) {
remoteLoader.load(config, promise)
} else {
remoteLoader.execute(config, promise)
}
}
config.url.protocol == "file" -> {
fileSystemLoader.load(config, promise)
}
else -> {
promise.reject(
ScriptLoadingError.UnsupportedScheme.code,
"Scheme in URL: '${config.url}' is not supported"
)
}
}
}
}


fun prefetchScript(scriptId: String, configMap: ReadableMap, promise: Promise, remoteLoader: RemoteScriptLoader) {
val config = ScriptConfig.fromReadableMap(scriptId, configMap)
if (!config.fetch) {
// Do nothing, script is already prefetched
promise.resolve(null)
} else {
runInBackground {
when {
config.url.protocol.startsWith("http") -> {
remoteLoader.prefetch(config, promise)
}
else -> {
promise.reject(
ScriptLoadingError.UnsupportedScheme.code,
"Scheme in URL: '${config.url}' is not supported"
)
}
}
}
}
}

fun invalidateScripts(scriptIds: ReadableArray, promise: Promise, remoteLoader: RemoteScriptLoader) {
runInBackground {
if (scriptIds.size() == 0) {
remoteLoader.invalidateAll()
promise.resolve(null)
} else {
try {
for (i in 0 until scriptIds.size()) {
val scriptId = scriptIds.getString(i)
remoteLoader.invalidate(scriptId)
}
promise.resolve(null)
} catch (error: Exception) {
promise.reject(
ScriptLoadingError.ScriptInvalidationFailure.code,
"Cannot invalidate some of the scripts"
)
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,30 @@
package com.callstack.repack

import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager
import com.facebook.react.bridge.*
import com.facebook.react.TurboReactPackage
import com.facebook.react.module.model.ReactModuleInfo
import com.facebook.react.module.model.ReactModuleInfoProvider

class ScriptManagerPackage : TurboReactPackage() {
override fun getModule(name: String?, reactContext: ReactApplicationContext): NativeModule? =
if (name == ScriptManagerModuleImpl.NAME) {
ScriptManagerModule(reactContext)
} else {
null
}

class ScriptManagerPackage : ReactPackage {
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
return listOf(ScriptManagerModule(reactContext))
}

override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
return emptyList()
override fun getReactModuleInfoProvider() = ReactModuleInfoProvider {
val isTurboModule: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
mapOf(
ScriptManagerModuleImpl.NAME to ReactModuleInfo(
ScriptManagerModuleImpl.NAME, // name
ScriptManagerModuleImpl.NAME, // className
false, // canOverrideExistingModule
false, // needsEagerInit
Copy link
Member

Choose a reason for hiding this comment

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

I don't know what exactly needsEagerInit means, but I'd say Repack should be initialized & available as soon as possible, especially in the Module Federation architecture we're now treating as a recommended one.

Take a look at the Super App Showcase Host App – https://github.com/callstack/super-app-showcase/blob/main/packages/host/index.js ScriptManager is used before the App component is registered.

Of course, what I'm saying here might be completely wrong since I'm not that familiar with the New Arch – if that's the case, sorry for the confusion :)

Copy link
Author

Choose a reason for hiding this comment

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

On repack side, i'm not sure how eager it needs to be initialised (even though i feel it's still initialised and available), however this is the default spec from docs and i'm sure it can be set to Eagerly initialise.

true, // hasConstants
false, // isCxxModule
isTurboModule // isTurboModule
)
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.callstack.repack

import android.os.Handler
import com.facebook.react.bridge.*

class ScriptManagerModule(reactContext: ReactApplicationContext) : NativeScriptManagerSpec(reactContext) {
private val remoteLoader: RemoteScriptLoader = RemoteScriptLoader(reactApplicationContext)
private val fileSystemLoader: FileSystemScriptLoader = FileSystemScriptLoader(reactApplicationContext)

override fun getName(): String = ScriptManagerModuleImpl.NAME

override fun loadScript(scriptId: String, configMap: ReadableMap, promise: Promise) {
teneeto marked this conversation as resolved.
Show resolved Hide resolved
ScriptManagerModuleImpl.loadScript(scriptId, configMap, promise, remoteLoader, fileSystemLoader)
}

override fun prefetchScript(scriptId: String, configMap: ReadableMap, promise: Promise) {
ScriptManagerModuleImpl.prefetchScript(scriptId, configMap, promise, remoteLoader)
}

override fun invalidateScripts(scriptIds: ReadableArray, promise: Promise) {
ScriptManagerModuleImpl.invalidateScripts(scriptIds, promise, remoteLoader)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.callstack.repack

import android.os.Handler
import com.facebook.react.bridge.*

class ScriptManagerModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
private val remoteLoader: RemoteScriptLoader = RemoteScriptLoader(reactApplicationContext)
private val fileSystemLoader: FileSystemScriptLoader = FileSystemScriptLoader(reactApplicationContext)

override fun getName(): String = ScriptManagerModuleImpl.NAME

@ReactMethod
fun loadScript(scriptId: String, configMap: ReadableMap, promise: Promise) {
ScriptManagerModuleImpl.loadScript(scriptId, configMap, promise, remoteLoader, fileSystemLoader)
}

@ReactMethod
fun prefetchScript(scriptId: String, configMap: ReadableMap, promise: Promise) {
ScriptManagerModuleImpl.prefetchScript(scriptId, configMap, promise, remoteLoader)
}

@ReactMethod
fun invalidateScripts(scriptIds: ReadableArray, promise: Promise) {
ScriptManagerModuleImpl.invalidateScripts(scriptIds, promise, remoteLoader)
}
}
19 changes: 18 additions & 1 deletion packages/repack/callstack-repack.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require "json"

package = JSON.parse(File.read(File.join(__dir__, "package.json")))
folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32'

Pod::Spec.new do |s|
s.name = "callstack-repack"
Expand All @@ -20,4 +21,20 @@ Pod::Spec.new do |s|
s.dependency "React-Core"
s.dependency 'JWTDecode', '~> 3.0'
s.dependency 'SwiftyRSA'
end

# Don't install the dependencies when we run `pod install` in the old architecture.
if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then
s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1"
s.pod_target_xcconfig = {
"HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"",
"OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1",
"CLANG_CXX_LANGUAGE_STANDARD" => "c++17"
}
s.dependency "React-Codegen"
s.dependency "RCT-Folly"
s.dependency "RCTRequired"
s.dependency "RCTTypeSafety"
s.dependency "ReactCommon/turbomodule/core"
s.dependency "React-RCTFabric"
end
end
14 changes: 14 additions & 0 deletions packages/repack/ios/ScriptManager.h
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
// Use Native Module Spec when we run new architecure
#ifdef RCT_NEW_ARCH_ENABLED
#import <repack/repack.h>

NS_ASSUME_NONNULL_BEGIN

@interface ScriptManager : NSObject <NativeScriptManagerSpec>

@end

NS_ASSUME_NONNULL_END

#else
#ifndef ScriptManager_h
#define ScriptManager_h

Expand All @@ -8,3 +21,4 @@
@end

#endif /* ScriptManager_h */
#endif
Loading