Expo 开源仓库贡献指南:从环境搭建、SDK 包编辑到测试与文档的完整贡献流程
2026/9/6 21:56:08 网站建设 项目流程

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)统一编排buildtypechecklinttest等任务,且配置了共享远程缓存(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 进行贡献。

基础步骤如下(原文档的完整步骤序列):

  1. 获取代码。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,跳过大部分分支与历史。
  2. 安装 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。
  3. 安装 Ruby 3.3 或更高版本。macOS 自带的是 ruby 2.6,本仓库不支持,可用brew install ruby@3.3
  4. 安装 Node LTS
  5. 部分脚本需要 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/Home

ANDROID_SDK_ROOT需要被设置,或者在你所操作的 native 项目的android文件夹下通过local.properties配置。

可选:用 ccache 加速 Android 原生构建

ccache 缓存 C/C++ 编译结果,当源码文件未变化时,原生代码的重建几乎是瞬时的。配置步骤:

  1. 安装:brew install ccache

  2. ~/.zshrc(或~/.bashrc)中添加:

    export CMAKE_C_COMPILER_LAUNCHER="ccache" export CMAKE_CXX_COMPILER_LAUNCHER="ccache"
  3. 启用预编译头(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)。

验证原生安装是否成功

  1. 进入 bare 沙盒项目:cd apps/bare-expo
  2. 在任意原生平台上运行项目:
    • iOS:pnpm ios
    • Android:pnpm android
    • 若在 Linux 上工作,需把TERMINAL环境变量设置为你的终端应用(例如export TERMINAL="konsole")。
  3. 此时你运行的就是通过bare-expo承载的test-suite应用,可以开始对 SDK 包进行改动。

从 apps/bare-expo/package.json 可以看出这些脚本的真实形态:iosandroid均以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/目录中的项目,因此你可以原地编辑并立即在运行中的应用中看到变化。标准工作流:

  1. 进入要编辑的包,例如cd packages/expo-constants
  2. 编辑后编译包的 TypeScript:pnpm build(若该包没有这个脚本可跳过)
  3. 在该包的src/目录中修改代码
  4. 通过bare-expo在模拟器或真机上验证改动:
    • 添加或修改一个以目标 API 命名的测试文件,例如apps/test-suite/tests/Constants.js
    • 要验证原生(native)层改动,需用apps/bare-expo工程运行test-suitepnpm <android | ios>
    • 如果只改了 JavaScript,也可以直接在apps/test-suite项目中用expo start运行
    • 运行完整测试套件:pnpm test:<android | ios>
  5. 原生代码既可以在packages/目录下的对应包内直接编辑,也可以打开bare-expo的原生工程:
    • cd apps/bare-expo
    • Android Studio:pnpm edit:android
    • Xcode:pnpm edit:ios
    • 任何原生改动之后必须重新构建(rebuild)native 工程
  6. (可选)包的文档部分由源码生成,运行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 srcdepscheckexpo-module depschecklint使用 oxlint——这些统一行为来自下文提到的expo-module-scripts包。

通用包脚本(Common package scripts)

packages/下几乎每个包都暴露同一组 npm 脚本,由 Turborepo 在 monorepo 层面统一编排。编译产物build/不会提交到 Git(在.gitignore中);Turborepo 按需构建并本地 + 远程缓存结果,避免git pull/git checkout后被迫重建所有包。这与 turbo.json 中的任务定义一一对应:build任务声明了build/**等输出产物并依赖上游包的^buildlint/format/test则关闭缓存(cache: false)。

脚本作用
build编译src/build/
typechecktsc对包做类型检查
test运行该包的 Jest 单元测试
lint对该包做 lint,可传--fix自动修复
format格式化该包,可传--check只检查不修改
depscheck校验包声明的依赖与其实际 import 是否一致

两种运行方式:

  • 从仓库根目录pnpm <script>(如pnpm buildpnpm testpnpm lintpnpm formatpnpm 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/DevSupportbare-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)。补上你发现的缺失测试是快速熟悉项目的好方式。

单元测试

  1. 在对应包的src/__tests__目录中为功能创建测试(若目录不存在就创建,文件扩展名使用*-test.ts*-test.tsx)。
  2. 所有新增的桥接(bridged)原生函数必须加入 jest-expo 包以确保被 mock。仓库为此提供了专门的工具和指南:Generating Jest Mocks。
  3. pnpm test运行测试,并确保覆盖 iOS、Android 和 web 各平台。若某功能不支持某平台,可将测试放入带平台扩展名的文件中排除,例如.test.ios.ts.test.native.ts.test.web.ts等。
  4. 也可以按住X选择要单独测试的平台,逐个平台运行。

E2E 测试

  1. 把测试写在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名称数组(如ConstantsCryptoSQLite等),Maestro 测试流程即由这份列表生成,添加或移除条目即可,同时该测试必须在TestModules.ts中注册。
  2. bare-expo目录本地运行:pnpm test:androidpnpm test:ios
    • 务必本地先测:native CI 测试可能脆弱、耗时长,失败时排查也很麻烦。
  3. 尽量让功能在尽可能多的平台上运行。

更新文档

Expo 文档基于 Next.js 构建,位于 docs 目录,更多细节见 docs/README.md。要点(TL;DR):

  • 运行 docs 的 pnpm 命令要求特定版本的 Node,该版本定义在 docs/package.json 的packageManager/engines字段中(当前仓库要求 Node 22.13.1 及以上,且 docs 包本身声明了更高的 Node 版本要求,以该文件为准)。
  • 操作步骤:
    1. 进入docs目录并运行pnpm install
    2. pnpm dev启动项目(确保没有其他服务占用3002端口——dev脚本即为next dev -p 3002,并先执行generate-static-resources);
    3. 进入要编辑的文档:cd docs/pages/
    4. 如果你更新的是某个旧版本,确保对应的 API 文档改动被拷贝到docs/pages/versions/unversioned/
    5. 包的 API 文档由源码生成。重新生成:et generate-docs-api-data -p <package-name>(面向下一个 SDK 版本),或et generate-docs-api-data -p <package-name> -s <number>(面向指定 SDK 版本)。

编写 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 buildpnpm typecheckpnpm testpnpm lintpnpm format),参见上文通用包脚本;
  • 运行pnpm lint --fixpnpm 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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询