首页
/ Expo 仓库内的本地开发沙箱应用:用 apps/sandbox 快速验证你在改的 SDK 模块

Expo 仓库内的本地开发沙箱应用:用 apps/sandbox 快速验证你在改的 SDK 模块

2026-09-07 19:56:57作者:蔡丛锟

在 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 屏蔽目录内除 .envpackage.json 外的所有内容
assets/ 提交的应用图标(icon.png)与启动屏(splash.png)

README 的核心使用建议可以概括为两句话:

  1. 需要快速测试你正在本地开发的内容时,用它;
  2. 目录内除已提交文件外全部被忽略,你需要在本地自行添加 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-routerpackages/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 源码的关键。expoexpo-dev-clientexpo-linkingexpo-routerexpo-splash-screenbabel-preset-expo 全部走本地副本;根 package.jsonresolutions 里也对 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 的 imageWidthresizeModebackgroundColor 都会作用于原生启动屏的生成;
  • 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

它先忽略目录内的一切,再强制放行 .envpackage.json。配合已提交的 README、app.json 等文件,效果是:你在本地添加的 App.jsApp.tsx、路由目录以及任何临时文件都不会被 Git 追踪,避免把私人试验代码误提交进仓库——这正是「Everything in this folder other than the already committed files is ignored」这句话的落地实现。

如何本地使用沙箱:补上 App 入口并启动

根据 README,唯一缺少的运行时文件是应用入口。推荐做法是从 blank 模板复制。仓库内可用的 blank 模板有两份:

例如从 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-expoapps/expo-go:覆盖几乎所有 Expo 模块的综合验证载体(后者是 Expo Go 客户端的源码),用于大范围回归;
  • apps/test-suiteapps/router-e2e:面向自动化测试(单元、端到端)的工程;
  • apps/sandbox交互式人工冒烟测试,胜在轻量与隔离,专为「开发中的模块 + 本地副本依赖」这个组合而生。

从源码结构看,可以合理推断沙箱面向的是 Exo 贡献者在提 PR 之前的最后一公里:它让开发者不必为每个小实验都新建完整工程,也不必担心把临时代码混入正式应用。

小结

apps/sandbox/README.md 篇幅极短,但承载了仓库内一个高频开发工作流:在一份不提交业务代码、自动忽略本地临时文件的最小工程里,通过 workspace 协议把正在开发的 Expo SDK 与模块直接跑起来。只需四步即可上手——理解目录内已提交的配置文件、从 templates/expo-template-blank 复制一份 App、写入你的测试代码、用 pnpm start 系列命令启动。对于任何需要快速验证 Expo 源码本地改动的人来说,这是仓库中最轻量、最低摩擦的试验场。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388