使用 Turborepo 构建 React Native Web 跨端 Monorepo:Expo + Next.js + 共享 UI 全指南
【免费下载链接】turboBuild system optimized for JavaScript and TypeScript, written in Rust项目地址: https://gitcode.com/gh_mirrors/tu/turbo
导读
本指南基于 Turborepo 官方仓库中的with-react-native-web社区示例,系统讲解如何在同一个 monorepo 中同时管理 React Native(Expo)原生应用与 React Native Web(Next.js)Web 应用,并借助共享的 React Native 组件库实现真正的"一次编写、多端运行"。读完本文,你将掌握该示例的完整目录结构、每个应用与包的职责划分、Turbo 任务的管道配置方式,以及如何从零创建和运行这套跨端工程。
示例概览:一个仓库,两端应用,一套组件
with-react-native-web是一个由社区维护(maintainedByCoreTeam: false,见 meta.json)的 Turborepo 示例,核心思路是:
native:基于 Expo 的 React Native 应用,用于 iOS / Android 原生开发;web:基于 Next.js 的 Web 应用,通过 react-native-web 在浏览器中渲染 React Native 组件;@repo/ui:一个存根(stub)React Native 组件库,被web与native两个应用共享;@repo/typescript-config:整个 monorepo 统一使用的tsconfig.json配置集合。
示例中所有应用与包均 100% 使用 TypeScript 编写(见 README),从而实现同一套 UI 代码在原生端与 Web 端的一致体验。
使用 create-turbo 快速初始化
与仓库内其他示例一致,创建本项目只需一条命令:
npx create-turbo@latest -e with-react-native-webcreate-turbo会拉取该示例的模板并基于当前 Turbo 版本初始化。初始化完成后,仓库根目录的 package.json 使用 Yarn Workspaces(apps/*与packages/*)组织包,并声明turbo: 2.9.6、node >= 18的环境要求。根目录统一暴露以下脚本:
{ "scripts": { "dev": "turbo run dev", "build": "turbo run build", "clean": "turbo run clean && rm -rf node_modules", "format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\" --ignore-path .gitignore" } }目录结构与各模块职责
with-react-native-web/ ├── apps/ │ ├── native/ # Expo 驱动的 React Native 应用 │ └── web/ # Next.js + react-native-web 应用 ├── packages/ │ ├── typescript-config/ # 共享的 tsconfig 配置 │ └── ui/ # 共享的 React Native 组件库(Button) ├── turbo.json # Turbo 任务管道配置 ├── package.json # workspace 根配置 └── yarn.locknative:Expo 原生应用
native/package.json 是标准的 Expo Router 项目,关键依赖为expo ^55.0.4、react-native 0.83.2、react 19.2.0,并包含expo-router、expo-status-bar、react-native-safe-area-context、react-native-screens等配套库。它提供的脚本有:
{ "scripts": { "dev": "expo start --web", "android": "expo run:android", "ios": "expo run:ios", "web": "expo start --web", "eject": "expo eject" } }入口为 index.js,页面代码位于app/目录(Expo Router 文件路由)。应用级配置集中在 app.json,其中值得注意的字段包括:
scheme: "com.turbo.example"与 Android 的package、iOS 的 bundle 标识;web.bundler: "metro",即 Web 端同样使用 Metro 打包;plugins: ["expo-router"]启用文件路由;assets/下的icon.png、splash.png、adaptive-icon.png、favicon.png分别用于应用图标、启动屏、Android 自适应图标与 Web favicon。
在 app/index.tsx 中,页面通过View、Text、StyleSheet渲染,并从@repo/ui引入共享的Button组件:
import { StyleSheet, Text, View } from "react-native"; import { StatusBar } from "expo-status-bar"; import { Button } from "@repo/ui"; export default function Native() { return ( <View style={styles.container}> <Text style={styles.header}>Native</Text> <Button onClick={() => { console.log("Pressed!"); alert("Pressed!"); }} text="Boop" /> <StatusBar style="auto" /> </View> ); }web:Next.js + react-native-web
web/package.json 是一个 Next.js 16 应用(next 16.3.0),除常规依赖外显式引入react-native-web ^0.21.0,并在开发依赖中声明babel-plugin-react-native-web ^0.19.10以支持 Babel 层的组件转换。脚本为标准的next dev/next build/next start/next lint。
首页 app/page.tsx 同样直接消费@repo/ui的Button:
"use client"; import { Button } from "@repo/ui"; import styles from "../styles/index.module.css"; export default function Web() { return ( <div className={styles.container}> <h1>Web</h1> <Button onClick={() => console.log("Pressed!")} text="Boop" /> </div> ); }由于组件库是 React Native 风格实现(Pressable+StyleSheet),在 Web 端正是依赖 react-native-web 将其编译为原生 DOM 元素与 CSS,实现同一组件在两端的真实复用。
@repo/ui:共享 React Native 组件库
组件库的入口 src/index.tsx 导出Button,核心实现 src/button.tsx 使用 React Native 的Pressable、Text与StyleSheet编写:
import * as React from "react"; import { StyleSheet, GestureResponderEvent, Text, Pressable, } from "react-native"; export interface ButtonProps { text: string; onClick?: (event: GestureResponderEvent) => void; } export function Button({ text, onClick }: ButtonProps) { return ( <Pressable style={styles.button} onPress={onClick}> <Text style={styles.text}>{text}</Text> </Pressable> ); } const styles = StyleSheet.create({ button: { maxWidth: 200, textAlign: "center", borderRadius: 10, paddingTop: 14, paddingBottom: 14, paddingLeft: 30, paddingRight: 30, fontSize: 15, backgroundColor: "#2f80ed", }, text: { color: "white", }, });其构建由 tsup.config.ts 驱动,关键配置包括:
entry指向src/index.tsx,输出 CJS 与 ESM 两种格式;banner.js: "'use client'",确保组件在 Next.js App Router 下被正确视为客户端组件;external: ["react"],将 react 排除在打包产物之外;- 自动生成
.d.ts类型声明(dts配置)。
包清单 package.json 声明main: ./dist/index.js、types: ./dist/index.d.ts,因此应用侧通过@repo/ui包名直接引用构建产物,这正是 monorepo 内部包共享的关键机制。
@repo/typescript-config:统一类型配置
该包提供三份可继承的配置:base.json、nextjs.json与react-native-library.json。react-native-library.json 面向 React Native 组件库,开启allowJs、jsx: react、lib: ["DOM", "ESNext"]、noEmit: true等选项;@repo/ui的 tsconfig.json 即通过"extends": "@repo/typescript-config/react-native-library.json"继承之,并额外开启strict: true。
Turbo 任务管道:让两端构建协同调度
根目录 turbo.json 定义了整个仓库的任务编排,是理解"为什么一条turbo run build就能串起全部应用"的关键:
{ "$schema": "https://turborepo.dev/schema.json", "ui": "tui", "tasks": { "build": { "inputs": ["$TURBO_DEFAULT$", ".env*"], "outputs": ["dist/**", ".next/**", "!.next/cache/**", "!.next/dev/**"], "dependsOn": ["^build"] }, "dev": { "cache": false, "persistent": true }, "lint": {}, "clean": { "cache": false } } }各任务的工程含义:
- build:
dependsOn: ["^build"]表示先构建所有依赖方(即@repo/ui),再并行构建web与native;inputs声明参与哈希计算的输入(默认文件加.env*);outputs声明产物目录(dist/**、.next/**),其中!.next/cache/**与!.next/dev/**用于排除不应纳入缓存的 Next.js 缓存与开发产物。 - dev:
persistent: true声明其为长驻进程(如expo start、next dev),cache: false关闭缓存,避免长期运行的开发服务器污染缓存。 - lint:空配置即采用 Turborepo 默认行为,对声明了
lint脚本的包并行执行。 - clean:
cache: false,确保清理任务总是真实执行。
这种管道设计使得新增一个应用或包时无需修改根配置,只要包内声明对应 script,Turbo 便会自动纳入依赖图调度并利用缓存加速构建。
运行与验证
在示例根目录依次执行:
yarn install # 安装全部 workspace 依赖 yarn dev # 等价于 turbo run dev,并行启动两端开发服务器 yarn build # 先构建 @repo/ui,再构建 web/native yarn clean # 清理各包构建产物并移除 node_modules开发模式下,native端可通过 Expo 的 Web 模式(expo start --web)在浏览器中预览,也可用yarn ios/yarn android启动原生模拟器;web端运行在 Next.js 默认端口。两端页面共享同一个Button:点击后原生端弹出alert("Pressed!"),Web 端则在控制台输出"Pressed!",直观验证了"组件复用、行为按平台定制"的跨端模式。
小结
with-react-native-web示例展示了 Turborepo 在跨端场景下的典型用法:Expo 与 Next.js 共存于一个 workspace,React Native 组件库通过 tsup 打包后在两端复用,统一的turbo.json管道则负责依赖排序、产物缓存与长驻开发进程管理。对于希望以最小成本实现 iOS / Android / Web 三端覆盖的团队,这是一个可以直接作为起点的工程模板;更完整的跨端与微前端组合方案,可进一步参考仓库中的 with-microfrontends 与 with-vite-module-federation 等示例。
【免费下载链接】turboBuild system optimized for JavaScript and TypeScript, written in Rust项目地址: https://gitcode.com/gh_mirrors/tu/turbo
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考