mx-jpush-expo
Version:
Expo 集成极光推送(JPush)一体化解决方案,支持 iOS/Android 厂商通道
393 lines (305 loc) • 15.4 kB
Markdown
<div align="center">
<h1>MX-JPush-Expo</h1>
<p>
一个面向 <strong>Expo prebuild / Dev Client</strong> 的 JPush Config Plugin。<br />
自动注入 iOS / Android 原生配置,完整支持主流 Android 厂商通道,保障推送送达率。
</p>
<p>
<a href="https://www.npmjs.com/package/mx-jpush-expo"><img alt="npm version" src="https://img.shields.io/npm/v/mx-jpush-expo?logo=npm&label=npm"></a>
<a href="https://github.com/konodioda727/JPush-Expo/actions/workflows/ci.yml"><img alt="CI status" src="https://img.shields.io/github/actions/workflow/status/konodioda727/JPush-Expo/ci.yml?branch=main&logo=githubactions&label=CI"></a>
<a href="https://github.com/konodioda727/JPush-Expo/blob/main/LICENSE"><img alt="license" src="https://img.shields.io/github/license/konodioda727/JPush-Expo"></a>
<img alt="Expo SDK 55+" src="https://img.shields.io/badge/Expo%20SDK-55%2B-000020?logo=expo">
<a href="https://nodejs.org/"><img alt="Node.js >=18.18.0" src="https://img.shields.io/badge/Node.js-%3E%3D18.18.0-339933?logo=nodedotjs&logoColor=white"></a>
</p>
</div>
> [!IMPORTANT]
> JPush 不支持 Expo Go。本项目面向 `expo prebuild` 后的原生工程,适用于 Dev Client 或原生构建流程。
> [!TIP]
> 本项目持续参考并吸收以下资料:<br>
> - [JPush 集成 Expo](https://juejin.cn/post/7423235127716659239)
> - [Expo SDK 53+ 集成极光推送 iOS Swift](https://juejin.cn/post/7554288083597885467)
> - [RunoMeow/jpush-expo-config-plugin](https://github.com/RunoMeow/jpush-expo-config-plugin)
## 目录
- [为什么使用它](#为什么使用它)
- [支持矩阵](#支持矩阵)
- [快速开始](#快速开始)
- [推荐配置](#推荐配置)
- [环境变量与厂商通道](#环境变量与厂商通道)
- [配置说明](#配置说明)
- [插件会修改哪些原生文件](#插件会修改哪些原生文件)
- [如何验证生成结果](#如何验证生成结果)
- [常见问题](#常见问题)
- [开发与测试](#开发与测试)
- [项目结构](#项目结构)
- [最近更新](#最近更新)
- [致谢与许可](#致谢与许可)
## 为什么使用它
`mx-jpush-expo` 把 Expo 项目接入 JPush 时最容易反复手改的原生步骤,收敛成一次 `expo prebuild`:
- 自动写入 iOS `Info.plist` 的 JPush 配置和后台模式
- 自动注入 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+` |
| 仓库开发基线 | `Expo SDK 55` |
| React Native | `0.83.6` |
| Node.js | `>= 18.18.0` |
| `jpush-react-native` | `3.1.9` |
| `jcore-react-native` | `2.3.0` |
## 快速开始
### 1. 安装依赖
```bash
npm install mx-jpush-expo
npm install jpush-react-native@3.1.9 jcore-react-native@^2.3.0
```
或使用 `pnpm`:
```bash
pnpm add mx-jpush-expo
pnpm add jpush-react-native@3.1.9 jcore-react-native@^2.3.0
```
### 2. 配置插件
推荐使用 `app.config.ts`,并把 Android 的敏感值交给环境变量或 `gradle.properties`。
### 3. 生成原生工程
```bash
npx expo prebuild
```
只刷新 Android:
```bash
npx expo prebuild -p android
```
如果你修改了 `app.config.ts` / `app.json` 中的插件参数,需要重新执行一次 `prebuild`,让最新配置重新落到原生工程。已经生成过原生目录时,建议按平台增量刷新;如果怀疑宿主工程里有历史残留,再使用 `--clean` 重新生成。
```bash
npx expo prebuild -p android --clean
npx expo prebuild -p ios --clean
```
## 推荐配置
```ts
import 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 ?? 'developer-default',
packageName:
process.env.JPUSH_PKGNAME ?? config.android?.package ?? '',
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`、`packageName` 仍然是插件必填项
- 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` 读取优先级如下:
1. `System.getenv("...")`
2. `project.findProperty("...")`
3. 插件收到的配置值:`JPUSH_PKGNAME`、`JPUSH_APPKEY`、`JPUSH_CHANNEL`
4. 空字符串,其余未提供默认值的字段
### 可用环境变量
| 变量名 | 说明 |
| --- | --- |
| `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`:
```bash
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 厂商通道参数获取](https://docs.jiguang.cn/jpush/client/Android/android_3rd_param)
## 配置说明
### iOS 配置
- `appKey`:JPush 后台创建应用后获得的 AppKey
- `channel`:渠道标识,默认 `developer-default`
- `apsForProduction`:是否使用生产环境 APNs,默认 `false`(开发环境)
### Android 配置
- `packageName`:Android 应用包名,用于 `manifestPlaceholders`
- 厂商通道通过 `vendorChannels` 对象配置,每个厂商独立开关
## 插件会修改哪些原生文件
| 平台 | 文件 | 作用 |
| --- | --- | --- |
| iOS | `ios/<app>/Info.plist` | 写入 `JPUSH_*` 配置并合并 `UIBackgroundModes` |
| 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` 后,建议检查:
### iOS
- `Info.plist` 中存在:
- `JPUSH_APPKEY`
- `JPUSH_CHANNEL`
- `JPUSH_APS_FOR_PRODUCTION`
- `UIBackgroundModes` 会保留宿主已有值,并补齐:
- `fetch`
- `remote-notification`
- `AppDelegate.swift` 中存在 `JPUSHService.setup`
- `AppDelegate.swift` 中的调试日志与 `JPUSHService.setDebugMode()` 会被 `#if DEBUG` 包裹,release 构建不再无条件打印
- 如果项目使用 Swift,插件会优先复用已有 `SWIFT_OBJC_BRIDGING_HEADER`;未配置时会自动创建 `<target>-Bridging-Header.h`
### Android
`android/app/build.gradle` 中的 JPush 占位符应保持"运行时读取",而不是写死明文:
```gradle
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` 在构建时覆盖这些值
- 厂商通道密钥如果需要按环境切换,也应沿用同样的覆盖策略
## 常见问题
### 是否支持 Expo Go?
不支持。JPush 需要原生工程和原生依赖,必须通过 `expo prebuild` 进入 Dev Client 或原生构建流程。
### 为什么 iOS 仍然要求在插件配置里填写 `appKey` / `channel`?
因为参数校验和 `Info.plist` 注入都发生在 Expo 配置阶段。它们不会再被直接拼进 `AppDelegate.swift`,但仍然需要在配置阶段提供。
### Android 遇到 Gradle 插件版本错误怎么办?
如果你遇到类似 `com.android.tools.build:gradle is no set in the build.gradle file` 的错误,需要检查业务项目自己的 `android/build.gradle` 与 Expo 版本是否匹配。这不是本插件主动引入的行为变更。
### 直接改 `node_modules/mx-jpush-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 包,发布前请先确认编译产物已同步更新,否则消费项目仍会执行旧逻辑
## 开发与测试
```bash
npm run build
npm run test -- --runInBand
npm run lint
```
### 测试覆盖重点
- iOS `Info.plist` 合并与 Bridging Header 创建 / 幂等
- iOS `AppDelegate.swift` 注入与幂等
- Android `Manifest`、Gradle、Settings 和 `gradle.properties` 原生输出
- fixture-based 回归测试,确保 `compileModsAsync` 输出稳定
更多开发细节见 [DEVELOPMENT.md](./DEVELOPMENT.md)。
## 项目结构
```text
mx-jpush-expo/
├── app.plugin.js
├── plugin/
│ ├── src/
│ │ ├── index.ts
│ │ ├── types.ts
│ │ ├── ios/
│ │ │ ├── infoPlist.ts
│ │ │ ├── appDelegate.ts
│ │ │ └── bridgingHeader.ts
│ │ ├── android/
│ │ │ ├── androidManifest.ts
│ │ │ ├── appBuildGradle.ts
│ │ │ ├── projectBuildGradle.ts
│ │ │ ├── settingsGradle.ts
│ │ │ └── gradleProperties.ts
│ │ └── utils/
│ │ ├── codeValidator.ts
│ │ ├── 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](./plugin/README.md)。
## 最近更新
完整更新历史请查看 [CHANGELOG.md](./CHANGELOG.md)。
- 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`
## 致谢与许可
感谢以下资料与实现思路的启发:
- [JPush 集成 Expo](https://juejin.cn/post/7423235127716659239)
- [Expo SDK 53+ 集成极光推送 iOS Swift](https://juejin.cn/post/7554288083597885467)
- [RunoMeow/jpush-expo-config-plugin](https://github.com/RunoMeow/jpush-expo-config-plugin)
本项目使用 [MIT License](./LICENSE)。