本文讨论了反应本机项目的CI/CD生态系统状态(重点是iOS)。感谢Apple,需要MACOS的Xcode对于创建iOS应用至关重要。
方法
每个人都知道捆绑对象C代码时Xcode非常慢。目前,我们有很多策略来加快iOS构建。我将它们分为两组:
1)仅构建JavaScript
- ðBuilding and injecting a JS bundle一个基本概念:如果本机代码没有更改,则只需创建JS捆绑包并将其注入预编译应用程序即可。 (如果您使用Hermes js engine,则必须采取额外的步骤将JS文件转换为字节码。)
- 如果您没有时间将上述策略付诸实践,则可以支付Nitro Build Service(x5更快)。我尝试了一下,对他们的工作感到非常高兴:
2)技巧和缓存
- ð¾Cache Pods may speed up iOS(〜x4更快)通常非常快,但仍然必须使用Mac OS跑步者
- 文档:Use a compiler cache(github Action implementation)
- Turning off Flipper on CI保存3分钟
- 禁用Minification&Metro Cache用于登台/Beta构建
- 从ci 上切换到M1 MacOS处理器
但是,上述所有方法都必须使用Apple Build工具或第三方服务ðµ。但是我确实有一些特别的特别之处。 ðð
是的,它是CodePush!
我本人更喜欢专注于一种称为CodePushð的免费开源替代品,这使我们能够真正停止为MacOS跑步者付款。安装新的本机依赖性或不经常触摸本地代码非常适合您。这怎么可能?
我使用下一个策略:
- 如果更改了本机代码(通常是
./android/**
,./ios/**
),我们将应用经典流程:为Android和iOS构建整个应用程序。 - 但是,如果仅修改了JavaScript代码,那么创建新的本机代码的重点是什么?相反,创建一个JavaScript捆绑包并将其部署到CodePush。当然,您不需要使用Mac OS跑步者来编译JS。最便宜的Linux应该足够ð°。
成功构建后,您的团队成员可以使用应用程序DEV菜单快速应用部署的CodePush版本。
演示回购:https://github.com/retyui/CodePushCiCd(CI/CD GitHub Action,Dev-Menu,客户部件)
配置项目
首先是在AppCenter中创建一个新的iOS和Android应用:https://appcenter.ms/orgs/MyOrganizationTest/applications/create
之后,您需要创建标准部署:
默认情况下,将创建两个分期和生产部署:
管理面板的最后一步是收集iOS和Android应用程序的生产部署密钥。 (稍后将在JS侧使用)。打开CodePush页面,然后点击部署Select select
旁边的扳手图标// constants.ts
import {Platform} from 'react-native';
const CODE_PUSH_IOS_PROD_KEY = 'Gbsg8cTjdcSWOwgJEOEHqk8VE1x6ITThqvNe0';
const CODE_PUSH_ANDROID_PROD_KEY = 'Ob7LrQg_w-l4w1SOLDYT5XBw76_6Pz-NVCed1';
export const CODE_PUSH_PROD_KEY = Platform.select({
ios: CODE_PUSH_IOS_PROD_KEY,
default: CODE_PUSH_ANDROID_PROD_KEY,
});
客户端(核心逻辑)
首先添加koude2。然后继续使用本地部分:
现在让我们编写一些代码。我创建了一个简单的挂钩koude3,它将封装您需要的所有逻辑,并应在应用程序的根组件中使用:
// App.tsx
import {useSyncOnAppStart} from './codepush/useSyncOnAppStart'
export detault function App(){
useSyncOnAppStart(); // Init CodePush sync on App mount
return <View/>;
}
// codepush/useSyncOnAppStart.ts
import {useEffect} from 'react';
import {AppState, AppStateStatus} from 'react-native';
import CodePush from 'react-native-code-push';
import {CODE_PUSH_PROD_KEY} from '../constants';
const noop = () => {};
const isDeploymentNotFoundError = (error: Error) =>
error?.message?.includes?.('No deployment found');
function syncOnAppStart() {
async function start() {
try {
// see comment#1
const runningPackage = await CodePush.getUpdateMetadata(
CodePush.UpdateState.RUNNING,
);
const isNonProduction =
runningPackage?.deploymentKey &&
runningPackage.deploymentKey !== CODE_PUSH_PROD_KEY;
// see comment#2
const nonProdConfig = {
// Non-Prod sync (To enable this variant use "Dev Menu" to change deployment)
deploymentKey: runningPackage?.deploymentKey,
installMode: CodePush.InstallMode.IMMEDIATE,
updateDialog: {
// Ask mobile team member before install new version for custom deployment
},
};
// see comment#3
const prodConfig = {
// Prod sync (↓↓↓ Default values, see: https://github.com/microsoft/react-native-code-push/blob/master/docs/api-js.md#codepushoptions)
installMode: CodePush.InstallMode.ON_NEXT_RESTART,
deploymentKey: CODE_PUSH_PROD_KEY,
rollbackRetryOptions: {
maxRetryAttempts: 1,
delayInHours: 24,
},
};
// see comment#4
const status = await CodePush.sync(
isNonProduction ? nonProdConfig : prodConfig,
);
return status;
} catch (error) {
// see comment#5
if (isDeploymentNotFoundError(error)) {
return CodePush.clearUpdates();
}
// see comment#6
trackError(error);
}
}
// see comment#7
const onAppStateChange = async (newState: AppStateStatus) => {
if (newState === 'active') {
await start();
}
};
let unsubscribe = noop;
start()
.catch(noop)
.finally(() => {
const subscription = AppState.addEventListener(
'change',
onAppStateChange,
);
unsubscribe = () => subscription.remove();
});
return () => {
unsubscribe();
};
}
export const useSyncOnAppStart = (): void => {
useEffect(() => {
const unsubscribe = syncOnAppStart();
return unsubscribe;
}, []);
};
注释#1 :首先获取有关已安装软件包的信息。 注释#2 :处理非生产软件包时,我们采用koude4,它向用户显示更新警报。 Comment#3
:对于生产构建,应用程序将使用默认选项。 Comment#4
:运行代码推送同步(读取文档:koude7)。 评论#5 :当开发人员仍然拥有已删除的开发部署中的安装程序包时,没有发现任何部署问题。 注释#6 :向您发送错误以进行错误跟踪(如Sentry)。 评论#7 :如果使用该应用程序在使用该应用程序时已发布新的CodePush,请添加应用程序侦听器以使所有内容保持同步。
客户端(Dev-Menu)
本节将容易得多。为了使应用自定义构建简单,我构建了react-native-code-push-dev-menu模块。
# Install
yarn add react-native-code-push-dev-menu
# or npm install react-native-code-push-dev-menu
用法示例:
// DevMenuScreen.tsx
import {
CodePushDeMenuButton,
configurateProject,
} from 'react-native-code-push-dev-menu';
configurateProject({
readonlyAccessToken: Platform.select({
// Read-only access tokens
// https://docs.microsoft.com/en-us/appcenter/api-docs/#creating-an-app-center-app-api-token
ios: '128009dc42ded5e71ef21e007a24eb67b5c3279f',
default: '42f471742864bd9c1917f322918b163a90d13904',
}),
appCenterAppName: Platform.select({
ios: 'MyApp-iOS',
default: 'MyApp-Android',
}),
appCenterOrgName: 'MyOrganizationTest',
});
function DevMenuScreen() {
return (
<SafeAreaView>
<CodePushDeMenuButton />
// Other dev things
</SafeAreaView>
);
}
CI/CD
自动化的CodePush构建是最后建立的。在AppCener的管理员中,您需要使用填充访问
创建APPCENTER_ACCESS_TOKEN
https://appcenter.ms/settings/apitokens
接下来,让我们构建配置文件koude9
#!/bin/bash
export APP_CENTER_ORG_NAME=MyOrganizationTest
export APP_CENTER_APP_NAME_IOS=MyApp-iOS
export APP_CENTER_APP_NAME_ANDROID=MyApp-Android
以下步骤是编写一个名为koude10的构建脚本,该脚本将创建新的部署(使用分支名称作为部署名称),并将将在该环境中部署的JS捆绑包。
#!/bin/bash
set -x # all executed commands are printed to the terminal
set -e # immediately exit if any command has a non-zero exit status
source "./scripts/envs.sh"
if [ -z "$DEPLOYMENT_NAME" ]; then
echo "Please sure that DEPLOYMENT_NAME exists"
exit 1
fi
if [ "$DEPLOYMENT_NAME" == "Production" ]; then
echo "You can't use reserved name 'Production' for deployment"
exit 1
fi
if [ -z "$APPCENTER_ACCESS_TOKEN" ]; then
echo "Please sure that APPCENTER_ACCESS_TOKEN exists"
exit 1
fi
if [ "$PLATFORM" != "ios" ] && [ "$PLATFORM" != "android" ]
then
echo "Please sure that you set PLATFORM env variable (ios | android)"
exit 1
fi
APP_CENTER_APP_NAME="$APP_CENTER_APP_NAME_ANDROID"
if [ "$PLATFORM" == "ios" ]; then
APP_CENTER_APP_NAME="$APP_CENTER_APP_NAME_IOS"
fi
# Create new Codepush Deployment
appcenter codepush deployment add -a "$APP_CENTER_ORG_NAME/$APP_CENTER_APP_NAME" "$DEPLOYMENT_NAME" || true # Ignore "deployment named test-build already exists" error
# Create JS bundle
appcenter codepush release-react -a "$APP_CENTER_ORG_NAME/$APP_CENTER_APP_NAME" -d "$DEPLOYMENT_NAME" --target-binary-version "*" --description "$DESCRIPTION"
您现在可以进行本地测试:
# Required
export APPCENTER_ACCESS_TOKEN=xxxx
export DEPLOYMENT_NAME=my-branch-name
# Optional
export DESCRIPTION="My desc..."
PLATFORM=ios ./scripts/codepush-non-prod.sh # Codepush release for iOS
PLATFORM=android ./scripts/codepush-non-prod.sh # Codepush release for Android
,如果您使用Github Actions a创建一个koude11文件:
name: Mobile CodePush
on:
pull_request:
paths-ignore:
- 'android/**'
- 'ios/**'
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
android_beta_mobile_build:
name: Build Non-Prod
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 1
- name: Yarn cache
uses: actions/cache@v3
id: node_cache
with:
path: node_modules
key: ${{ runner.os }}-yarn-${{ hashFiles('./yarn.lock') }}
- name: Install node_modules
run: yarn install --frozen-lockfile
if: steps.node_cache.outputs.cache-hit != 'true'
- name: Create Beta CodePush Release
env:
APPCENTER_ACCESS_TOKEN: ${{ secrets.APPCENTER_ACCESS_TOKEN }}
DEPLOYMENT_NAME: ${{ github.head_ref || github.ref_name }} # `head_ref` pull_request event, `ref_name` workflow_dispatch event
DESCRIPTION: Made by - ${{ github.actor }}
NODE_ENV: production
run: |
npm install -g appcenter-cli@2.12.0
PLATFORM=android ./scripts/codepush-non-prod.sh
PLATFORM=ios ./scripts/codepush-non-prod.sh
此外,可以使用手动运行来在CI上进行测试:
让我们回顾创建的内容ð¥³ðÖððð
- 所有刚刚修改JS的PR将释放到CodePush(分支名称将用作部署名称);
- 您可以使用Dev Menu快速应用这些更改; ð¶©
- 您无需使用MacOS来创建CodePush版本;ð
如果您有任何疑问,请随时在评论部分中询问。
Muramur© p>