executing-plans
executing-plans
Use when executing an implementation plan in the current session as the implementer yourself — your human partner chose inline execution, or no subagent tool is available
react-native-monorepo-configConfigures React Native projects in monorepo environments using npm/yarn workspaces, Metro bundler, shared packages, and cross-platform module resolution.
Independent GitHub snapshot · Automated classification · Original documentation governs
name: react-native-monorepo-config description: Configures React Native projects in monorepo environments using npm/yarn workspaces, Metro bundler, shared packages, and cross-platform module resolution. license: MIT compatibility: opencode archetypes:
A senior build engineer who configures React Native projects within monorepo workspaces — wiring Metro bundler to watch all packages, resolving modules across workspace boundaries, linking native modules, and sharing TypeScript/ESLint/Prettier configs across application and library packages.
apps/* and packages/*watchFolders to include all workspace package directoriesnodeModulesPaths / moduleDirs for cross-workspace module resolutionreact-native.config.js for native module linking across packages.watchmanconfig to the monorepo root for proper file watchingextends pattern across all packagesprojects for per-package test configurationUse this skill when:
Avoid this skill for:
Initialize Workspaces — Create the monorepo root with workspace configuration:
yarn workspaces (more mature monorepo support) or npm workspacesapps/* (applications) and packages/* (shared libraries)"private": true at root with "workspaces" config
Checkpoint: Run yarn install — verify packages link correctly in node_modules.Configure Metro — The critical step for React Native monorepos:
watchFoldersnodeModulesPaths to resolve modules across workspacesnode_modules outside workspace packagesprojectRoot explicitly to avoid ambiguity
Checkpoint: Run npx react-native start — Metro must bundle without "module not found" errors.Set Up Native Module Linking — Bridge React Native packages across workspaces:
react-native.config.js in app packages to declare dependenciesdependency.platforms for iOS (podspec path) and Android (gradle link)pod install in the iOS directory to link native pods
Checkpoint: Verify native modules (camera, maps, gestures) build on both platforms.Share Configuration — Unify tooling across packages:
packages/config/ with shared tsconfig.json, .eslintrc.js, .prettierrc.jsextends: '../../packages/config/tsconfig.json'jest.config.base.js and per-package overrides
Checkpoint: Run tsc --noEmit and eslint . from root — both must pass with shared configs.Configure File Watching — Ensure monorepo file changes trigger Metro rebuilds:
.watchmanconfig with {} at the monorepo rootwatchman is installed and version >= 2022.02.14.00watchFolders to Metro config for all symlinked package directories
Checkpoint: Make a change in a shared package and verify Metro triggers a rebuild.// ❌ BAD: Missing private: true, no workspaces config, flat node_modules
{
"name": "my-app",
"version": "1.0.0"
}
// ✅ GOOD: Proper workspaces config with app and package directories
{
"name": "my-monorepo",
"version": "1.0.0",
"private": true,
"workspaces": [
"apps/*",
"packages/*"
],
"scripts": {
"android": "yarn workspace @my/app android",
"ios": "yarn workspace @my/app ios",
"start": "yarn workspace @my/app start",
"lint": "eslint . --ext .ts,.tsx",
"typecheck": "tsc --noEmit --pretty",
"test": "jest"
},
"devDependencies": {
"typescript": "^5.4.0",
"eslint": "^8.57.0",
"prettier": "^3.2.0",
"jest": "^29.7.0"
}
}
// metro.config.js (at apps/my-app/)
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
const path = require('path');
const workspaceRoot = path.resolve(__dirname, '../..');
const appRoot = __dirname;
/**
* Metro configuration for React Native monorepo.
*
* watchFolders: tells Metro to monitor all workspace package directories.
* nodeModulesPaths: resolves require('module') across workspace boundaries.
* blockList: prevents Metro from crawling large extraneous directories.
*/
const config = {
projectRoot: appRoot,
watchFolders: [
workspaceRoot,
// Explicitly add shared package directories so Metro watches them
path.resolve(workspaceRoot, 'packages/shared-ui'),
path.resolve(workspaceRoot, 'packages/config'),
path.resolve(workspaceRoot, 'packages/helpers'),
],
resolver: {
nodeModulesPaths: [
// Resolve modules from the app's node_modules first, then hoisted root
path.resolve(appRoot, 'node_modules'),
path.resolve(workspaceRoot, 'node_modules'),
],
// Prevent Metro from crawling every node_modules in every workspace
blockList: [
/.*\/node_modules\/.*\/node_modules\/.*/,
/.*\/__pycache__\/.*/,
/.*\/\.git\/.*/,
/.*\/build\/.*/,
/.*\/\.expo\/.*/,
],
// Ensure sourceExts covers shared package file types
sourceExts: ['js', 'jsx', 'ts', 'tsx', 'json', 'svg'],
},
transformer: {
// Required for monorepo symlinks
minifierConfig: {
keep_classnames: true,
keep_fnames: true,
},
},
};
module.exports = mergeConfig(getDefaultConfig(appRoot), config);
// ❌ BAD: No react-native.config.js — native modules in workspace packages won't link
// (metro cannot resolve native modules from symlinked packages without this config)
// ✅ GOOD: react-native.config.js at the app level declares workspace dependency linkage
// apps/my-app/react-native.config.js
module.exports = {
project: {
ios: {
sourceDir: './ios',
},
android: {
sourceDir: './android',
},
},
dependencies: {
// Map workspace packages to their native module locations
'@my/shared-ui': {
root: path.resolve(__dirname, '../../packages/shared-ui'),
platforms: {
ios: {
podspecPath: path.resolve(
__dirname,
'../../packages/shared-ui/ios/SharedUI.podspec'
),
},
android: {
sourceDir: path.resolve(
__dirname,
'../../packages/shared-ui/android'
),
},
},
},
},
};
// ✅ GOOD: Workspace package declares its native module in package.json
// packages/shared-ui/package.json
{
"name": "@my/shared-ui",
"version": "1.0.0",
"main": "src/index.ts",
"react-native": "src/index.ts",
"peerDependencies": {
"react": "^18.2.0",
"react-native": "^0.73.0"
},
"publishConfig": {
"access": "public"
}
}
// ❌ BAD: Each package duplicates its own TypeScript config — drifts over time
// packages/shared-ui/tsconfig.json
{
"compilerOptions": {
"strict": true,
"moduleResolution": "node",
"jsx": "react-native",
"target": "esnext"
}
}
// apps/my-app/tsconfig.json — same thing copy-pasted
{
"compilerOptions": {
"strict": true,
"moduleResolution": "node",
"jsx": "react-native",
"target": "esnext"
}
}
// ✅ GOOD: Single shared config with per-package overrides
// packages/config/tsconfig.base.json
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"jsx": "react-native",
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"baseUrl": ".",
"paths": {
"@my/*": ["packages/*/src"]
}
},
"exclude": ["node_modules", "build", "dist"]
}
// packages/shared-ui/tsconfig.json — extends the base
{
"extends": "../../packages/config/tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src"]
}
// apps/my-app/tsconfig.json — extends the same base
{
"extends": "../../packages/config/tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src"]
}
// packages/config/.eslintrc.js
module.exports = {
root: true,
extends: [
'@react-native',
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
],
plugins: ['@typescript-eslint', 'react', 'react-hooks'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: { jsx: true },
ecmaVersion: 'latest',
sourceType: 'module',
},
rules: {
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn',
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'react-native/no-inline-styles': 'error',
},
ignorePatterns: ['node_modules', 'build', 'dist', '.expo'],
};
// .eslintrc.js at the monorepo root — just points to shared config
module.exports = {
root: true,
extends: ['./packages/config/.eslintrc.js'],
};
// packages/config/.prettierrc.js
module.exports = {
semi: true,
trailingComma: 'all',
singleQuote: true,
printWidth: 100,
tabWidth: 2,
bracketSpacing: true,
arrowParens: 'always',
};
// .prettierrc.js at root — delegates to shared config
module.exports = {
...require('./packages/config/.prettierrc.js'),
};
watchFolders to include all workspace package directoriesnodeModulesPaths to resolve modules across workspace boundariesreact-native.config.js in each app package for native module linking.watchmanconfig at the monorepo root for reliable file change detectionpackages/config packageextends pattern in tsconfig.json to share compilation optionsprojects or a root config that covers all workspace packagesnohoist for react-native packageswatchFoldersblockList in Metro config — crawling every nested node_modules causes OOM crashes| Skill | Purpose |
|---|---|
react-native-state-management | State management library shared across monorepo packages |
react-native-rendering | Rendering optimizations shared between app and component library packages |
Authoritative documentation links for React Native monorepo configuration.