-
Notifications
You must be signed in to change notification settings - Fork 140
Flagship 12
Flagship Code™ is a configuration as code (CaC) toolkit focusing on native code generation for React Native, leveraging simplicity, extensibility, and type safety. The core foundation is based on React Native and TypeScript. To manage this core foundation, we created this toolkit.
CaC is the process of configuring services via machine-readable definition files. Our configuration files are typesafe JavaScript objects represented in TypeScript files.
Native code generation is everything from React Native essentials to third-party dependencies that require native code changes.
This toolkit can be broken into three models: Core, CLI, and Plugins.
The core SDK contains utility functions, executors, and a template. Utility functions are foundational functions that executors and more complex functions are built upon to manipulate or generate native code.
The CLI SDK is a command line interface that listens for options that conditionally run executors.
Executors are complex functions that are executed at different native-specific lifecycles.
Plugins are published or local native-specific scripts (e.g., iOS and/or Android) that manipulate or generate native code for a specific third-party SDK. These plugins are run generically based on a priority list captured in the package.json file.
At any time in the phase of your React Native project, you can opt-in to using Flagship Code™; opting in is as easy as creating a typesafe configuration file + required plugins and adding the iOS and Android directories to your .gitignore
.
If you are already using Flagship Code™ - at any time, you can easily opt out from continuing to use Flagship Code™; opting out from Flagship Code™ is as easy as removing your configuration folder and iOS and Android directories from your .gitignore
.
Integrating with Flagship Code™ can be broken down into: configuring assets, configuring environments and linking / generating plugins. While this may seem tedious, it will empower you with idempotent native code generation to effortlessly collaborate on building your application. Your team will no longer have to worry about maintaining native code directories for iOS and Android - just focus on React Native.
To get started, you will want to add the Flagship Code™ core and cli packages.
First, add the ios
and android
directories to your .gitignore
. Create a .coderc
directory in the root of your project. This is the Flagship Code™ runtime configuration directory where all configurations will be hosted i.e., CaC. Inside .coderc
, create an env
directory - where all your typed environments will exist. These are the only two mandatory directories.
Additional directories are recommended for separation of concerns. Inside .coderc
create a signing
directory - this will be where your native codesigning exists. For iOS, this would be certs and profiles. For Android, it will be your keystores.
It will be beneficial to break down your iOS codesigning assets into their respective environments e.g., enterprise, adhoc, store, etc. This way only a minimal set of profiles and certs are configured during adding of codesigning keys for CI/CD. Inside .coderc
create an assets
directory - this will be useful to host all plugin assets. To follow along, check out this example.
Your .coderc
directory should now look like:
.coderc
├── assets
├── env
└── signing
Assets will generally be files related to plugins e.g., _.png for app icons, _.png for splash screen, and google-services.json for Firebase, etc. Typically, organizing assets by functionality is the most readable structure. To follow along, check out this example.
./assets
├── app-icon
│ ├── android-adaptive-background.png
│ ├── android-adaptive-foreground.png
│ ├── android-legacy.png
│ └── ios-universal.png
├── extensions
│ └── notifications
│ ├── notifications-Info.plist
│ ├── notifications.h
│ └── notifications.m
├── fonts
│ ├── Font-Bold.ttf
│ ├── Font-ExtraBold.ttf
│ ├── Font-ExtraLight.ttf
│ ├── Font-Light.ttf
│ ├── Font-Medium.ttf
│ ├── Font-Regular.ttf
│ └── Font-SemiBold.ttf
└── splash-screen
├── android
│ └── generated
│ └── logo.png
└── ios
└── generated
└── logo.png
Env will be contain environment files e.g., development, production, adhoc, etc. These are TypeScript files based on the @brandingbrand/code-core
exported type Config<T>
. To follow along, check out this example.
./env
├── env.dev.ts
└── env.prod.ts
The signing folder will host all of your files related to codesigning. Android codesigning relates to your *.keystore. iOS codesigning relates to your certificates, entitlements and provisioning profiles. To follow along, check out this example.
./signing
├── AppleWWDRCA.cer
├── release.keystore
├── enterprise
│ ├── enterprise.cer
│ ├── enterprise.entitlements
│ ├── enterprise.mobileprovision
│ ├── enterprise.p12
│ └── enterprise_notification.mobileprovision
└── store
├── store.cer
├── store.entitlements
├── store.mobileprovision
├── store.p12
└── store_notification.mobileprovision
Environment configuration files are the foundation of Flagship Code™ - this is the Configuration as Code (CaC) content.
In the .coderc/env
directory create an environment file e.g., env.prod.ts. Next, Import Config
from @brandingbrand/code-core
in your environment configuration file. This will allow you to generate a basic build configuration for iOS and Android. Plugin types can be imported and extended upon the base config e.g. Config & CodePluginAsset
. With this simplistic approach you get a build configuration with plugins.
import {Config} from '@brandingbrand/code-core';
import {CodePluginAsset} from '@brandingbrand/code-plugin-asset';
const prod: Config & CodePluginAsset = {
ios: {
name: 'code',
bundleId: 'com.code',
displayName: 'Flagship Code™',
.
.
.
},
android: {
name: 'code',
displayName: 'Flagship Code™',
packageName: 'com.code',
.
.
.
},
app: {},
codePluginAsset: {
code: {
assetPath: ['assets/fonts'],
},
},
};
export default prod;
The advanced approach will allow you take advantage of build and runtime configuration from a single environment configuration file. We will utiilize TypeScript's triple-dash directive to solve for a single source of truth.
Move the environment type to a common location inside the src/types
directory. Inside the src/types
directory create an app.d.ts
file. This file is a type declaration file which we will use to define our overall app type.
In this file you will define the build and runtime configuration, where the interface App
is your runtime configuration. This will provide us with type-hints when utilizing runtime configurations.
app.d.ts
declare module '@brandingbrand/code-app' {
type ENV = import('@brandingbrand/code-core').Config<App> &
import('@brandingbrand/code-plugin-asset').CodePluginAsset &
import('@brandingbrand/code-plugin-app-icon').CodePluginAppIcon &
import('@brandingbrand/code-plugin-fastlane').CodePluginFastlane &
import('@brandingbrand/code-plugin-permissions').CodePluginPermissions &
import('@brandingbrand/code-plugin-splash-screen').CodePluginSplashScreen &
import('@brandingbrand/code-plugin-target-extension').CodePluginTargetExtension;
interface App {
foo: string;
bar: number;
}
}
If you are utilizing fsapp
we will overwrite the any
env
type with our defined type via fsapp.d.ts
file - To follow along, check out this example.
fsapp.d.ts
import '@brandingbrand/fsapp';
declare module '@brandingbrand/fsapp' {
let env: import('@brandingbrand/code-core').Config<
import('@brandingbrand/code-app').App
>;
}
In your env file, .coderc/env/env.*.ts
you can now utilize the triple-dash directive to utilize this type outside the src/
directory. To follow along, check out this example.
env.prod.ts
/// <reference path="../../src/types/app.d.ts" />
import {ENV} from '@brandingbrand/code-app';
const prod: ENV = {
ios: {
name: 'code',
bundleId: 'com.code',
displayName: 'Flagship Code™',
.
.
.
},
android: {
name: 'code',
displayName: 'Flagship Code™',
packageName: 'com.code',
.
.
.
},
app: {
foo: 'bar',
bar: 123,
},
codePluginAsset: {
code: {
assetPath: ['assets/fonts'],
},
},
};
export default prod;
If you are using fsapp
to access the env
object - the environment configuration is now typesafe at runtime.
Plugins are platform specific scripts that generate or manipulate native code with respect to a specific third-party depedency. Plugins are npm packages that are added as dev dependencies (only required at time of code init
). They may or may not export an interface that will need to be added to your environment as shown in the previous section. Plugins are run by the plugins
executor by auto-discovery.
Plugins can be remote or local for ultimate flexibility. Link your plugins by adding them to the package.json
code.plugins
array in a priority order. Plugins are run asynchronously from 0th-index to Nth-index (async / await).
package.json
"code": {
"plugins": [
"@brandingbrand/code-plugin-asset",
"@brandingbrand/code-plugin-permissions",
"@brandingbrand/code-plugin-native-navigation",
"@brandingbrand/code-plugin-app-icon",
.
.
.
]
}
Generating a plugin assumes you have familiarity with creating TypeScript / JavaScript packages. A plugin is a TypeScript package and must abide by the interface of having ios
and android
named exports that accept the Config
type; exported from @brandingbrand/code-core
. As noted by the interface below, these are async functions - all plugins are run via async / await.
index.d.ts
declare const ios: (config: Config) => Promise<void>;
declare const android: (config: Config) => Promise<void>;
The config interface can also be an intersection type of the Config
and the interface of the plugin i.e. CodePluginAppIcon
.
index.d.ts
declare const ios: (config: Config & CodePluginAppIcon) => Promise<void>;
declare const android: (config: Config & CodePluginAppIcon) => Promise<void>;
There are a number of ways to compile / bundle your TypeScript to JavaScript. In this monorepo, microbundle is utilized to compile and bundle TypeScript into JavaScript.
A very simple plugin that can be used as inspiration to generate your own plugin is the local plugin example - the only difference being that if you are distrbuting this package to npm you would not persist the dist
directory. If you are making a local plugin, you should persist the dist
directory. It's strongly recommended that each plugin have it's own set of unit tests than can be run via jest.
Getting Started
- Flagship Technical Introduction
- Setting up Your Development Environment
- Getting Started with Flagship
- Creating a Flagship App
How To
- Running Flagship Apps
- Managing Environments
- Creating App Icons
- Creating Launch Screens
- Signing Your Apps
- Using React Native Permissions v2
- Using SSL Certificate Pinning
- Initializing Multiple Xcode Targets
Major Releases