I am creating several mobile applications in react-native that share common components. I have difficulties handling the dependencies. Here is what I do, which is tedious, is there a better way?
- A repository "common-modules" has shared components
- Several repositories include the common one as a dependency like this:
Package.json
"dependencies": { "common-components": "file:../common-components" }, I use it like that in the different apps:
import XXX from 'common-components/src/...' Now this is great because all other dependencies are in "common-components", but as soon as one of them has native code, I am forced to link the library again in each app.
For instance, if I use "react-native-image-picker", I have to install it again in each application and link it in XCode, edit build.gradle etc. etc.
- It takes forever
- Are my linked dependencies bundled twice?
- I fear the day when I must change/upgrade one of them...
Is there a better way?
3 Answers
Answers 1
I've heard of projects that share code being managed in a monorepo. That may help managing shared code but won't solve the problem of linking native modules N times for N apps.
However, there is react-native link that should automate the process, and ease linking the native modules a lot. Note there is no need to re-link if you just upgrade a native dependency.
Alternatively, I think it should be possible to wrap multiple native modules into one. If you take a look at MainReactPackage.java in RN repo, it wraps several native modules. I imagine similar mechanism can be employed on iOS, with static libraries. Obviously, this means that it won't be easy to selectively include some modules and others not.
Answers 2
Like you said yourself it is easier to work with a duplicated codebase. To deal with that you can create your own package manager for your shared components. Make a script for each component which will add it's dependencies to package.json and configure gradle and XCode. Add a simple GUI to include your components with a single click.
This may seem like a lot of work upfront, but: - you will keep full control - once you have a script to install a component you will save time each time you use it on a new app - in the case of updates you can create a script to handle that as well
Answers 3
Requirements
This is a tutorial for advanced iOS developers. It requires practical Swift programming skills and good knowledge about iOS development. You need to know your way around the shell and about versioning software projects with Git. Please be aware that iOS is not officially supported by the Swift Package Manager and the described approach is experimental and for learning purposes only.
Motivation
The Swift Package manager shines when it comes to creating packages - it is easy and quick to create own frameworks. Perfect to create internal components as separate modules.
The integration with Xcode is very clean and lightweight: Like with Carthage, the tooling doesn’t touch your Xcode app project. The following approach uses the Swift package manager to generate an Xcode project to build the libraries. This project is manually embedded in the app project to build the dependencies:
The xcodeproj library from the Cocoapods project is used to modify the generated Dependencies project so it builds for iOS. This allows to customize the generated project to support anything the Swift Package Manager cannot do as of now.
Creating a HelloKit package
Run xcode-select to configure the Xcode you want to use (this tutorial has been tested with Xcode 9 and 10): sudo xcode-select -s /Applications/Xcode-beta.app/ Create a folder for this tutorial:
mkdir PackageTutorial && cd PackageTutorial/ Create a folder for the HelloKit library we are going to create. Initialize the Swift package structure for the project and remove the files that are for Linux support:
mkdir HelloKit && cd HelloKit swift package init rm Tests/LinuxMain.swift Tests/HelloKitTests/XCTestManifests.swift The Swift Package manager can generate Xcode projects (swift package generate-xcodeproj), but they are generated for macOS. The following Rakefile (rake is a make-like build tool for Ruby) calls this generator and modifies the generated project to use iOS instead.
Download the example Rakefile, install the required Ruby gem xcodeproj (if you don’t have it installed already) and have a look at the script:
sudo gem install xcodeproj curl https://raw.githubusercontent.com/ralfebert/HelloKit/64097ea/Rakefile -o Rakefile cat Rakefile Generate the Xcode project:
rake xcodeproj Open the generated project:
open HelloKit.xcodeproj Make the struct public in Sources/HelloKit/HelloKit.swift:
public struct HelloKit { public init() { } public var text = "Hello, World!" } Run the tests with ⌘U.
The Swift package manager uses git projects and uses tags for versioning. Create a git project, commit the changes and tag the version:
git init git add . git commit -m "HelloKit library" git tag v0.1.0 Create a repository on Github for the library and push the changes including tags to Github:
git remote add origin git@github.com:example/HelloKit.git git push --set-upstream origin master git push --tags Here is the example repository, the project should look like this: HelloKit library repository.
Creating the HelloApp iOS project
Create a new iOS Single View App project named HelloApp in Xcode in the PackageTutorial folder.
Create a folder Dependencies for all the package manager related files:
cd HelloApp/ mkdir Dependencies cd Dependencies Create a file Package.swift in the Dependencies folder. You can download an example here:
curl https://raw.githubusercontent.com/ralfebert/HelloApp/c77d6a4/Dependencies/Package.swift -o Package.swift Customize the dependencies properties with the Clone URL of your repository:
// swift-tools-version:4.0 import PackageDescription let package = Package( name: "Dependencies", products: [ .library(name: "Dependencies", type: .dynamic, targets: ["Dependencies"]), ], dependencies: [ .package(url: "https://github.com/ralfebert/HelloKit.git", from: "0.1.0"), ], targets: [ .target(name: "Dependencies", dependencies: ["HelloKit"], path: "." ) ] ) Let the package manager fetch the package:
swift package update This should say something like:
Resolving https://github.com/example/HelloKit.git at 0.1.0 This will download the dependency source projects into a folder .build.
Download the example Rakefile to the Dependencies folder and have a look at the script:
curl https://raw.githubusercontent.com/ralfebert/HelloApp/c77d6a4/Dependencies/Rakefile -o Rakefile cat Rakefile Generate the xcode project:
rake xcodeproj Open HelloApp.xcodeproj and drag the generated Dependencies.xcodeproj into the HelloApp.xcodeproj and add the resulting HelloKit.framework as an embedded binary for the app target:
Select the HelloApp scheme (the project has some other schemes that can be deleted) and build it.
Use the class from the library, f.e. in the AppDelegate:
import UIKit import HelloKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. print(HelloKit().text) return true } Run the project. (Example project on GitHub)
Updating the generated Xcode projects - Common Tasks
You can use the Rakefiles to update the generated Xcode projects. Here is example code for some common tasks:
Setting the iOS Deployment target:
project.build_configurations.each { |config| config.build_settings["IPHONEOS_DEPLOYMENT_TARGET"] = "10.0" } Adding resources to a target (this might be possible in the future with the Swift package manager: SwiftPM: Need a way to include resources with targets):
target = project.targets.select {|t|t.name=="SomeTarget"}.first group = project.main_group["Sources/SomeFolder"] file = group.new_file(File.expand_path("Sources/SomeFolder/cat.png")) target.add_resources([file]) Setting an environment variable for a shared scheme:
scheme = Xcodeproj::XCScheme.new("SomeProject.xcodeproj/xcshareddata/xcschemes/SomeSharedScheme.xcscheme") action = scheme.launch_action env = action.environment_variables env["FOO"] = "BAR" scheme.launch_action.environment_variables = env scheme.launch_action = action scheme.save! Adding a system framework as linked framework (for Objective C projects) - from this gist (has some other examples of xcodeproj usage):
def add_system_frameworks(target, names) build_phase = target.frameworks_build_phase framework_group = target.project.frameworks_group names.each do |name| path = "System/Library/Frameworks/#{name}.framework" file_ref = framework_group.new_reference(path) file_ref.name = "#{name}.framework" file_ref.source_tree = 'SDKROOT' build_file = build_phase.add_file_reference(file_ref) end end More information
Nick Teissler’s article [It Looks Like You Are Trying to Use a Framework][7] describes how frameworks/shared libraries work in Xcode. Credit for tutorial goes to Ralf Ebert can be found here
Hope this helps you.


0 comments:
Post a Comment