Expo 开源仓库贡献指南:从环境搭建、SDK 包编辑到测试与文档的完整贡献流程
【免费下载链接】expoAn open-source framework for making universal native apps with React. Expo runs on Android, iOS, and the web.项目地址: https://gitcode.com/GitHub_Trending/ex/expo
本文基于 Expo 仓库根目录的 CONTRIBUTING.md 编写,面向希望向 Expo SDK 提交代码的外部贡献者与团队成员。文章完整覆盖仓库贡献的各个环节:开发环境搭建(direnv、Ruby、JDK、ccache 等)、基于apps/bare-expo沙盒项目的 SDK 包编辑工作流、单元测试与 E2E 测试的编写与运行、文档更新规则,以及提交前的检查清单,并结合仓库源码补充了各命令背后的实际实现。
贡献范围与开发工作流的核心选型
Expo 仓库目前接受针对packages/、docs/、templates/、guides/、apps/目录以及 markdown 文件的 PR。整个仓库是一个 pnpm workspace 单仓(monorepo),根目录 package.json 通过workspaces.packages声明了apps/*、packages/*、packages/@expo/*等工作区成员,并用 Turborepo(根目录 turbo.json)统一编排build、typecheck、lint、test等任务,且配置了共享远程缓存(turbo.json中的remoteCache段),因此git pull或切换分支后通常不需要从头重新编译每个包。
关键选型:SDK 开发请使用apps/bare-expo,而不是 Expo Go(apps/expo-go)。原因有两点:
- Expo Go 应用本身较难搭建,且依赖 API token;
- apps/bare-expo 项目链接了
packages/目录下的绝大部分 Expo SDK 依赖,能够直接运行 apps/test-suite 与 apps/native-component-list 两个测试/演示应用,方便浏览 SDK 组件与 API、为任意 SDK 包编写并运行 iOS / Android 的 E2E 测试。单元测试则直接写在 SDK 包内部。代码推送到远端后,CI 会运行该项目并在 Android/iOS 上执行测试,结果回显到你的 PR 上。
二者的关系是:bare-expo是一个 bare React Native 应用,为了能够运行apps/目录下的项目,它链接了packages/下的全部 Expo SDK 依赖;它导入test-suite应用的根组件并作为自己的根组件使用。test-suite是一个带有少量自定义代码的 Expo 应用,被改造成了测试运行器(test runner);如果在apps/test-suite目录里直接运行expo start,也可以把该项目加载到 Expo Go 中。
此外,apps/native-component-list 中内置了大量人工冒烟测试(manual smoke tests),非常适合需要真机物理交互的演示或测试场景——当你测试 UI 组件交互、或者某个行为很难自动化但手动交互即可验证时,它是首选工具。
下载与基础环境搭建
注意:本仓库的开发环境不支持 Windows,Windows 用户必须使用 WSL 进行贡献。
基础步骤如下(原文档的完整步骤序列):
- 获取代码。Expo 团队成员直接克隆仓库;外部贡献者先将仓库 fork 到自己的账号再克隆到本地,并添加上游远端:
git remote add upstream git@github.com:expo/expo.git。若希望加速克隆,可用git clone --depth 1 --single-branch --branch main git@github.com:expo/expo.git,跳过大部分分支与历史。 - 安装 direnv。macOS 上执行
brew install direnv,并记得把 shell hook 安装到你的 shell profile 中。direnv 对本仓库尤为重要:根目录 .envrc 会在进入仓库时自动加载环境,其中:PATH_add bin把仓库根下的bin/加入 PATH(et等工具即来自这里);- 导出
EXPO_USE_SOURCE=1,强制所有 Expo 模块从源码编译; - 设置
CCACHE_BASEDIR为当前目录,使 ccache 缓存可跨 git worktree 共享(见下文 Android 加速节); - 校验 Ruby 版本(
use_ruby "3.3" "3.4" "4.0"),不在允许列表内会直接报错退出; - 从
secrets/expotools.env加载 expotools 密钥(如存在),并安装 scripts/git-hooks 下的 Git hooks。
- 安装 Ruby 3.3 或更高版本。macOS 自带的是 ruby 2.6,本仓库不支持,可用
brew install ruby@3.3。 - 安装 Node LTS。
- 部分脚本需要 Bun。大多数任务用不到,可按需安装。
Android 环境配置
如果计划贡献 Android 相关代码,在仓库根目录运行:
pnpm run setup:native从根 package.json 可见,该脚本实际是./scripts/download-dependencies.sh --native && ./scripts/setup-react-android.sh的组合。查看 scripts/download-dependencies.sh 可知它依次完成:
- 前置检查 node、npm、direnv 是否安装(缺失则报错退出);
git submodule update --init拉取react-native等子模块;- 确保 pnpm 已安装(缺失时通过
npm install -g pnpm补装); - 执行
pnpm install下载全部 Node 依赖(并确保你的电脑满足 React Native 环境要求,如缺失会安装 Android NDK)。
JDK:推荐使用 JDK 17(如 zulu17):
brew tap homebrew/cask-versions brew install --cask zulu@17安装后在~/.bash_profile(ZSH 用户为~/.zshrc)中设置:
export JAVA_HOME=/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/HomeANDROID_SDK_ROOT需要被设置,或者在你所操作的 native 项目的android文件夹下通过local.properties配置。
可选:用 ccache 加速 Android 原生构建
ccache 缓存 C/C++ 编译结果,当源码文件未变化时,原生代码的重建几乎是瞬时的。配置步骤:
安装:
brew install ccache在
~/.zshrc(或~/.bashrc)中添加:export CMAKE_C_COMPILER_LAUNCHER="ccache" export CMAKE_CXX_COMPILER_LAUNCHER="ccache"启用预编译头(precompiled header)支持(
expo-modules-core等模块需要):ccache -o sloppiness=pch_defines,time_macros
仓库的 .envrc 会通过 direnv 自动设置CCACHE_BASEDIR,因此无需额外配置即可在多个 git worktree 之间共享缓存。
iOS 环境配置
如果你要开发 iOS 项目:
- 确保机器上安装了Ruby 3.3(macOS 自带的 ruby 2.6 不受支持,Homebrew 用户执行
brew install ruby@3.3); - 安装最新稳定版 Xcode 及 Xcode 命令行工具(command line tools)。
验证原生安装是否成功
- 进入 bare 沙盒项目:
cd apps/bare-expo - 在任意原生平台上运行项目:
- iOS:
pnpm ios - Android:
pnpm android - 若在 Linux 上工作,需把
TERMINAL环境变量设置为你的终端应用(例如export TERMINAL="konsole")。
- iOS:
- 此时你运行的就是通过
bare-expo承载的test-suite应用,可以开始对 SDK 包进行改动。
从 apps/bare-expo/package.json 可以看出这些脚本的真实形态:ios与android均以NODE_ENV="development"调用 scripts/start-simulator.sh 或 scripts/start-emulator.sh;而test:ios/test:android则切换为NODE_ENV="test"。以start-simulator.sh为例,开发模式下它会先执行setup-ios-project.sh再运行npx expo run:ios;测试模式下则自动检测/安装 Maestro 与 idb-companion,必要时先构建BareExpo.app,然后执行 E2E 测试流程——这正是下文 E2E 测试章节的运行入口。若上述流程无法正常工作,仓库建议开一个 issue 反馈。
编辑 SDK 包
所有 Expo SDK 包都位于packages/目录,并且自动链接到apps/目录中的项目,因此你可以原地编辑并立即在运行中的应用中看到变化。标准工作流:
- 进入要编辑的包,例如
cd packages/expo-constants - 编辑后编译包的 TypeScript:
pnpm build(若该包没有这个脚本可跳过) - 在该包的
src/目录中修改代码 - 通过
bare-expo在模拟器或真机上验证改动:- 添加或修改一个以目标 API 命名的测试文件,例如
apps/test-suite/tests/Constants.js - 要验证原生(native)层改动,需用
apps/bare-expo工程运行test-suite:pnpm <android | ios> - 如果只改了 JavaScript,也可以直接在
apps/test-suite项目中用expo start运行 - 运行完整测试套件:
pnpm test:<android | ios>
- 添加或修改一个以目标 API 命名的测试文件,例如
- 原生代码既可以在
packages/目录下的对应包内直接编辑,也可以打开bare-expo的原生工程:cd apps/bare-expo- Android Studio:
pnpm edit:android - Xcode:
pnpm edit:ios - 任何原生改动之后必须重新构建(rebuild)native 工程
- (可选)包的文档部分由源码生成,运行
et generate-docs-api-data -p <package-name>重新生成文档(et是仓库内 tools 目录提供的 expotools CLI,对应实现见 tools/src/commands/GenerateDocsAPIData.ts)。
以 packages/expo-constants/package.json 为例,可以看到build脚本实际是expo-build src,depscheck是expo-module depscheck,lint使用 oxlint——这些统一行为来自下文提到的expo-module-scripts包。
通用包脚本(Common package scripts)
packages/下几乎每个包都暴露同一组 npm 脚本,由 Turborepo 在 monorepo 层面统一编排。编译产物build/不会提交到 Git(在.gitignore中);Turborepo 按需构建并本地 + 远程缓存结果,避免git pull/git checkout后被迫重建所有包。这与 turbo.json 中的任务定义一一对应:build任务声明了build/**等输出产物并依赖上游包的^build,lint/format/test则关闭缓存(cache: false)。
| 脚本 | 作用 |
|---|---|
build | 编译src/→build/ |
typecheck | 用tsc对包做类型检查 |
test | 运行该包的 Jest 单元测试 |
lint | 对该包做 lint,可传--fix自动修复 |
format | 格式化该包,可传--check只检查不修改 |
depscheck | 校验包声明的依赖与其实际 import 是否一致 |
两种运行方式:
- 从仓库根目录
pnpm <script>(如pnpm build、pnpm test、pnpm lint、pnpm format、pnpm typecheck)。这会触发turbo <task>,在整个工作区范围内按依赖图和缓存运行脚本; - 从单个包目录
pnpm run <script>(如cd packages/expo-constants && pnpm run test),只运行该包的脚本。
对于“我的改动是否通过了构建、类型检查、lint 和测试”的一次性验证,跨包使用et check-packages <...packages>(实现见 tools/src/commands/CheckPackages.ts),它运行与 CI 相同的 Turborepo 任务图。
如何找到可做的任务
如果你暂时没有目标,最好的入手点是带有 "Issue accepted" 标签的 open issues。另外注意:仓库一般不接受仅升级原生依赖版本的 PR——这类升级由 Expo 团队在每个 SDK 版本发布流程中统一处理,因为引入新版本需要了解相当多的 Expo Go 上下文。
代码风格
所有模块应遵循以下风格指南:
- Expo Module Infrastructure
- Expo JS Style Guide(大部分规则同样适用于 TypeScript)
- Expo Swift Style Guide
- Updating Changelogs
进阶提示(Extra Credit)
- React Native dev tools 目前在仓库的 RN fork 中处于禁用状态(对应 issue #5602)。可以克隆一份独立于本仓库的 React Native,把其
react-native/React/DevSupport目录内容复制到react-native-lab/react-native/React/DevSupport(bare-expo的 package.json 中也提供了sync:tools脚本做类似同步)。这样只能启用 shake 手势,CMD+R 暂时仍不可用。 - 仓库使用的是
react-native的 fork,位于 react-native-lab/react-native(通过 git submodule 拉取)。你可以在这里做修改或 cherry-pick;该 fork 与package.json中的react-native版本只保持最小必要的偏离。 - 仓库使用一套统一的基础 Bash 脚本与配置 expo-module-scripts,保证 TypeScript、Babel、Jest 等工具链在所有包中行为一致。
测试你的改动
PR 的Test Plan部分需要写清楚你如何测试了你的改动。
让改动被合并的最好方式就是为它构建良好的测试。仓库有三类测试:单元测试、自动化 E2E 测试、演示(demo)。补上你发现的缺失测试是快速熟悉项目的好方式。
单元测试
- 在对应包的
src/__tests__目录中为功能创建测试(若目录不存在就创建,文件扩展名使用*-test.ts或*-test.tsx)。 - 所有新增的桥接(bridged)原生函数必须加入 jest-expo 包以确保被 mock。仓库为此提供了专门的工具和指南:Generating Jest Mocks。
- 用
pnpm test运行测试,并确保覆盖 iOS、Android 和 web 各平台。若某功能不支持某平台,可将测试放入带平台扩展名的文件中排除,例如.test.ios.ts、.test.native.ts、.test.web.ts等。 - 也可以按住X选择要单独测试的平台,逐个平台运行。
E2E 测试
- 把测试写在
apps/test-suite/tests目录中:- 这些测试运行在 Android/iOS 客户端上,基于一个功能并不完整的 Jasmine 版本,因此快照测试等特殊功能不可用;
- 新建的测试文件务必在 apps/test-suite/TestModules.ts 中注册,应用才能运行它(测试辅助函数位于 apps/test-suite/TestUtils.js);
- 若新测试文件应能从
bare-expo自动化测试中自动运行,将其加入 apps/bare-expo/e2e/TestSuite-test.native.js。从源码可以看到,该文件导出一个TESTS名称数组(如Constants、Crypto、SQLite等),Maestro 测试流程即由这份列表生成,添加或移除条目即可,同时该测试必须在TestModules.ts中注册。
- 在
bare-expo目录本地运行:pnpm test:android或pnpm test:ios。- 务必本地先测:native CI 测试可能脆弱、耗时长,失败时排查也很麻烦。
- 尽量让功能在尽可能多的平台上运行。
更新文档
Expo 文档基于 Next.js 构建,位于 docs 目录,更多细节见 docs/README.md。要点(TL;DR):
- 运行 docs 的 pnpm 命令要求特定版本的 Node,该版本定义在 docs/package.json 的
packageManager/engines字段中(当前仓库要求 Node 22.13.1 及以上,且 docs 包本身声明了更高的 Node 版本要求,以该文件为准)。 - 操作步骤:
- 进入docs目录并运行
pnpm install; - 用
pnpm dev启动项目(确保没有其他服务占用3002端口——dev脚本即为next dev -p 3002,并先执行generate-static-resources); - 进入要编辑的文档:
cd docs/pages/; - 如果你更新的是某个旧版本,确保对应的 API 文档改动被拷贝到
docs/pages/versions/unversioned/; - 包的 API 文档由源码生成。重新生成:
et generate-docs-api-data -p <package-name>(面向下一个 SDK 版本),或et generate-docs-api-data -p <package-name> -s <number>(面向指定 SDK 版本)。
- 进入docs目录并运行
编写 Commit Message
Commit message 最有用的格式是[platform][api] Title。例如修复了expo-video包在 iOS 上的一个 bug,可以写:
[ios][video] Fixed black screen bug that appears on older devices提交前检查清单
- 记得在改动的包的
CHANGELOG.md中为任何用户可见的改动添加简明描述;若改动不涉及任何包,则写入 根目录 CHANGELOG.md。这对破坏性变更(breaking changes)尤其重要。
改动了packages/中的内容时
- 对你改动的包运行
et check-packages <...packages>(等价于pnpm build、pnpm typecheck、pnpm test、pnpm lint、pnpm format),参见上文通用包脚本; - 运行
pnpm lint --fix和pnpm format修复代码格式,并确认两个命令都能无错误、无警告地通过; - (可选)包的文档部分由源码生成,运行
et generate-docs-api-data -p <package-name>重新生成文档; - 删除所有
console.log和被注释掉的代码块。
编辑了 docs 目录时
- 针对当前 SDK 版本的文档改动,必须同步到 unversioned 副本。当前文档 SDK 版本定义在 docs/package.json 中,版本化工作流描述在 docs/README.md。示例:
- 你修复了
docs/pages/versions/vXX.0.0/sdk/app-auth.md中的拼写错误; - 则需确保把该改动同步到
docs/pages/versions/unversioned/sdk/app-auth.md。
- 你修复了
- 无需本地运行 docs 测试。只需确保你加入的链接没有断链、格式正确,并且改动符合 Expo Documentation Writing Style Guide。
提速技巧(Extra Credit)
CI 测试在你未改动某些目录时会提前结束。如果你想更快拿到结果,应该把docs目录的改动单独放在一个 PR 中,其余改动放在另一个 PR 中。
【免费下载链接】expoAn open-source framework for making universal native apps with React. Expo runs on Android, iOS, and the web.项目地址: https://gitcode.com/GitHub_Trending/ex/expo
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考