Skip to content

Commit 140d71f

Browse files
committed
feat: add expo plugin
1 parent ece451c commit 140d71f

File tree

12 files changed

+1747
-17
lines changed

12 files changed

+1747
-17
lines changed

.changeset/fluffy-mice-roll.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@callstack/repack-plugin-expo": minor
3+
---
4+
5+
Add Expo Router + CNG Plugin

packages/plugin-expo/README.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<div align="center">
2+
<img src="https://raw.githubusercontent.com/callstack/repack/HEAD/logo.png" width="650" alt="Re.Pack logo" />
3+
<h3>A toolkit to build your React Native application with Rspack or Webpack.</h3>
4+
</div>
5+
<div align="center">
6+
7+
[![mit licence][license-badge]][license]
8+
[![npm downloads][npm-downloads-badge]][npm-downloads]
9+
[![Chat][chat-badge]][chat]
10+
[![PRs Welcome][prs-welcome-badge]][prs-welcome]
11+
12+
</div>
13+
14+
`@callstack/repack-plugin-expo` is a plugin for [`@callstack/repack`](https://github.com/callstack/repack) that compliments the integration of Expo Router and Expo CNG into your React Native projects.
15+
16+
## About
17+
18+
This plugin helps and compliments the process of enabling Expo Router and Expo CNG in Re.Pack projects by defining necessary globals that are expected by Expo Router at runtime. However, it is not sufficient on its own for a complete setup. For comprehensive guidance on using Expo Modules with Re.Pack, please refer to our [official documentation](https://re-pack.dev/).
19+
20+
## Installation
21+
22+
```sh
23+
npm install -D @callstack/repack-plugin-expo
24+
```
25+
26+
## Usage
27+
28+
### Plugin
29+
30+
To add the plugin to your Re.Pack configuration, update your `rspack.config.js` or `webpack.config.js` as follows:
31+
32+
```js
33+
import { ExpoPlugin } from "@callstack/repack-plugin-expo";
34+
35+
export default (env) => {
36+
// ...
37+
return {
38+
// ...
39+
plugins: [
40+
// ...
41+
new ExpoPlugin(),
42+
],
43+
};
44+
};
45+
```
46+
47+
### CNG Configuration Plugin
48+
49+
To add the configuration plugin to your Expo CNG configuration update your `app.json` or `app.config.js` as follows:
50+
51+
```jsonc
52+
{
53+
// ...
54+
"plugins": [
55+
// ...
56+
"expo-router",
57+
"@callstack/repack-plugin-expo/plugin"
58+
]
59+
}
60+
```
61+
62+
---
63+
64+
Check out our website at https://re-pack.dev for more info and documentation or our GitHub: https://github.com/callstack/repack.
65+
66+
<!-- badges -->
67+
68+
[license-badge]: https://img.shields.io/npm/l/@callstack/repack?style=for-the-badge
69+
[license]: https://github.com/callstack/repack/blob/main/LICENSE
70+
[npm-downloads-badge]: https://img.shields.io/npm/dm/@callstack/repack?style=for-the-badge
71+
[npm-downloads]: https://www.npmjs.com/package/@callstack/repack
72+
[prs-welcome-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=for-the-badge
73+
[prs-welcome]: ./CONTRIBUTING.md
74+
[chat-badge]: https://img.shields.io/discord/426714625279524876.svg?style=for-the-badge
75+
[chat]: https://discord.gg/Q4yr2rTWYF

packages/plugin-expo/package.json

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
{
2+
"name": "@callstack/repack-plugin-expo",
3+
"version": "5.1.3",
4+
"description": "A plugin for @callstack/repack that integrates Expo CNG and Router",
5+
"author": "Carlos Eduardo <[email protected]>",
6+
"contributors": ["Jakub Romańczyk <[email protected]>"],
7+
"license": "MIT",
8+
"homepage": "https://github.com/callstack/repack",
9+
"repository": "github:callstack/repack",
10+
"type": "commonjs",
11+
"main": "dist/index.js",
12+
"types": "dist/index.d.ts",
13+
"files": ["dist", "plugin"],
14+
"exports": {
15+
".": {
16+
"types": "./dist/index.d.ts",
17+
"default": "./dist/index.js"
18+
},
19+
"./plugin": {
20+
"types": "./plugin/index.d.ts",
21+
"default": "./plugin/index.js"
22+
}
23+
},
24+
"keywords": [
25+
"repack",
26+
"re.pack",
27+
"plugin",
28+
"repack-plugin",
29+
"expo-router",
30+
"expo-cng",
31+
"expo"
32+
],
33+
"publishConfig": {
34+
"registry": "https://registry.npmjs.org/",
35+
"access": "public"
36+
},
37+
"engineStrict": true,
38+
"engines": {
39+
"node": ">=18"
40+
},
41+
"peerDependencies": {
42+
"@callstack/repack": "workspace:*",
43+
"@callstack/repack-plugin-expo-modules": "workspace:*",
44+
"expo": "catalog:"
45+
},
46+
"devDependencies": {
47+
"@callstack/repack": "workspace:*",
48+
"@callstack/repack-plugin-expo-modules": "workspace:*",
49+
"expo": "catalog:",
50+
"@rspack/core": "catalog:",
51+
"@types/node": "catalog:",
52+
"webpack": "catalog:"
53+
},
54+
"scripts": {
55+
"build": "tsc -p tsconfig.build.json",
56+
"typecheck": "tsc --noEmit"
57+
}
58+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from '../dist/config-plugin.js';
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require('../dist/config-plugin.js');
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// plugins/expo-repack-plugin.js
2+
import type { ConfigPlugin } from 'expo/config-plugins';
3+
import { withAppBuildGradle, withXcodeProject } from 'expo/config-plugins';
4+
5+
/**
6+
* Configuration plugin to remove some expo defaults to ensure that re-pack works correctly.
7+
*
8+
* @param current Current expo configuration
9+
* @returns Modified expo configuration
10+
*/
11+
const plugin: ConfigPlugin = (current) => {
12+
let res = current;
13+
14+
// iOS
15+
// Replace $CLI_PATH and $BUNDLE_COMMAND in the Xcode project (this will ensure that the correct CLI is used in production builds)
16+
res = withXcodeProject(res, (configuration) => {
17+
const xcodeProject = configuration.modResults;
18+
const bundleReactNativeCodeAndImagesBuildPhase =
19+
xcodeProject.buildPhaseObject(
20+
'PBXShellScriptBuildPhase',
21+
'Bundle React Native code and images'
22+
);
23+
24+
if (!bundleReactNativeCodeAndImagesBuildPhase) return configuration;
25+
26+
const script = JSON.parse(
27+
bundleReactNativeCodeAndImagesBuildPhase.shellScript
28+
);
29+
const patched = script
30+
.replace(
31+
/if \[\[ -z "\$CLI_PATH" \]\]; then[\s\S]*?fi\n?/g,
32+
`export CLI_PATH="$("$NODE_BINARY" --print "require('path').dirname(require.resolve('@react-native-community/cli/package.json')) + '/build/bin.js'")"`
33+
)
34+
.replace(/if \[\[ -z "\$BUNDLE_COMMAND" \]\]; then[\s\S]*?fi\n?/g, '');
35+
36+
bundleReactNativeCodeAndImagesBuildPhase.shellScript =
37+
JSON.stringify(patched);
38+
return configuration;
39+
});
40+
41+
// Android
42+
// Replace cliFile and bundleCommand in the app/build.gradle file (this will ensure that the correct CLI is used in production builds)
43+
res = withAppBuildGradle(res, (configuration) => {
44+
const buildGradle = configuration.modResults.contents;
45+
const patched = buildGradle
46+
.replace(/cliFile.*/, '')
47+
.replace(/bundleCommand.*/, 'bundleCommand = "bundle"');
48+
49+
configuration.modResults.contents = patched;
50+
return configuration;
51+
});
52+
53+
return res;
54+
};
55+
56+
export default plugin;

packages/plugin-expo/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { ExpoPlugin } from './plugin.js';

packages/plugin-expo/src/plugin.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { resolve } from 'node:path';
2+
import { ExpoModulesPlugin } from '@callstack/repack-plugin-expo-modules';
3+
import type { Compiler as RspackCompiler } from '@rspack/core';
4+
import type { Compiler as WebpackCompiler } from 'webpack';
5+
6+
interface ExpoPluginOptions {
7+
/**
8+
* Base URL for the Expo Router.
9+
*
10+
* By default, it's set to an empty string.
11+
*/
12+
baseUrl?: string;
13+
14+
/**
15+
* Routes root directory.
16+
*
17+
* By default, it's set to <projectRoot>/app (which is the default for Expo Router).
18+
* It should match the `root` option in your `expo-router` `app.json` plugin configuration.
19+
*
20+
* @see https://docs.expo.dev/router/reference/src-directory/#custom-directory
21+
*/
22+
routesRoot?: string;
23+
24+
/**
25+
* Project root directory.
26+
*
27+
* By default, it's set to the current working directory.
28+
*/
29+
projectRoot?: string;
30+
31+
/**
32+
* Target application platform (e.g. `ios`, `android`).
33+
*
34+
* By default, the platform is inferred from `compiler.options.name` which is set by Re.Pack.
35+
*/
36+
platform?: string;
37+
}
38+
39+
export class ExpoPlugin {
40+
constructor(private options: ExpoPluginOptions = {}) {}
41+
42+
apply(compiler: RspackCompiler): void;
43+
apply(compiler: WebpackCompiler): void;
44+
45+
apply(__compiler: unknown) {
46+
const compiler = __compiler as RspackCompiler;
47+
48+
const baseUrl = this.options.baseUrl ?? '';
49+
const projectRoot = this.options.projectRoot ?? process.cwd();
50+
const routesRoot = this.options.routesRoot ?? resolve(projectRoot, 'app');
51+
52+
const platform = this.options.platform ?? (compiler.options.name as string);
53+
54+
// apply expo modules plugin
55+
new ExpoModulesPlugin({ platform }).apply(compiler);
56+
57+
// expo router expect this to be defined in runtime
58+
new compiler.webpack.DefinePlugin({
59+
'process.env.EXPO_BASE_URL': JSON.stringify(baseUrl),
60+
'process.env.EXPO_PROJECT_ROOT': JSON.stringify(projectRoot),
61+
'process.env.EXPO_ROUTER_ABS_APP_ROOT': JSON.stringify(routesRoot),
62+
'process.env.EXPO_ROUTER_APP_ROOT': JSON.stringify(routesRoot),
63+
'process.env.EXPO_ROUTER_IMPORT_MODE': JSON.stringify('sync'),
64+
}).apply(compiler);
65+
66+
// add proxy configuration in development
67+
if (
68+
compiler.options.mode === 'development' &&
69+
!!compiler.options.devServer
70+
) {
71+
compiler.options.devServer.proxy ??= [];
72+
73+
// redirect `/.expo/.virtual-metro-entry` to `/index` to match metro behavior in Expo managed projects
74+
compiler.options.devServer.proxy.push({
75+
context: ['/.expo/.virtual-metro-entry'],
76+
pathRewrite: { '^/.expo/.virtual-metro-entry': '/index' },
77+
});
78+
}
79+
}
80+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"extends": "./tsconfig.json",
3+
"compilerOptions": {
4+
"outDir": "dist",
5+
"rootDir": "src",
6+
"paths": {}
7+
}
8+
}

packages/plugin-expo/tsconfig.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"extends": "../../tsconfig.base.json",
3+
"compilerOptions": {
4+
"outDir": "dist",
5+
"rootDirs": ["src", "../repack/src", "../dev-server/src"],
6+
"paths": {
7+
"@callstack/repack": ["../repack/src/index.ts"],
8+
"@callstack/repack/*": ["../repack/src/*"],
9+
"@callstack/repack-dev-server": ["../dev-server/src/index.ts"],
10+
"@callstack/repack-dev-server/*": ["../dev-server/src/*"],
11+
"@callstack/repack-plugin-expo-modules": [
12+
"../plugin-expo-modules/src/index.ts"
13+
],
14+
"@callstack/repack-plugin-expo-modules/*": [
15+
"../plugin-expo-modules/src/*"
16+
]
17+
}
18+
},
19+
"include": ["src/**/*"]
20+
}

0 commit comments

Comments
 (0)