一个面向 Expo prebuild / Dev Client 的 JPush Config Plugin。
自动注入 iOS / Android 原生配置,完整支持主流 Android 厂商通道,保障推送送达率。
Important
JPush 不支持 Expo Go。本项目面向 expo prebuild 后的原生工程,适用于 Dev Client 或原生构建流程。
mx-jpush-expo 把 Expo 项目接入 JPush 时最容易反复手改的原生步骤,收敛成一次 expo prebuild:
- 自动写入 iOS
Info.plist的 JPush 配置和后台模式 - 自动写入 iOS
.entitlements的aps-environment,保障 APNs 推送授权 - 自动注入 iOS
AppDelegate.swift的 JPush 初始化与回调代码 - 自动复用或创建 Swift
Bridging Header - 自动修改 Android
AndroidManifest.xml、build.gradle、settings.gradle - 支持华为、FCM、小米、OPPO、VIVO、魅族、荣耀、蔚来���厂商通道注入
- 敏感参数支持环境变量注入,避免密钥明文提交到代码仓库
适合这些场景:
- 你使用 Expo,但需要 JPush 和厂商通道能力
- 你希望把原生改动交给 Config Plugin 管理,而不是手改生成代码
- 你需要在 CI / 团队协作里稳定复现
prebuild输出 - 你需要严格的密钥安全管理,避免敏感信息泄露
| 项目 | 版本 |
|---|---|
| Expo SDK | 55 / 56 / 57 |
| 仓库开发基线 | Expo SDK 57.0.15 |
| React Native | 0.83.6(SDK 55)/ 0.85.2(SDK 56)/ 0.86.2(SDK 57) |
| Node.js | >= 20.19.4 |
jpush-react-native |
3.1.9 |
jcore-react-native |
2.3.0 |
package.json 保留 Node.js >= 20.19.4,以继续支持 SDK 55 / 56。Expo SDK 57 的开发与 prebuild 工具链要求 Node.js >= 22.13.0;仓库的 SDK 56 / 57 smoke 均使用 Node.js 24.15.0。
npm install mx-jpush-expo
npm install jpush-react-native@3.1.9 jcore-react-native@^2.3.0或使用 pnpm:
pnpm add mx-jpush-expo
pnpm add jpush-react-native@3.1.9 jcore-react-native@^2.3.0推荐使用 app.config.ts,并把 Android 的敏感值交给环境变量或 gradle.properties。
npx expo prebuild只刷新 Android:
npx expo prebuild -p android如果你修改了 app.config.ts / app.json 中的插件参数,需要重新执行一次 prebuild,让最新配置重新落到原生工程。已经生成过原生目录时,建议按平台增量刷新;如果怀疑宿主工程里有历史残留,再使用 --clean 重新生成。
npx expo prebuild -p android --clean
npx expo prebuild -p ios --cleanimport type { ConfigContext, ExpoConfig } from 'expo/config';
import 'dotenv/config';
export default ({ config }: ConfigContext): ExpoConfig => {
const isProduction = process.env.EXPO_PUBLIC_ENV === 'production';
return {
...config,
plugins: [
...(config.plugins || []),
[
'mx-jpush-expo',
{
appKey: process.env.JPUSH_APP_KEY ?? '',
channel: process.env.JPUSH_CHANNEL,
packageName: process.env.JPUSH_PKGNAME,
apsForProduction: isProduction,
vendorChannels: {
huawei: { enabled: true },
fcm: { enabled: true },
xiaomi: {
appId: process.env.JPUSH_XIAOMI_APP_ID,
appKey: process.env.JPUSH_XIAOMI_APP_KEY,
},
oppo: {
appId: process.env.JPUSH_OPPO_APP_ID,
appKey: process.env.JPUSH_OPPO_APP_KEY,
appSecret: process.env.JPUSH_OPPO_APP_SECRET,
},
vivo: {
appId: process.env.JPUSH_VIVO_APP_ID,
appKey: process.env.JPUSH_VIVO_APP_KEY,
},
meizu: {
appId: process.env.JPUSH_MEIZU_APP_ID,
appKey: process.env.JPUSH_MEIZU_APP_KEY,
},
honor: {
appId: process.env.JPUSH_HONOR_APP_ID,
},
nio: {
appId: process.env.JPUSH_NIO_APP_ID,
},
},
},
],
],
};
};appKey仍然是插件必填项channel默认使用developer-default;只有需要区分渠道时才需要显式传入packageName默认读取expo.android.package;如果项目未配置 Android 包名,则需要显式传入- iOS 初始化参数会写入
Info.plist,不再直接拼进AppDelegate.swift - Android
manifestPlaceholders优先读取环境变量或gradle.properties,缺失时会回退到插件配置里的appKey/channel/packageName - 如果宿主已经定义了
manifestPlaceholders,插件会通过manifestPlaceholders += [...]追加 JPush 字段 非 JPush 的宿主键会被保留;如果宿主和插件都声明了同名的JPUSH_*键,后追加的 JPush 默认值会生效 - Android
app/build.gradle注入不再依赖versionName所在行;缺少versionName或使用 Gradle 变量时也能稳定prebuild vendorChannels决定要注入哪些厂商 SDK 与占位符;若声明某个厂商通道,就必须提供该厂商要求的必填字段- 厂商密钥仍然建议交给环境变量,避免把敏感信息直接���交到仓库
Android 端的 manifestPlaceholders 读取优先级如下:
System.getenv("...")project.findProperty("...")- 插件收到的配置值:
JPUSH_PKGNAME、JPUSH_APPKEY、JPUSH_CHANNEL - 空字符串,其余未提供默认值的字段
| 变量名 | 说明 |
|---|---|
JPUSH_APP_KEY |
JPush AppKey |
JPUSH_CHANNEL |
JPush Channel |
JPUSH_PKGNAME |
Android 包名 |
JPUSH_XIAOMI_APP_ID / JPUSH_XIAOMI_APP_KEY |
小米通道 |
JPUSH_OPPO_APP_ID / JPUSH_OPPO_APP_KEY / JPUSH_OPPO_APP_SECRET |
OPPO 通道 |
JPUSH_VIVO_APP_ID / JPUSH_VIVO_APP_KEY |
VIVO 通道 |
JPUSH_MEIZU_APP_ID / JPUSH_MEIZU_APP_KEY |
魅族通道 |
JPUSH_HONOR_APP_ID |
荣耀通道 |
JPUSH_NIO_APP_ID |
蔚来通道 |
示例 .env:
JPUSH_APP_KEY=your-jpush-app-key
JPUSH_CHANNEL=developer-default
JPUSH_PKGNAME=com.your.app
JPUSH_XIAOMI_APP_ID=your-xiaomi-app-id
JPUSH_XIAOMI_APP_KEY=your-xiaomi-app-key| 厂商 | 额外文件 | 签名要求 | 说明 |
|---|---|---|---|
| 华为 | agconnect-services.json |
需要 | 需配置 SHA256 指纹 |
| FCM | google-services.json |
不需要 | 需在 Firebase 控制台申请 |
| 荣耀 | 无 | 需要 | 需配置 SHA256 指纹 |
| 蔚来 | 无 | 需要 | 需配置应用签名 |
| 小米 | 无 | 不需要 | 仅需 AppId / AppKey |
| OPPO | 无 | 不需要 | 仅需 AppId / AppKey / AppSecret |
| VIVO | 无 | 不需要 | 仅需 AppId / AppKey |
| 魅族 | 无 | 不需要 | 仅需 AppId / AppKey |
官方参数说明见:极光推送 Android 厂商通道参数获取
appKey:JPush 后台创建应用后获得的 AppKeychannel:渠道标识,默认developer-defaultapsForProduction:是否使用生产环境 APNs,默认false(开发环境)。插件会据此在.entitlements里写入aps-environment: development / production,并保留宿主已有的其他 entitlement 键autoRegisterOnLaunch:iOS only. Defaults totrue; set it tofalseto defer JPush setup and APNs registration until the user gives consent
Set autoRegisterOnLaunch to false when your app must obtain user consent
before initializing JPush or registering with APNs:
[
'mx-jpush-expo',
{
appKey: 'your-jpush-app-key',
channel: 'developer-default',
apsForProduction: false,
autoRegisterOnLaunch: false,
},
]The plugin still generates the iOS notification delegates, APNs token
callbacks, Info.plist, entitlements, and Bridging Header. After consent, call
the existing jpush-react-native API once from your application code:
import JPush from 'jpush-react-native';
export function registerPushAfterConsent(): void {
JPush.init({
appKey: 'your-jpush-app-key',
channel: 'developer-default',
production: false,
});
}Keep production aligned with apsForProduction. JPush.init performs both
JPush setup and APNs registration on iOS, so no additional native bridge is
required. This option only changes iOS launch-time registration; Android
behavior is unchanged.
packageName:Android 应用包名,用于manifestPlaceholders,默认读取expo.android.package- 厂商通道通过
vendorChannels对象配置,每个厂商独立开关
| 平台 | 文件 | 作用 |
|---|---|---|
| iOS | ios/<app>/Info.plist |
写入 JPUSH_* 配置并合并 UIBackgroundModes |
| iOS | ios/<app>/<app>.entitlements |
按 apsForProduction 写入 aps-environment 并保留其他 entitlement 键 |
| iOS | ios/<app>/AppDelegate.swift |
默认注入 JPush 启动初始化,并始终保留 APNs 回调和代理扩展;延后模式会省略启动初始化 |
| iOS | ios/<app>/<target>-Bridging-Header.h |
复用或创建桥接头文件,保证 import 幂等 |
| Android | android/app/src/main/AndroidManifest.xml |
写入 JPUSH_APPKEY / JPUSH_CHANNEL meta-data |
| Android | android/app/build.gradle |
注入依赖、manifestPlaceholders、abiFilters、厂商插件 |
| Android | android/build.gradle |
注入厂商 Maven 仓库与 classpath |
| Android | android/settings.gradle |
注册 jpush-react-native / jcore-react-native 模块 |
| Android | android/gradle.properties |
写入华为 AGC 兼容性开关 |
重新执行 expo prebuild 后,建议检查:
Info.plist中存在:JPUSH_APPKEYJPUSH_CHANNELJPUSH_APS_FOR_PRODUCTION
UIBackgroundModes会保留宿主已有值,并补齐:fetchremote-notification
<app>.entitlements中存在aps-environment,值跟随apsForProduction为development或productionautoRegisterOnLaunch省略或为true时,AppDelegate.swift中存在JPUSHService.setupautoRegisterOnLaunch: false时,AppDelegate.swift不包含启动阶段的JPUSHService.setup或 APNs registration,但仍保留 APNs token callbacks 与JPUSHRegisterDelegateextensionAppDelegate.swift中的调试日志与JPUSHService.setDebugMode()会被#if DEBUG包裹,release 构建不再无条件打印- 如果项目使用 Swift,插件会优先复用已有
SWIFT_OBJC_BRIDGING_HEADER;未配置时会自动创建<target>-Bridging-Header.h
android/app/build.gradle 中的 JPush 占位符应保持"运行时读取",而不是写死明文:
manifestPlaceholders += [
JPUSH_PKGNAME: System.getenv("JPUSH_PKGNAME") ?: (project.findProperty("JPUSH_PKGNAME") ?: "com.your.app"),
JPUSH_APPKEY: System.getenv("JPUSH_APP_KEY") ?: (project.findProperty("JPUSH_APP_KEY") ?: "your-jpush-app-key"),
JPUSH_CHANNEL: System.getenv("JPUSH_CHANNEL") ?: (project.findProperty("JPUSH_CHANNEL") ?: "developer-default")
]也就是说:
- 宿主原本的
manifestPlaceholders = [...]配置会被保留 - 对于宿主和插件都声明的
JPUSH_*键,后追加的 JPush 值会在合并结果中生效 - 对最终消费方,插件配置本身已经足够让
prebuild产出可用的 Android 默认值 - 对 CI / EAS / 多环境发布,依然推荐通过环境变量或
gradle.properties在构建时覆盖这些值 - 厂商通道密钥如果需要按环境切换,也应沿用同样的覆盖策略
不支持。JPush 需要原生工程和原生依赖,必须通过 expo prebuild 进入 Dev Client 或原生构建流程。
因为参数校验和 Info.plist 注入都发生在 Expo 配置阶段。它不会再被直接拼进 AppDelegate.swift,但仍然需要在配置阶段提供。channel 未传时会写入默认值 developer-default。
如果你遇到类似 com.android.tools.build:gradle is no set in the build.gradle file 的错误,需要检查业务项目自己的 android/build.gradle 与 Expo 版本是否匹配。这不是本插件主动引入的行为变更。
不建议。重装依赖后会丢失,正式方式建议使用 pnpm patch mx-jpush-expo 或维护自己的 fork。
npm 包入口是根目录的 app.plugin.js,它会加载发布产物 plugin/build。这意味着:
- 仓库开发者在发布新版本前,需要先执行
npm run build package.json的files已包含app.plugin.js与plugin/build,消费方不会直接运行plugin/src中的 TypeScript 源码- 如果你维护的是 fork 或 patch 包,发布前请先确认编译产物已同步更新,否则消费项目仍会执行旧逻辑
npm run build
pnpm test --runInBand
pnpm run lint
pnpm run test:prebuild:expo56
pnpm run test:prebuild:expo57- iOS
Info.plist合并与 Bridging Header 创建 / 幂等 - iOS
AppDelegate.swift注入与幂等 - Android
Manifest、Gradle、Settings 和gradle.properties原生输出 - fixture-based 回归测试,确保
compileModsAsync输出稳定 - Expo 56 / 57 真实
prebuild --clean --no-installsmoke,验证发布包入口和 iOS / Android 生成结果;SDK 57 fixture 还会先通过expo-doctor
prebuild smoke 只证明配置插件能在对应 Expo 模板中生成预期原生配置,不代表已完成真机推送、原生 release build 或 JPush 服务端投递验证。
更多开发细节见 DEVELOPMENT.md。
mx-jpush-expo/
├── app.plugin.js
├── plugin/
│ ├── src/
│ │ ├── index.ts
│ │ ├── types.ts
│ │ ├── ios/
│ │ │ ├── infoPlist.ts
│ │ │ ├── entitlements.ts
│ │ │ ├── appDelegate.ts
│ │ │ └── bridgingHeader.ts
│ │ ├── android/
│ │ │ ├── androidManifest.ts
│ │ │ ├── appBuildGradle.ts
│ │ │ ├── projectBuildGradle.ts
│ │ │ ├── settingsGradle.ts
│ │ │ └── gradleProperties.ts
│ │ └── utils/
│ │ ├── generateCode.ts
│ │ └── sourceCode.ts
│ ├── __tests__/
│ │ ├── fixtures/
│ │ ├── iosFixture.ts
│ │ ├── androidFixture.ts
│ │ └── *.test.ts
│ └── build/
├── .github/workflows/ci.yml
├── CHANGELOG.md
├── DEVELOPMENT.md
└── README.md
插件内部说明见 plugin/README.md。
完整更新历史请查看 CHANGELOG.md。
- 支持 Expo 57(React Native 0.86.2 / React 19.2.3),仓库开发基线升级到 Expo SDK 57.0.15,同时保留 SDK 55 / 56 的 peer compatibility
channel和packageName支持默认推断,基础接入只需要显式提供appKey- Expo 56 / 57 真实 prebuild smoke 可验证发布包入口和 iOS / Android 生成结果,SDK 57 fixture 同时运行
expo-doctor - iOS
AppDelegate.swift注入兼容 Swift 访问级别导入(SDK 56 默认internal import Expo) - iOS prebuild ��在会按
apsForProduction写入.entitlements的aps-environment,解决推送通知无法收到的问题,同时保留宿主已有的其他 entitlement 键 - iOS
UIBackgroundModes改为合并写入,不再覆盖宿主已有后台模式 - Swift
Bridging Header支持优先复用、缺失自动创建,并保持幂等 - 补齐 iOS / Android fixture-based 原生回归测试
- Android
manifestPlaceholders改为在宿主现有配置后追加,不再依赖versionName文本锚点 - iOS
AppDelegate.swift中的 JPush 调试日志只在DEBUG构建启用 - 加入 ESLint 与 CI 质量闭环
- 对齐 Expo SDK 55 的版本声明与仓库开发基线
- Android 敏感参数支持环境变量 /
gradle.properties读取,不再明文写入构建脚本 - iOS 初始化参数改为从
Info.plist读取,不再直接注入AppDelegate.swift
感谢以下资料与实现思路的启发:
本项目使用 MIT License。