@crossbind/plugin-metro 2.0.0-beta.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -0
- package/LICENSE +21 -0
- package/README.md +75 -0
- package/package.json +33 -0
- package/src/metro-plugin.cjs +51 -0
- package/src/metro-transformer.cjs +47 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# @crossbind/plugin-metro
|
|
2
|
+
|
|
3
|
+
## 2.0.0-beta.50
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Aligned on the 2.0.0-beta.50 baseline. No source change; the version exists so every package in
|
|
8
|
+
the workspace names the same release.
|
|
9
|
+
|
|
10
|
+
## 1.0.2
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- Updated dependencies
|
|
15
|
+
- crossbind@1.0.4
|
|
16
|
+
|
|
17
|
+
## 1.0.0
|
|
18
|
+
|
|
19
|
+
### Major Changes
|
|
20
|
+
|
|
21
|
+
- 🚀 first stable release
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- Updated dependencies
|
|
26
|
+
- crossbind@1.0.0
|
|
27
|
+
|
|
28
|
+
## 1.0.0-beta.38
|
|
29
|
+
|
|
30
|
+
### Patch Changes
|
|
31
|
+
|
|
32
|
+
- chore: add initial version of CHANGELOGS files
|
|
33
|
+
- Updated dependencies
|
|
34
|
+
- crossbind@1.0.0-beta.33
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Buğra Sarı and crossbind contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# @crossbind/plugin-metro
|
|
2
|
+
**crossbind Metro plugin**
|
|
3
|
+
A tool for seamless C++ integration with the Metro bundler.
|
|
4
|
+
|
|
5
|
+
<a href="https://www.npmjs.com/package/@crossbind/plugin-metro">
|
|
6
|
+
<img alt="NPM version" src="https://img.shields.io/npm/v/@crossbind/plugin-metro?style=for-the-badge" />
|
|
7
|
+
</a>
|
|
8
|
+
<a href="https://github.com/crossbind/crossbind/blob/main/LICENSE">
|
|
9
|
+
<img alt="License" src="https://img.shields.io/github/license/crossbind/crossbind?style=for-the-badge" />
|
|
10
|
+
</a>
|
|
11
|
+
<a href="https://crossbind.dev/docs/guide/integrate-into-existing-project/react-native">
|
|
12
|
+
<img alt="Docs - React Native" src="https://img.shields.io/badge/Docs_-_React%20Native-20B2AA?style=for-the-badge" />
|
|
13
|
+
</a>
|
|
14
|
+
<a href="https://crossbind.dev/docs/guide/integrate-into-existing-project/expo">
|
|
15
|
+
<img alt="Docs - Expo" src="https://img.shields.io/badge/Docs_-_Expo-20B2AA?style=for-the-badge" />
|
|
16
|
+
</a>
|
|
17
|
+
|
|
18
|
+
## Integration
|
|
19
|
+
To integrate crossbind into your project using Metro as a bundler, you can utilize the @crossbind/plugin-metro plugin. Start by installing these package with the following command:
|
|
20
|
+
|
|
21
|
+
NPM
|
|
22
|
+
```sh
|
|
23
|
+
npm install @crossbind/plugin-metro --save-dev
|
|
24
|
+
```
|
|
25
|
+
or YARN
|
|
26
|
+
```sh
|
|
27
|
+
yarn add @crossbind/plugin-metro --dev
|
|
28
|
+
```
|
|
29
|
+
or PNPM
|
|
30
|
+
```sh
|
|
31
|
+
pnpm add @crossbind/plugin-metro --save-dev
|
|
32
|
+
```
|
|
33
|
+
or BUN
|
|
34
|
+
```sh
|
|
35
|
+
bun add @crossbind/plugin-metro --dev
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
To enable the plugin, modify the metro.config.js file as shown below.
|
|
39
|
+
|
|
40
|
+
**React Native**
|
|
41
|
+
```diff
|
|
42
|
+
const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config');
|
|
43
|
+
+const CrossbindMetroPlugin = require('@crossbind/plugin-metro');
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Metro configuration
|
|
47
|
+
* https://reactnative.dev/docs/metro
|
|
48
|
+
*
|
|
49
|
+
* @type {import('metro-config').MetroConfig}
|
|
50
|
+
*/
|
|
51
|
+
-const config = {};
|
|
52
|
+
+const config = {
|
|
53
|
+
+ ...CrossbindMetroPlugin(getDefaultConfig(__dirname)),
|
|
54
|
+
+};
|
|
55
|
+
|
|
56
|
+
module.exports = mergeConfig(getDefaultConfig(__dirname), config);
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**Expo**
|
|
60
|
+
```diff
|
|
61
|
+
// Learn more https://docs.expo.io/guides/customizing-metro
|
|
62
|
+
const { getDefaultConfig } = require('expo/metro-config');
|
|
63
|
+
+const { mergeConfig } = require('metro-config');
|
|
64
|
+
+const CrossbindMetroPlugin = require('@crossbind/plugin-metro');
|
|
65
|
+
|
|
66
|
+
/** @type {import('expo/metro-config').MetroConfig} */
|
|
67
|
+
const config = getDefaultConfig(__dirname);
|
|
68
|
+
|
|
69
|
+
+const newConfig = {
|
|
70
|
+
+ ...CrossbindMetroPlugin(config),
|
|
71
|
+
+};
|
|
72
|
+
|
|
73
|
+
-module.exports = config;
|
|
74
|
+
+module.exports = mergeConfig(config, newConfig);
|
|
75
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@crossbind/plugin-metro",
|
|
3
|
+
"version": "2.0.0-beta.50",
|
|
4
|
+
"description": "crossbind Metro plugin: A tool for seamless C++ integration with the Metro bundler.",
|
|
5
|
+
"homepage": "https://github.com/crossbind/crossbind/tree/main/plugins/metro#readme",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/crossbind/crossbind.git"
|
|
9
|
+
},
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"main": "src/metro-plugin.cjs",
|
|
12
|
+
"peerDependencies": {
|
|
13
|
+
"metro": "*",
|
|
14
|
+
"@babel/core": "^7.0.0"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"crossbind": "^2.0.0-beta.50",
|
|
18
|
+
"@babel/runtime": "^7.29.7",
|
|
19
|
+
"@react-native/metro-babel-transformer": "^0.85.3",
|
|
20
|
+
"upath": "^3.0.8"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"crossbind-plugin",
|
|
24
|
+
"metro",
|
|
25
|
+
"react-native",
|
|
26
|
+
"expo",
|
|
27
|
+
"bind",
|
|
28
|
+
"c++",
|
|
29
|
+
"android",
|
|
30
|
+
"ios",
|
|
31
|
+
"mobile"
|
|
32
|
+
]
|
|
33
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const path = require('node:path');
|
|
2
|
+
|
|
3
|
+
let getDependFilePathFunc;
|
|
4
|
+
let crossbindState;
|
|
5
|
+
import('crossbind').then(({ getDependFilePath, state }) => {
|
|
6
|
+
getDependFilePathFunc = getDependFilePath;
|
|
7
|
+
crossbindState = state;
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
module.exports = function CrossbindMetroPlugin(defaultConfig) {
|
|
11
|
+
// Generated proxy modules (e.g. a cargo package's lib.rs) originate in package workspaces
|
|
12
|
+
// outside the app's node_modules chain, so their bare imports (react-native, @babel/runtime,
|
|
13
|
+
// @crossbind/core-embind-jsi, ...) must fall back to the app's resolution. extraNodeModules is
|
|
14
|
+
// consulted only when normal resolution fails, so everything else is untouched.
|
|
15
|
+
const projectRoot = defaultConfig.projectRoot || process.cwd();
|
|
16
|
+
const appNodeModules = new Proxy({}, {
|
|
17
|
+
get: (_, name) => {
|
|
18
|
+
try {
|
|
19
|
+
return path.dirname(require.resolve(`${String(name)}/package.json`, { paths: [projectRoot] }));
|
|
20
|
+
} catch (e) {
|
|
21
|
+
return path.join(projectRoot, 'node_modules', String(name));
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
resetCache: true,
|
|
28
|
+
resolver: {
|
|
29
|
+
extraNodeModules: appNodeModules,
|
|
30
|
+
sourceExts: [...defaultConfig.resolver.sourceExts, ...['h', 'hpp', 'hxx', 'hh'], ...['i'], ...['rs']],
|
|
31
|
+
resolveRequest: (context, moduleName, platform) => {
|
|
32
|
+
// The `crossbind` import above resolves asynchronously; until it lands (and for
|
|
33
|
+
// platforms with no matching target) fall back to Metro's default resolution
|
|
34
|
+
// instead of dereferencing undefined state.
|
|
35
|
+
if (crossbindState && getDependFilePathFunc) {
|
|
36
|
+
const target = crossbindState.targets.find((t) => t.platform === platform);
|
|
37
|
+
const dependFilePath = target && getDependFilePathFunc(moduleName, target);
|
|
38
|
+
if (dependFilePath) {
|
|
39
|
+
return context.resolveRequest(context, dependFilePath, platform);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return context.resolveRequest(context, moduleName, platform);
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
transformer: {
|
|
47
|
+
...defaultConfig.transformer,
|
|
48
|
+
babelTransformerPath: require.resolve('./metro-transformer.cjs'),
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
|
|
2
|
+
const upstreamTransformer = (() => {
|
|
3
|
+
try {
|
|
4
|
+
return require('@expo/metro-config/babel-transformer');
|
|
5
|
+
} catch (error) {
|
|
6
|
+
try {
|
|
7
|
+
return require('@react-native/metro-babel-transformer');
|
|
8
|
+
} catch (e) {
|
|
9
|
+
return require('metro-react-native-babel-transformer');
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
})();
|
|
13
|
+
|
|
14
|
+
let compilerClassPromise;
|
|
15
|
+
const getCompilerClass = () => {
|
|
16
|
+
if (compilerClassPromise) return compilerClassPromise;
|
|
17
|
+
compilerClassPromise = new Promise((resolve, reject) => {
|
|
18
|
+
import('crossbind').then(({ state, getCrossbindScript, getRustJsScript, createBridgeFile }) => {
|
|
19
|
+
resolve({ state, getCrossbindScript, getRustJsScript, createBridgeFile });
|
|
20
|
+
}).catch((e) => reject(e));
|
|
21
|
+
});
|
|
22
|
+
return compilerClassPromise;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
module.exports.transform = async ({ src, filename, ...rest }) => {
|
|
26
|
+
const { state, getCrossbindScript, getRustJsScript, createBridgeFile } = await getCompilerClass();
|
|
27
|
+
const headerRegex = new RegExp(`\\.(${state.config.ext.header.join('|')})$`);
|
|
28
|
+
const moduleRegex = new RegExp(`\\.(${state.config.ext.module.join('|')})$`);
|
|
29
|
+
if (headerRegex.test(filename) || moduleRegex.test(filename) || filename.endsWith('.rs')) {
|
|
30
|
+
let target;
|
|
31
|
+
if (rest.options.platform === 'ios') target = state.targets.find((t) => t.platform === 'ios');
|
|
32
|
+
else if (rest.options.platform === 'android') target = state.targets.find((t) => t.platform === 'android');
|
|
33
|
+
else target = state.targets.find((t) => t.platform === 'wasm');
|
|
34
|
+
|
|
35
|
+
// Rust surfaces need no bridge file here: the native bridge is the package's generated
|
|
36
|
+
// companion crate; this only emits the JS proxy module (same shape as the .h flow).
|
|
37
|
+
if (filename.endsWith('.rs')) {
|
|
38
|
+
return upstreamTransformer.transform({ src: getRustJsScript(target, filename), filename, ...rest });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const bridgeFile = createBridgeFile(filename, target);
|
|
42
|
+
|
|
43
|
+
return upstreamTransformer.transform({ src: getCrossbindScript(target, bridgeFile), filename, ...rest });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return upstreamTransformer.transform({ src, filename, ...rest });
|
|
47
|
+
};
|