已上线Expo React Native应用迁移至多入口Monorepo结构技术问询
Alright, let's get this Expo monorepo up and running properly—since you've got two production apps already, we'll focus on making the migration smooth while keeping shared code centralized. Here's how to implement your planned structure step by step:
First, set up the base Lerna structure. Run this in your root directory:
npx lerna init
This will generate lerna.json and a root package.json. Tweak these files to streamline management:
Root package.json (add convenience scripts):
{ "name": "your-monorepo-root", "private": true, "scripts": { "bootstrap": "lerna bootstrap", "start:mobile1": "lerna run start --scope mobile-1", "start:mobile2": "lerna run start --scope mobile-2", "build:mobile1": "lerna run build --scope mobile-1", "build:mobile2": "lerna run build --scope mobile-2" }, "workspaces": ["packages/*"] }
lerna.json (configure workspace behavior):
{ "packages": ["packages/*"], "version": "independent", "npmClient": "yarn", // Swap to "npm" if you prefer "useWorkspaces": true }
mobile-common Package This is your shared code hub. First, set its package.json to define the entry point and shared dependencies:
{ "name": "mobile-common", "version": "1.0.0", "main": "index.js", "dependencies": { "react": "^18.2.0", "react-native": "0.72.0", "expo": "~49.0.0" // Add all shared dependencies here—no need to duplicate in mobile-1/2 } }
Next, create the app startup function in mobile-common/index.js. This function will handle initializing the app and accepting app-specific config:
import { registerRootComponent } from 'expo'; import App from './src/App'; export const startApp = (appConfig) => { // Optional: Set global config for shared components to reference global.appContext = { appId: appConfig.slug, theme: appConfig.extra?.theme }; // Register the shared root component (Expo's standard startup flow) registerRootComponent(App); };
Your actual shared UI/ business logic will live in mobile-common/src/—build App.js to adapt to the appContext or props passed from the individual apps.
mobile-1 and mobile-2 Each app will be a lightweight wrapper that calls the shared startApp function.
For mobile-1/package.json:
{ "name": "mobile-1", "version": "1.0.0", "main": "index.js", "scripts": { "start": "expo start", "build": "expo build:android" // Adjust for iOS as needed }, "dependencies": { "mobile-common": "^1.0.0", "expo": "~49.0.0" // Only add app-specific dependencies here (e.g., a payment SDK unique to mobile-1) }, "devDependencies": { "@babel/core": "^7.20.0" } }
mobile-1/index.js (call the shared startup function):
import { startApp } from 'mobile-common'; import appConfig from './app.json'; // Pass app-specific config (from app.json + any custom values) startApp(appConfig.expo);
Repeat this setup for mobile-2—just swap out the app.json values (like bundle ID, app name, icons) and any custom config you want to pass to startApp.
Each app's app.json must remain independent since Expo requires unique identifiers and assets per app:
// mobile-1/app.json example { "expo": { "name": "Mobile App 1", "slug": "mobile-app-1", "bundleIdentifier": "com.yourcompany.mobile1", "version": "1.0.0", "icon": "./assets/icon.png", "splash": { "image": "./assets/splash.png", "resizeMode": "contain" }, "extra": { "theme": "blue" // Custom config to pass to mobile-common } } }
- Dependency Hoisting: Lerna/Yarn Workspaces will automatically hoist shared dependencies to the root. If you hit Expo-specific conflicts, add
hoistExcludetolerna.jsonto exclude problematic packages. - Hot Reloading: Changes to
mobile-commonwill auto-refresh in both apps during development (thanks to Lerna's symlinks). If it doesn't work, restart the Expo dev server. - Production Builds: Use the root scripts (e.g.,
npm run build:mobile1) or navigate directly to the app's directory to runexpo build. - Environment Variables: Create app-specific
.envfiles in eachmobile-*/directory, or pass env values through thestartAppconfig for shared code to use.
内容的提问来源于stack exchange,提问作者Sebastian Garrido

