React Native 打包体积优化指南:在保证功能正常的前提下把包压到最小

React Native 打包体积优化指南:在保证功能正常的前提下把包压到最小

适用版本:React Native 0.70+(Hermes 已成为默认引擎),Android & iOS 双端。

核心思路:先量化,再削减,最后验证。没有测量的优化是盲目的,没有验证的削减是危险的。


目录

  1. 第一步:先搞清楚包里面有什么
  2. [JavaScript 侧优化](#JavaScript 侧优化)
  3. [Android 原生侧优化](#Android 原生侧优化)
  4. [iOS 原生侧优化](#iOS 原生侧优化)
  5. 资源文件优化(双端通用)
  6. 依赖治理:最容易被忽视的大头
  7. 发版形态:让商店帮你瘦身
  8. 如何保证功能正常:验证清单
  9. 一张速查表

一、先搞清楚包里面有什么

任何优化之前,先分析包体构成。

Android

  • Android Studio 的 APK Analyzer (Build → Analyze APK)直接打开 APK/AAB,可以看到 classes.dexlib/(各架构的 .so)、res/assets/ 各占多少。
  • 重点关注:
    • lib/arm64-v8alib/armeabi-v7a 等目录:原生库(包括 React Native 自己的 libreactnativejni.so、Hermes 引擎、第三方原生模块)。
    • assets/index.android.bundle:JS 产物(开启 Hermes 后是字节码)。
    • res/assets/ 里的图片、字体。

iOS

  • 在 Xcode 中 Product → Archive 后,用 App Store Connect 的 App Thinning Size Report 查看各设备的实际下载体积。
  • 本地可以解压 .ipa(改后缀为 zip),查看 Payload/App.app 里的二进制主文件、Assets.car、JS bundle、字体等。

JS Bundle

用工具分析 JS bundle 里哪些模块占体积:

bash 复制代码
# 方案一:source-map-explorer
npm install -g source-map-explorer
# 先生成带 sourcemap 的 bundle,再分析
source-map-explorer index.android.bundle index.android.bundle.map

# 方案二:react-native-bundle-visualizer(更方便)
npx react-native-bundle-visualizer

经验值:一个空的新建 RN 项目,Android APK(单架构 + Hermes)约 8--12 MB;如果业务包到了 50 MB+,通常图片资源和第三方依赖是重灾区。


二、JavaScript 侧优化

2.1 使用 Hermes 引擎(新版默认开启)

Hermes 把 JS 预编译为字节码,包内不再需要携带 JS 引擎解释执行源码,bundle 更小、启动更快、内存更低。

js 复制代码
// android/app/build.gradle(RN 0.70+ 默认 true)
project.ext.react = [
    enableHermes: true,
]
ruby 复制代码
# ios/Podfile
use_react_native!(
  :path => config[:reactNativePath],
  :hermes_enabled => true
)

注意:切换 Hermes 后一定要全量回归测试,个别依赖 JIT 的库(如老版本 realm、某些 polyfill)可能不兼容。

2.2 Metro 配置:压缩与内联优化

js 复制代码
// metro.config.js
module.exports = {
  transformer: {
    // 开启内联 require:延迟加载模块,减小启动开销,bundle 更紧凑
    getTransformOptions: async () => ({
      transform: {
        experimentalImportSupport: false,
        inlineRequires: true,
      },
    }),
    minifierPath: 'metro-minify-terser',
    minifierConfig: {
      compress: {
        drop_console: true,      // 生产环境移除 console.*
        drop_debugger: true,
        pure_funcs: ['console.log', 'console.info', 'console.debug'],
      },
      mangle: true,              // 变量名混淆缩短
      output: {
        comments: false,         // 去掉注释
      },
    },
  },
};

也可以用 babel 插件兜底去 console:

bash 复制代码
npm i -D babel-plugin-transform-remove-console
js 复制代码
// babel.config.js
module.exports = {
  presets: ['module:@react-native/babel-preset'],
  env: {
    production: {
      plugins: ['transform-remove-console'],
    },
  },
};

2.3 代码分割与按需加载

  • React.lazy + Suspense 或路由库的懒加载能力,把非首屏页面拆出去。
  • 大图表、富文本编辑器等"重功能"页面,做到进入时才加载。
  • 避免在入口文件 import 整个大工具库:
js 复制代码
// ❌ 会把整个 lodash 打进 bundle
import _ from 'lodash';

// ✅ 只打包用到的方法
import debounce from 'lodash/debounce';

// ✅ 或者用 babel-plugin-lodash / babel-plugin-import 自动按需转换

2.4 别打不该打的

  • 确保打包脚本用的是 release 模式,debug bundle 会大很多且未压缩。
  • .env 开发配置、mock 数据、Storybook、测试代码不要进 release bundle。

三、Android 原生侧优化

3.1 按 ABI 拆分:立竿见影的大招

不同 CPU 架构的设备只需要对应的 .so 原生库。打 universal APK 会把 4 套架构全塞进去。

groovy 复制代码
// android/app/build.gradle
android {
    splits {
        abi {
            reset()
            enable true
            universalApk false   // 不打全架构包
            include "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
        }
    }
}

2026 年的今天,绝大多数国内分发只保留 arm64-v8a + armeabi-v7a 即可,x86 系列基本只用于模拟器,可以再省一份。上传 Google Play 则直接用 AAB(见第七节),商店自动按设备分发。

3.2 开启代码混淆与资源压缩

groovy 复制代码
android {
    buildTypes {
        release {
            minifyEnabled true        // R8 代码压缩、混淆、优化
            shrinkResources true      // 移除未引用的资源(必须配合 minifyEnabled)
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            zipAlignEnabled true
        }
    }
}

⚠️ 关键风险点shrinkResources 和 R8 会移除"看起来没被引用"的资源和类。RN 的反射调用、第三方 SDK 的 keep 规则没配好,就会运行时崩溃。务必:

  • 保留 RN 必需规则(RN 官方 aar 已自带大部分 proguard 规则);
  • 为三方 SDK 逐个添加官方文档要求的 -keep 规则;
  • tools:keep 标记被动态引用的资源(res/raw/keep.xml)。
xml 复制代码
<!-- res/raw/keep.xml:防止被反射引用的资源被 shrinkResources 删掉 -->
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:keep="@drawable/splash_*,@raw/notification_sound" />

3.3 压缩 .so 并启用 extractNativeLibs 控制

groovy 复制代码
android {
    packagingOptions {
        jniLibs {
            useLegacyPackaging false  // .so 压缩存储,安装时解压
        }
    }
}
  • useLegacyPackaging = false:APK 内 .so 被压缩,下载体积小,但安装后占用略大(Android 6.0+ 推荐)。
  • useLegacyPackaging = true:.so 不压缩并对齐,系统可直接从 APK 映射加载,安装后占用小但 APK 下载体积大。
  • 上架商店(走 AAB)时让商店处理即可,一般选 false

3.4 精简语言资源

groovy 复制代码
android {
    defaultConfig {
        resConfigs "zh-rCN", "zh-rTW", "en"  // 只保留需要的语言
    }
}

这一行可以砍掉所有第三方库自带的多语言字符串,通常能省几百 KB 到几 MB。

3.5 其他 Android 小技巧

  • 移除未用模块 :检查 android/app/build.gradledependenciessettings.gradle,把已经删掉的 JS 库对应的原生模块一并移除。
  • WebP 替代 PNGres/ 下的位图转 WebP(见第五节)。
  • VectorDrawable 替代多分辨率 PNG:单色图标尽量用矢量图,一份文件适配所有密度。
  • NDK 过滤 :如果项目里有自编译的 C/C++ 库,ndk { abiFilters } 只保留目标架构。

四、iOS 原生侧优化

4.1 开启编译优化

在 Xcode Target → Build Settings 中:

配置项 推荐值 作用
Optimization Level (Release) -Os(Optimize for Size) 以体积优先优化
Strip Linked Product Yes 去除符号表
Strip Style (Release) All SymbolsNon-Global Symbols 进一步精简符号
Dead Code Stripping Yes(默认) 移除无用代码段
Symbols Hidden by Default Yes 减少导出符号
Generate Debug Symbols (Release) No(符号表交给 dSYM 文件单独保存) 二进制不带调试符号

注意:Yes 关掉调试符号后,务必保存每次发版的 dSYM 文件,否则线上 crash 堆栈无法符号化。接入 Sentry/Bugly 等平台时上传 dSYM。

4.2 App Thinning(交给 App Store 自动做)

上传 App Store 的构建会自动享受:

  • Slicing:按设备只下发对应架构(arm64)和对应分辨率(@2x/@3x)的资源;
  • On-Demand Resources (ODR):非首屏必需的大资源(教学视频、游戏关卡素材)标记为按需下载;
  • 静态库的 Bitcode 重新编译(注:Xcode 14+ 已废弃 Bitcode,不用管)。

Asset Catalog 是前提 :图片必须放进 Assets.xcassets 才能被 slicing,散放在 bundle 里的图片无法按分辨率裁剪。

4.3 只保留 arm64 架构

RN 0.70+ 的项目,Release 构建默认只打 arm64(iPhone 5s 之后全支持)。检查 Build Settings → Architectures,不要遗留 armv7

4.4 精简 Pod 依赖

  • pod install 后检查 Podfile.lock,删掉 JS 侧已经卸载的库(很多人删了 npm 包忘了 pod install 清理)。
  • 对特别大的三方 SDK(推送、统计、IM),评估是否所有功能都用得上,很多 SDK 支持子模块按需引入(pod 'XXX/Core' 而不是整个 pod 'XXX')。

五、资源文件优化(双端通用)

资源往往是体积优化的性价比之王,一个没压缩的启动图可能比整个 JS bundle 还大。

5.1 图片

bash 复制代码
# 批量把 PNG/JPG 转 WebP(平均再省 25%--70%)
cwebp -q 80 input.png -o output.webp

# 无损压 PNG
pngquant --quality=65-80 --ext .png --force *.png
  • RN 原生支持 WebP(Android 原生支持;iOS 需要 react-native-webp-format 或 SDWebImage 支持,新版本 RN 的 iOS 已内置支持)。
  • 大图(背景、Banner)优先 WebP;纯色图标用 SVG(react-native-svg)或 iconfont 字体。
  • 图标用 iconfont:几百个图标只占几十 KB。

5.2 字体

  • 中文字体动辄 10 MB+。如果只为了数字/少量特殊字符,用 字体子集化(subset)只保留用到的字符:
bash 复制代码
pip install fonttools
pyftsubset PingFang.ttf --text-file=used-chars.txt --output-file=PingFang-subset.ttf

几百 KB 即可覆盖常用数字和标点。

5.3 音视频、Lottie

  • 内置视频改为在线加载 + 本地缓存。
  • Lottie JSON 可以用 lottie 压缩工具 或手动删掉冗余字段;超大动画考虑换成序列帧 WebP/视频。

六、依赖治理:最容易被忽视的大头

定期执行:

bash 复制代码
# 找出占用最大的依赖
npx dependency-cruiser --version  # 或用下面这个更直观
npm ls --all | sort

# 检查重复和过期依赖
npx depcheck          # 找未使用的依赖
npm dedupe            # 消除重复版本

常见"重量级"依赖的平替

说明
moment(~300KB,还带一堆 locale) dayjs(~2KB) / date-fns(按需) API 几乎一致
lodash 全量 lodash/debounce 按需引入 或 lodash-es + tree shaking 见 2.3
整个 UI 组件库 按需引入 + babel-plugin-import 检查是否有未用主题被打入
多套图表库 只留一套 很多项目历史遗留 echarts + victory + chart.js 并存
重复的 react-native-vector-icons 字体 include 用到的字体族 默认会把十几套 icon 字体全打进去!

vector-icons 专项(Android):

groovy 复制代码
// android/app/build.gradle 顶部
project.ext.vectoricons = [
    iconFontNames: ['MaterialIcons.ttf', 'FontAwesome.ttf']  // 只留用到的
]
apply from: file("../../node_modules/react-native-vector-icons/fonts.gradle")

iOS 则删掉 Info.plistUIAppFonts 中不用的字体声明,并从 Xcode 资源里移除对应文件。

原生依赖同理

  • 第三方 SDK(推送 × 2 家、统计 × 3 家这种历史遗留)是重灾区,定期梳理下线。
  • 每次升级 RN 版本本身也常常带来引擎层面的瘦身。

七、发版形态:让商店帮你瘦身

Android:AAB(Android App Bundle)

bash 复制代码
cd android
./gradlew bundleRelease   # 产出 app-release.aab

上传到 Google Play 后,Play 会按设备动态生成只含所需架构、语言、分辨率的 APK。用户实际下载体积通常比 universal APK 小 30%--50%。国内渠道(华为、小米、OPPO、vivo、应用宝)也均已支持 AAB 或各家自己的动态分发。

注意:提交 AAB 需要在 Play 后台开启 Play App Signing,妥善保管签名密钥。

iOS:天然享受 App Thinning

只要资源进了 Asset Catalog、按规范出包,App Store 的 slicing 自动生效,无需额外操作。重点看 App Store Connect 里的体积报告验收效果。


八、如何保证功能正常:验证清单

体积优化最大的风险是"压过头把功能压坏了"。以下清单按顺序执行:

8.1 构建期验证

  • Release 构建通过,无 ProGuard/R8 警告(有 warning 不要忽略,逐条确认或加 keep 规则)。
  • bundle 大小对比记录在案(每次发版记录,防止某次依赖升级悄悄膨胀)。

8.2 自动化测试

  • 单元测试全绿(尤其工具类被混淆后,JSON 序列化/反射相关代码)。
  • 核心链路 UI 自动化(Detox / Appium):登录、支付、核心业务流,必须跑 Release 包而不是 Debug 包------很多问题只在混淆/压缩后出现。

8.3 针对每项优化点的专项回归

优化手段 重点回归项
Hermes 全量回归,重点测用到 Proxy、正则、JSON 大数的功能和第三方 SDK
drop_console 确认没有逻辑依赖 console 副作用(少见但存在)
R8 / 混淆 反射调用的类、Gson/Fastjson 序列化的 Model、JNI 回调、推送/支付 SDK 回调
shrinkResources 通过 getIdentifier() 动态加载的资源(最常见踩坑点!)
ABI 拆分 每个渠道包的安装测试,确认没把 armeabi-v7a 设备漏掉
WebP 替换 Android 5.0 以下老设备、iOS 低端机的图片显示
字体子集化 生僻字、特殊符号是否显示为豆腐块 □
删语言资源 系统切换英文/繁体时应用是否正常
依赖替换 moment → dayjs 的时区、本地化格式差异

8.4 灰度与监控兜底

  • 灰度发布:先 1%--5% 用户,观察 24--48 小时。
  • 崩溃监控:Sentry / Bugly / Firebase Crashlytics,务必上传对应版本的 sourcemap(JS)和 dSYM / mapping.txt(原生混淆还原表),否则报错看不懂。
  • 关键指标对比:崩溃率、ANR 率、启动耗时在新包上不应劣化------Hermes + 按需加载通常还会让启动变快。

8.5 一条铁律

每次只改一类优化,验证通过后再叠加下一项。 一口气上十个优化,出了问题根本定位不到是哪个引起的。


九、一张速查表

层面 手段 预估收益 风险
发版形态 AAB / App Store Slicing ⭐⭐⭐⭐⭐
Android ABI 拆分(只留 arm64 + v7a) ⭐⭐⭐⭐⭐
Android minify + shrinkResources + R8 ⭐⭐⭐⭐ 中(反射/动态资源)
Android resConfigs 精简语言 ⭐⭐
JS Hermes 引擎 ⭐⭐⭐ 中(兼容性回归)
JS 去 console + terser 压缩 ⭐⭐
JS 代码分割 / 懒加载 ⭐⭐⭐(首屏)
依赖 moment→dayjs、lodash 按需、去重复依赖 ⭐⭐⭐ 中(API 差异)
依赖 vector-icons 只保留用到的字体 ⭐⭐⭐
资源 PNG→WebP、SVG/iconfont、字体子集 ⭐⭐⭐⭐ 中(老设备兼容)
iOS -Os 优化 + Strip 符号 ⭐⭐ 低(保存 dSYM)
iOS Asset Catalog + ODR ⭐⭐⭐
双端 卸载残留的原生模块/三方 SDK 视情况,可能很大

结语

包体积优化没有银弹,它是一个"测量 → 削减 → 验证 → 再测量"的循环。建议:

  1. APK/IPA 体积bundle 体积 纳入 CI,每次构建自动记录,超标报警;
  2. 每次引入新依赖前,先用 react-native-bundle-visualizer 看一眼它多大;
  3. 每半年做一次依赖大扫除。

做到这三点,你的包体积就永远不会失控。

相关推荐
无糖可可果2 小时前
从零看懂 JWT 登录鉴权:一个 React Demo 的完整拆解
前端·javascript
BreezeJiang2 小时前
JWT 登录不是存个 token:React 鉴权真正要闭合的是一条数据流
javascript·react.js·axios
四千岁2 小时前
稀疏向量BM25Retriever不支持中文怎么办?jieba来帮忙
前端·javascript·后端
这是个栗子2 小时前
【JS代码分析】前端鉴权基础:Token 的本地存储与状态重置实践
开发语言·前端·javascript
半个落月2 小时前
React 性能优化入门:用 memo 避免无关的子组件重复渲染
前端·react.js
界面开发小八哥3 小时前
界面控件DevExpress Blazor v26.1新版亮点 - TreeList & 数据编辑器
javascript·编辑器·.net·界面控件·blazor·devexpress·用户界面
风月说与山鬼3 小时前
二、uni-app入口文件(main.js)
javascript·uni-app
mCell12 小时前
Lua 编程入门:从基础语法到元表
javascript·算法·lua
伟大的兔神13 小时前
我做了一个本地优先的 AI 图片工作台:Loomora v1.0.0 正式发布
前端·javascript·vue.js