news 2026/9/19 3:45:28

使用 Turborepo 构建 React Native Web 跨端 Monorepo:Expo + Next.js + 共享 UI 全指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
使用 Turborepo 构建 React Native Web 跨端 Monorepo:Expo + Next.js + 共享 UI 全指南

使用 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 组件库,被webnative两个应用共享;
  • @repo/typescript-config:整个 monorepo 统一使用的tsconfig.json配置集合。

示例中所有应用与包均 100% 使用 TypeScript 编写(见 README),从而实现同一套 UI 代码在原生端与 Web 端的一致体验。

使用 create-turbo 快速初始化

与仓库内其他示例一致,创建本项目只需一条命令:

npx create-turbo@latest -e with-react-native-web

create-turbo会拉取该示例的模板并基于当前 Turbo 版本初始化。初始化完成后,仓库根目录的 package.json 使用 Yarn Workspaces(apps/*packages/*)组织包,并声明turbo: 2.9.6node >= 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.lock

native:Expo 原生应用

native/package.json 是标准的 Expo Router 项目,关键依赖为expo ^55.0.4react-native 0.83.2react 19.2.0,并包含expo-routerexpo-status-barreact-native-safe-area-contextreact-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.pngsplash.pngadaptive-icon.pngfavicon.png分别用于应用图标、启动屏、Android 自适应图标与 Web favicon。

在 app/index.tsx 中,页面通过ViewTextStyleSheet渲染,并从@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/uiButton

"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 的PressableTextStyleSheet编写:

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.jstypes: ./dist/index.d.ts,因此应用侧通过@repo/ui包名直接引用构建产物,这正是 monorepo 内部包共享的关键机制。

@repo/typescript-config:统一类型配置

该包提供三份可继承的配置:base.jsonnextjs.jsonreact-native-library.json。react-native-library.json 面向 React Native 组件库,开启allowJsjsx: reactlib: ["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 } } }

各任务的工程含义:

  • builddependsOn: ["^build"]表示先构建所有依赖方(即@repo/ui),再并行构建webnativeinputs声明参与哈希计算的输入(默认文件加.env*);outputs声明产物目录(dist/**.next/**),其中!.next/cache/**!.next/dev/**用于排除不应纳入缓存的 Next.js 缓存与开发产物。
  • devpersistent: true声明其为长驻进程(如expo startnext dev),cache: false关闭缓存,避免长期运行的开发服务器污染缓存。
  • lint:空配置即采用 Turborepo 默认行为,对声明了lint脚本的包并行执行。
  • cleancache: 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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/19 3:43:54

.NET 8 vs Java:产线物联网后端的资源确定性选型指南

1. 为什么一个物联网产线项目&#xff0c;会因为选 .NET 还是 Java 被客户当场质疑&#xff1f;我干工业物联网系统集成快八年了&#xff0c;跑过三十多个产线级项目&#xff0c;从汽车焊装车间到食品灌装线&#xff0c;从半导体晶圆厂到中药提取车间。最常被客户运维团队堵在控…

作者头像 李华
网站建设 2026/9/19 3:41:38

人脸识别只是入口:从检测对齐到边缘部署的AI全链路拆解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/19 3:39:48

一文讲透信令流程讲义:附着、切换与定时器排障要点

简介&#xff1a;以GSM信令流程为主线的完整讲义PPT&#xff0c;面向通信工程专业学生、网络运维与优化人员&#xff0c;也适合作为企业内训或高校教学的辅助材料&#xff0c;帮助读者系统建立移动核心网信令分析框架。内容从GSM网络拓扑入手&#xff0c;阐释MSC、BSC、BTS、HL…

作者头像 李华
网站建设 2026/9/19 3:39:11

Agent测试框架Harbor:从路由识别到工具调用的全链路实践

Agent项目跑起来容易&#xff0c;测起来是真的烦。你千辛万苦把Agent接上大模型&#xff0c;调通工具调用&#xff0c;结果一改prompt&#xff0c;原来的路由识别直接跑偏&#xff0c;你还没法像传统接口那样用断言一把梭。我最近在做一个内部Agent平台&#xff0c;把大模型接入…

作者头像 李华