Expo 仓库内的本地开发沙箱应用:用 apps/sandbox 快速验证你在改的 SDK 模块
在 Expo 这个庞大的单体仓库(monorepo)中,expo 及其数十个 universal modules(如 expo-router、expo-splash-screen、expo-linking 等)都以「本地工作副本」的形式随仓库一起维护。直接在这些包上改动代码后,很难在隔离环境里快速验证效果。本文介绍的 apps/sandbox 正是为此准备的本地开发沙箱应用:它不提交任何业务代码,只提供一个与仓库本地工作副本硬链接的最小工程骨架,供你在开发/调试 SDK 功能时即兴测试。读完本文,你将掌握该沙箱的结构、它与「Yarn/Pnpm workspaces + workspace:* 协议」的关系、如何用 blank 模板补上缺失的 App 入口并跑起来,以及为什么它是仓库内做本地改动验证的高性价比选择。
沙箱应用是什么
按 apps/sandbox/README.md 的说明,这是一份「blank app(空应用)」,通过 package workspaces 机制直接使用当前仓库本地工作副本中的 expo-sdk 与全部 universal modules,而不是 npm registry 上发布的预编译版本。
该目录当前只提交了基础设施文件,实际结构如下:
| 文件 | 作用 |
|---|---|
| package.json | 声明包名为 @expo/sandbox、入口与启动脚本,并声明依赖 |
| app.json | Expo 应用配置(名称、slug、scheme、SDK 版本、插件等) |
| babel.config.js | Babel 配置,仅启用 babel-preset-expo |
| metro.config.js | Metro 打包配置,复用 expo/metro-config 的默认配置 |
| .gitignore | 屏蔽目录内除 .env、package.json 外的所有内容 |
| assets/ | 提交的应用图标(icon.png)与启动屏(splash.png) |
README 的核心使用建议可以概括为两句话:
- 需要快速测试你正在本地开发的内容时,用它;
- 目录内除已提交文件外全部被忽略,你需要在本地自行添加
App.js才能使用——最省事的办法是从 blank 项目模板里复制一份。
本地开发场景:为什么需要它
Expo 仓库的根 package.json 通过 workspaces 字段把 apps/*、packages/*、packages/@expo/* 等全部纳入同一个 workspace,而根目录的 pnpm-workspace.yaml 进一步把沙箱、bare-expo 等示例应用与各个 SDK 包统一管理。这意味着:
- 你在
packages/expo(即expo-sdk)里改了源码,沙箱里引用的expo会直接指向这份本地修改后的副本; - 你在
packages/expo-router、packages/expo-splash-screen等模块中新增 API,沙箱立即就能 import 到。
这与发布到 npm 的正式包体验不同:正式 App 会锁定依赖版本,改动需要发版、升级依赖才能验证。而沙箱通过 workspace 内联解析,源码改动即所测即所得,非常适合:
- 开发新 API 或修改现有模块行为时的冒烟测试;
- 复现并排查只在特定依赖组合下出现的集成问题;
- 在提交 PR 前,用最小工程验证改动不会破坏基础启动链路。
值得指出的是,仓库内另一个体量更大的验证载体是 apps/bare-expo,它同样使用 workspace:* 指向本地模块,但其定位是覆盖全部模块的综合性「bare 工程」;相比之下沙箱刻意保持最小化,避免每次测试都被庞大的依赖树拖慢。
目录里有什么:沙箱的配置解剖
沙箱虽小,却五脏俱全。逐个看这些已提交的配置文件,能帮你理解它在仓库中的精确角色。
package.json:入口与依赖
apps/sandbox/package.json 定义了沙箱的元信息:
{
"name": "@expo/sandbox",
"version": "0.0.0",
"main": "expo-router/entry",
"scripts": {
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios"
},
"dependencies": {
"@react-navigation/bottom-tabs": "^7.15.5",
"@react-navigation/native": "^7.1.33",
"expo": "workspace:*",
"expo-dev-client": "workspace:*",
"expo-linking": "workspace:*",
"expo-router": "workspace:*",
"expo-splash-screen": "workspace:*",
"react": "19.2.3",
"react-native": "0.87.0",
"react-native-safe-area-context": "5.7.0",
"react-native-screens": "4.27.0"
},
"devDependencies": {
"babel-preset-expo": "workspace:*"
},
"private": true
}
要点:
workspace:*协议表示「使用本 workspace 中的同名包」,这正是沙箱能吃到本地 SDK 源码的关键。expo、expo-dev-client、expo-linking、expo-router、expo-splash-screen与babel-preset-expo全部走本地副本;根 package.json 的resolutions里也对react-native(0.87.0)等做了统一收口。react/react-native/react-native-screens等依赖与根仓库、默认模板的版本保持一致,避免版本漂移带来的复现偏差。"private": true表明该包仅供仓库内部使用,不会被发布。"main": "expo-router/entry"表示入口指向 expo-router 的 entry——即使你要测试的只是一个裸组件,这个入口也会先建立 expo-router 的运行时环境。
app.json:Expo 运行时配置
apps/sandbox/app.json 中几个值得留意的字段:
{
"expo": {
"name": "sandbox",
"slug": "sandbox",
"sdkVersion": "UNVERSIONED",
"scheme": "sandbox",
...
"plugins": [
["expo-router", { "origin": "https://naviloop.netlify.app/", "asyncRoutes": true }],
["expo-splash-screen", { "image": "./assets/splash.png", "imageWidth": 200, "resizeMode": "contain", "backgroundColor": "#ffffff" }]
],
"web": { "bundler": "metro", "output": "server" }
}
}
sdkVersion: "UNVERSIONED"是仓库内开发 App 的常见写法,表示跟随当前源码的未发布 SDK 版本,而不是某个已发布的固定 SDK;scheme: "sandbox"用于自定义 URL scheme 与 deep link 测试;plugins挂载了 expo-router 与 expo-splash-screen 的 config plugin,其中 splash 的imageWidth、resizeMode、backgroundColor都会作用于原生启动屏的生成;web.output: "server"表明该沙箱面向 web 时按 SSR/server 输出模式打包,说明它也可以用来验证expo-router在 web 端的服务端渲染行为。
babel 与 metro:复用仓库标准工具链
babel.config.js 只是简单地缓存并启用 babel-preset-expo:
module.exports = function (api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],
};
};
metro.config.js 基于 expo/metro-config 的默认配置构建,并额外做了两点针对仓库内开发环境的优化:
const { getDefaultConfig } = require('expo/metro-config');
const config = getDefaultConfig(__dirname, { isCSSEnabled: true });
...
// 关闭 Babel 对 .babelrc 的查找,减少 Babel 配置加载,加快转译冷启动
config.transformer.enableBabelRCLookup = false;
module.exports = config;
isCSSEnabled: true让 Metro 支持 CSS 处理,便于测试 DOM/Web 相关能力;- 关闭
enableBabelRCLookup可以避免 Metro 在 monorepo 内反复向上查找 Babel 配置文件,显著缩短首包转换时间。
.gitignore:保证沙箱「随用随弃」
apps/sandbox/.gitignore 内容极短但语义明确:
# ignore everything and force add a few important files
*
!.env
!package.json
它先忽略目录内的一切,再强制放行 .env 与 package.json。配合已提交的 README、app.json 等文件,效果是:你在本地添加的 App.js、App.tsx、路由目录以及任何临时文件都不会被 Git 追踪,避免把私人试验代码误提交进仓库——这正是「Everything in this folder other than the already committed files is ignored」这句话的落地实现。
如何本地使用沙箱:补上 App 入口并启动
根据 README,唯一缺少的运行时文件是应用入口。推荐做法是从 blank 模板复制。仓库内可用的 blank 模板有两份:
- templates/expo-template-blank/App.js(JavaScript 版);
- templates/expo-template-blank-typescript/App.tsx(TypeScript 版)。
例如从 JS 模板复制时,会得到这样的根组件:
import { StatusBar } from 'expo-status-bar';
import { StyleSheet, Text, View } from 'react-native';
export default function App() {
return (
<View style={styles.container}>
<Text>Open up App.js to start working on your app!</Text>
<StatusBar style="auto" />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
随后把这段 JSX 逐步替换为你正在开发的模块测试代码即可。若模板同时提供 index.js(调用 registerRootComponent(App),见 templates/expo-template-blank/index.js),理论上也要一并复制;不过由于本沙箱 main 指向 expo-router/entry,部分场景下直接提供 App 导出也能被 expo-router 运行时接管,具体以你要验证的路由/非路由行为为准。
补好入口后,在仓库根目录安装依赖并进入沙箱目录运行(仓库为只读资源,这里仅说明运行方式,不涉及任何修改提交):
# 1. 在仓库根目录安装一次依赖,让 workspace:* 解析到本地包
pnpm install
# 2. 进入沙箱目录启动
cd apps/sandbox
pnpm start # 通用启动
pnpm android # 启动 Android(expo start --android)
pnpm ios # 启动 iOS(expo start --ios)
启动后 Metro 会把你对本地模块的改动即时编译进 bundle,结合 Expo Go、开发构建或直接模拟器即可验证效果。由于 .env 被 .gitignore 显式放行,需要注入本地开发用环境变量时,在沙箱目录放一份 .env 即可。
定位与边界:它和其他示例 App 的分工
在 apps/ 目录下存在多个职责不同的测试应用,理解分工能避免用错工具:
- apps/bare-expo 与 apps/expo-go:覆盖几乎所有 Expo 模块的综合验证载体(后者是 Expo Go 客户端的源码),用于大范围回归;
- apps/test-suite、apps/router-e2e:面向自动化测试(单元、端到端)的工程;
- apps/sandbox:交互式人工冒烟测试,胜在轻量与隔离,专为「开发中的模块 + 本地副本依赖」这个组合而生。
从源码结构看,可以合理推断沙箱面向的是 Exo 贡献者在提 PR 之前的最后一公里:它让开发者不必为每个小实验都新建完整工程,也不必担心把临时代码混入正式应用。
小结
apps/sandbox/README.md 篇幅极短,但承载了仓库内一个高频开发工作流:在一份不提交业务代码、自动忽略本地临时文件的最小工程里,通过 workspace 协议把正在开发的 Expo SDK 与模块直接跑起来。只需四步即可上手——理解目录内已提交的配置文件、从 templates/expo-template-blank 复制一份 App、写入你的测试代码、用 pnpm start 系列命令启动。对于任何需要快速验证 Expo 源码本地改动的人来说,这是仓库中最轻量、最低摩擦的试验场。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00