Skip to content

Commit dfff0b9

Browse files
authored
Merge pull request #2 from EndLess728/new-config-plugin
Expo config plugin
2 parents fa67ea4 + 1ef0101 commit dfff0b9

8 files changed

Lines changed: 818 additions & 305 deletions

File tree

README.md

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
<img src="https://img.shields.io/npm/dw/react-native-mediapipe-posedetection?color=blue&style=flat-square&logo=npm" alt="npm weekly downloads"/>
66
</div>
77

8-
98
High-performance pose detection for React Native using Google's MediaPipe models with optimized frame processing for smooth real-time tracking.
109

1110
You can find the package on npm: [react-native-mediapipe-posedetection](https://www.npmjs.com/package/react-native-mediapipe-posedetection)
@@ -42,6 +41,35 @@ npm install react-native-mediapipe-posedetection react-native-vision-camera reac
4241
yarn add react-native-mediapipe-posedetection react-native-vision-camera react-native-worklets-core
4342
```
4443

44+
### Expo Configuration
45+
46+
If you are using Expo, you can use the built-in config plugin to automatically copy your MediaPipe model files to the native Android and iOS projects during prebuild.
47+
48+
1. Add your model files (e.g., `pose_landmarker_lite.task`) to a directory in your project (e.g., `./assets/models/`).
49+
2. Update your `app.json` or `app.config.js`:
50+
51+
```json
52+
{
53+
"expo": {
54+
"plugins": [
55+
[
56+
"react-native-mediapipe-posedetection",
57+
{
58+
"assetsPaths": ["./assets/models/"]
59+
}
60+
]
61+
]
62+
}
63+
}
64+
```
65+
66+
This plugin will copy all files from the specified `assetsPaths` to:
67+
68+
- **Android:** `android/app/src/main/assets/`
69+
- **iOS:** The root of the Xcode project (and add them to the build resources).
70+
71+
> **Note:** The `assetsPaths` are relative to your project root.
72+
4573
### Enable New Architecture
4674

4775
If you haven't already enabled the New Architecture in your React Native app:

app.plugin.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require('./lib/commonjs/plugin/withMediapipePosedetection');

package.json

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,36 @@
22
"name": "react-native-mediapipe-posedetection",
33
"version": "0.2.0",
44
"description": "PoseDetection using google's mediapipe models using poselandmark",
5-
"main": "./lib/module/index.js",
6-
"types": "./lib/typescript/src/index.d.ts",
5+
"main": "./lib/commonjs/index.js",
6+
"module": "./lib/module/index.js",
7+
"types": "./lib/typescript/commonjs/src/index.d.ts",
8+
"source": "./src/index.tsx",
79
"exports": {
810
".": {
9-
"source": "./src/index.tsx",
10-
"types": "./lib/typescript/src/index.d.ts",
11-
"default": "./lib/module/index.js"
11+
"import": {
12+
"types": "./lib/typescript/module/src/index.d.ts",
13+
"default": "./lib/module/index.js"
14+
},
15+
"require": {
16+
"types": "./lib/typescript/commonjs/src/index.d.ts",
17+
"default": "./lib/commonjs/index.js"
18+
}
19+
},
20+
"./app.plugin.js": {
21+
"require": {
22+
"default": "./app.plugin.js"
23+
}
1224
},
13-
"./package.json": "./package.json"
25+
"./plugin": {
26+
"import": {
27+
"types": "./lib/typescript/module/src/plugin/withMediapipePosedetection.d.ts",
28+
"default": "./lib/module/plugin/withMediapipePosedetection.js"
29+
},
30+
"require": {
31+
"types": "./lib/typescript/commonjs/src/plugin/withMediapipePosedetection.d.ts",
32+
"default": "./lib/commonjs/plugin/withMediapipePosedetection.js"
33+
}
34+
}
1435
},
1536
"files": [
1637
"src",
@@ -19,6 +40,7 @@
1940
"ios",
2041
"cpp",
2142
"*.podspec",
43+
"app.plugin.js",
2244
"react-native.config.js",
2345
"!ios/build",
2446
"!android/build",
@@ -55,6 +77,9 @@
5577
"url": "https://github.com/EndLess728/react-native-mediapipe-posedetection/issues"
5678
},
5779
"homepage": "https://github.com/EndLess728/react-native-mediapipe-posedetection#readme",
80+
"dependencies": {
81+
"fs-extra": "^11.2.0"
82+
},
5883
"publishConfig": {
5984
"registry": "https://registry.npmjs.org/"
6085
},
@@ -63,10 +88,12 @@
6388
"@eslint/compat": "^1.3.2",
6489
"@eslint/eslintrc": "^3.3.1",
6590
"@eslint/js": "^9.35.0",
91+
"@expo/config-plugins": "^9.0.0",
6692
"@react-native-community/cli": "20.0.1",
6793
"@react-native/babel-preset": "0.81.1",
6894
"@react-native/eslint-config": "^0.81.1",
6995
"@release-it/conventional-changelog": "^10.0.1",
96+
"@types/fs-extra": "^11.0.4",
7097
"@types/jest": "^29.5.14",
7198
"@types/react": "^19.1.0",
7299
"commitlint": "^19.8.1",
@@ -87,13 +114,20 @@
87114
"typescript": "^5.9.2"
88115
},
89116
"peerDependencies": {
90-
"react": "*",
117+
"@expo/config-plugins": ">=7",
118+
"@types/react": ">=16.6.1",
119+
"react": ">=16.6.1",
91120
"react-native": ">=0.74.0",
92121
"react-native-vision-camera": "*",
93122
"react-native-worklets-core": "*"
94123
},
95124
"peerDependenciesMeta": {
96-
"react-native": {}
125+
"@expo/config-plugins": {
126+
"optional": true
127+
},
128+
"@types/react": {
129+
"optional": true
130+
}
97131
},
98132
"workspaces": [
99133
"example"
@@ -103,6 +137,12 @@
103137
"source": "src",
104138
"output": "lib",
105139
"targets": [
140+
[
141+
"commonjs",
142+
{
143+
"esm": true
144+
}
145+
],
106146
[
107147
"module",
108148
{

src/plugin/PluginProps.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/**
2+
* Plugin options for react-native-mediapipe-posedetection config plugin
3+
*/
4+
export interface MediapipePluginProps {
5+
/**
6+
* Array of paths to asset directories (relative to project root)
7+
* These assets will be copied to Android assets and iOS project
8+
* @example ["./models/"]
9+
*/
10+
assetsPaths?: string[];
11+
12+
/**
13+
* Optional regex pattern to ignore files during copy
14+
* @example "\\.txt$" to ignore .txt files
15+
*/
16+
ignoredPattern?: string;
17+
}

src/plugin/android.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import path from 'path';
2+
import { withDangerousMod, type ConfigPlugin } from '@expo/config-plugins';
3+
import { copyFileSync, ensureDirSync, readdirSync } from 'fs-extra';
4+
5+
import type { MediapipePluginProps } from './PluginProps';
6+
7+
/**
8+
* Android-specific config plugin to copy assets to android/app/src/main/assets/
9+
*/
10+
export const withAssets: ConfigPlugin<MediapipePluginProps> = (
11+
config,
12+
props
13+
) => {
14+
const { assetsPaths = [], ignoredPattern } = props || {};
15+
16+
return withDangerousMod(config, [
17+
'android',
18+
async (cfg) => {
19+
const { projectRoot } = cfg.modRequest;
20+
21+
// Copy to android/app/src/main/assets/
22+
const assetsDir = path.join(
23+
projectRoot,
24+
'android',
25+
'app',
26+
'src',
27+
'main',
28+
'assets'
29+
);
30+
ensureDirSync(assetsDir);
31+
32+
for (const assetSourceDir of assetsPaths) {
33+
const assetSourcePath = path.join(projectRoot, assetSourceDir);
34+
35+
let files;
36+
try {
37+
files = readdirSync(assetSourcePath, { withFileTypes: true });
38+
} catch {
39+
console.warn(
40+
`⚠️ [Android] Could not read directory: ${assetSourcePath}`
41+
);
42+
continue;
43+
}
44+
45+
for (const file of files) {
46+
if (
47+
file.isFile() &&
48+
(!ignoredPattern || !file.name.match(new RegExp(ignoredPattern)))
49+
) {
50+
const srcPath = path.join(assetSourcePath, file.name);
51+
const destPath = path.join(assetsDir, file.name);
52+
copyFileSync(srcPath, destPath);
53+
console.log(`✅ [Android] Copied ${file.name} to assets/`);
54+
}
55+
}
56+
}
57+
58+
return cfg;
59+
},
60+
]);
61+
};
62+
63+
export const android = {
64+
withAssets,
65+
};

src/plugin/ios.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import path from 'path';
2+
import {
3+
withXcodeProject,
4+
IOSConfig,
5+
type ConfigPlugin,
6+
} from '@expo/config-plugins';
7+
import { copyFileSync, ensureDirSync, readdirSync } from 'fs-extra';
8+
9+
import type { MediapipePluginProps } from './PluginProps';
10+
11+
/**
12+
* iOS-specific config plugin to copy assets to iOS project with Xcode references
13+
*/
14+
export const withAssets: ConfigPlugin<MediapipePluginProps> = (
15+
config,
16+
props
17+
) => {
18+
const { assetsPaths = [], ignoredPattern } = props || {};
19+
20+
return withXcodeProject(config, async (cfg) => {
21+
const { projectRoot, platformProjectRoot } = cfg.modRequest;
22+
const project = cfg.modResults;
23+
const projectName = cfg.modRequest.projectName || 'App';
24+
25+
// Copy files directly to iOS root folder
26+
ensureDirSync(platformProjectRoot);
27+
28+
for (const assetSourceDir of assetsPaths) {
29+
const assetSourcePath = path.join(projectRoot, assetSourceDir);
30+
31+
let files;
32+
try {
33+
files = readdirSync(assetSourcePath, { withFileTypes: true });
34+
} catch {
35+
console.warn(`⚠️ [iOS] Could not read directory: ${assetSourcePath}`);
36+
continue;
37+
}
38+
39+
for (const file of files) {
40+
if (
41+
file.isFile() &&
42+
(!ignoredPattern || !file.name.match(new RegExp(ignoredPattern)))
43+
) {
44+
const srcPath = path.join(assetSourcePath, file.name);
45+
const destPath = path.join(platformProjectRoot, file.name);
46+
copyFileSync(srcPath, destPath);
47+
console.log(`✅ [iOS] Copied ${file.name} to ios/ root`);
48+
49+
// Add the file to the Xcode project with proper reference
50+
IOSConfig.XcodeUtils.addResourceFileToGroup({
51+
filepath: file.name,
52+
groupName: projectName,
53+
project,
54+
isBuildFile: true,
55+
verbose: true,
56+
});
57+
}
58+
}
59+
}
60+
61+
return cfg;
62+
});
63+
};
64+
65+
export const ios = {
66+
withAssets,
67+
};
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { type ConfigPlugin, createRunOncePlugin } from '@expo/config-plugins';
2+
3+
import type { MediapipePluginProps } from './PluginProps';
4+
import { android } from './android';
5+
import { ios } from './ios';
6+
7+
let pkg: { name: string; version?: string } = {
8+
name: 'react-native-mediapipe-posedetection',
9+
};
10+
try {
11+
pkg = require('react-native-mediapipe-posedetection/package.json');
12+
} catch {
13+
// empty catch block
14+
}
15+
16+
/**
17+
* Main config plugin entry point for react-native-mediapipe-posedetection
18+
*
19+
* Copies model assets to:
20+
* - Android: android/app/src/main/assets/
21+
* - iOS: ios/ (root folder with Xcode project references)
22+
*
23+
* Usage in app.json:
24+
* ```json
25+
* [
26+
* "react-native-mediapipe-posedetection",
27+
* {
28+
* "assetsPaths": ["./models/"],
29+
* "ignoredPattern": "\\.txt$" // optional regex pattern
30+
* }
31+
* ]
32+
* ```
33+
*/
34+
const withMediapipePosedetection: ConfigPlugin<MediapipePluginProps> = (
35+
config,
36+
props
37+
) => {
38+
const { assetsPaths = [] } = props || {};
39+
40+
if (assetsPaths.length === 0) {
41+
console.warn(
42+
'⚠️ [react-native-mediapipe-posedetection] No assetsPaths provided to config plugin'
43+
);
44+
return config;
45+
}
46+
47+
// Android
48+
config = android.withAssets(config, props);
49+
50+
// iOS
51+
config = ios.withAssets(config, props);
52+
53+
return config;
54+
};
55+
56+
export default createRunOncePlugin(
57+
withMediapipePosedetection,
58+
pkg.name,
59+
pkg.version
60+
);

0 commit comments

Comments
 (0)