feat(expo): add first-class Re.Pack integration - #1425
Conversation
|
|
@whydidoo is attempting to deploy a commit to the Callstack Team on Vercel. A member of the Team first needs to authorize it. |
|
@whydidoo can you check the merge conflicts? |
| "expo-router": "~56.2.7", | ||
| "expo-status-bar": "~56.0.4", | ||
| "react": "19.2.3", | ||
| "react-native": "0.85.3", |
There was a problem hiding this comment.
I recently updated our react native version to "0.86.0"
also note that we have some of our package versions in catalogs in pnpm workspace
would be good to keep react native versions in sync across projects I think
There was a problem hiding this comment.
Expo SDK 57 currently expects React Native 0.86.2 and React 19.2.3.
I’ve kept both Expo examples on the same Expo-compatible versions rather than forcing the default catalog versions. We could add a dedicated Expo SDK 57 catalog later to keep those versions centralized as well.
|
lets try and align the Expo package and examples with the workspace catalogs At the moment the new Expo projects use direct pins for React, React Native, Community CLI packages, the React Native Babel preset, React types, TypeScript, and Module Federation, while existing testers use the shared catalog entries. In particular, the Expo apps use React Native 0.86.2 while the catalog is 0.86.0; if 0.86.2 is the Expo 57-compatible version, please update the shared React Native catalog to 0.86.2 and have the relevant examples consume it through the catalog. The Expo SDK packages themselves can live in a dedicated Expo 57 catalog, shared by both Expo fixtures, so their versions do not drift. The same applies to any Expo-specific React, CLI, or Babel compatibility pins that genuinely cannot use the default catalog. regarding typescript version I have a separate pr that updates typescript to v7 which we could merge before this and then update those expo projects to be on v7 as well |
- align Expo 57 fixtures with workspace catalogs and React Native 0.86.2 - tolerate whitespace changes in generated Android and iOS config anchors - stop package manager lookup at package.json workspaces - use Rspack RuleSetRules for Expo Router configuration - add regression coverage for config anchors and workspace detection
MikitasK
left a comment
There was a problem hiding this comment.
excellent work overall 👏 👍
just 2 comments about forwarding custom Expo entry & supporting Rspack 2 cache invalidation, plus 2 non-blocking edge-cases related suggestions:
| module.exports = (env) => createConfig(env, projectRoot); | ||
|
|
||
| function createConfig(env, projectRoot) { | ||
| const { mode = 'development', platform = process.env.PLATFORM, devServer } = env; |
There was a problem hiding this comment.
could we pass env.entry to ExpoPlugin in both generated config variants?
the thing is that native release builds provide Config Plugin’s custom entry through env.entry, but generated config currently drops it. ExpoPlugin therefore bundles default entry from package.json instead of custom entry
const {
entry,
mode = 'development',
platform = process.env.PLATFORM,
devServer,
} = env;
...
plugins: [new ExpoPlugin({ entry, platform })],
There was a problem hiding this comment.
Good point - entry needs to be forwarded here because ExpoPlugin owns the final compiler entry and prepends the ScriptManager runtime. Without forwarding env.entry, a CLI-provided --entry-file would be silently replaced by package.json#main.
I’ve updated both generated CJS and ESM configs to pass entry to ExpoPlugin and added regression coverage.
package.json#main remains the default source of truth. The following config was only used locally to validate a custom native entry through expo prebuild and an iOS Release build:
app.json
[
"@callstack/repack-expo",
{
"entry": "custom-entry.prototype.tsx"
}
]
It is not required for normal usage and won’t be included in the example. The regular setup remains:
{
"main": "expo-router/entry",
"plugins": ["@callstack/repack-expo"]
}
It won't be part of the example. Normally the entry comes from package.json#main, and the plugin config stays simple:
"plugins": ["@callstack/repack-expo"]
The config plugin entry option is there for cases where the native entry intentionally differs from package.json#main. --entry-file is still available for a single bundle command.
| ); | ||
|
|
||
| export default (env) => { | ||
| const { mode = 'development', platform = process.env.PLATFORM, devServer } = env; |
| compiler: Compiler, | ||
| digest: string | ||
| ): void { | ||
| const cache = compiler.options.experiments?.cache; |
There was a problem hiding this comment.
could we read persistent-cache configuration from both compiler.options.cache & compiler.options.experiments?.cache?
rspack 2 moved experiments.cache to the top-level cache option (source), so expo env digest is currently not added to rspack 2 cache version
| const cache = compiler.options.experiments?.cache; | |
| const cache = compiler.options.cache || compiler.options.experiments?.cache; |
| /@callstack\/repack-expo\/rspack/.test(contents) && | ||
| /new\s+(?:\w+\.)?ExpoPlugin\s*\(/.test(contents) && | ||
| !/new\s+(?:Repack\.)?RepackPlugin\s*\(/.test(contents) && |
There was a problem hiding this comment.
[nit] could we ignore comments when checking for ExpoPlugin & RepackPlugin usage? for example, this config is currently reported as compatible even though the plugin is never instantiated:
import { ExpoPlugin } from '@callstack/repack-expo/rspack';
// new ExpoPlugin();
export default {};
could we strip comments before running these checks & add this examples as a regression test?
| const version = match[1] === 'ModuleFederationPluginV2' ? 'v2' : 'v1'; | ||
| aliases[version].add(match[2] as string); |
There was a problem hiding this comment.
[nit] could we ensure that aliased ModuleFederationPluginV2 is still detected as v2? here's the case:
import { ModuleFederationPluginV2 as ModuleFederationPlugin } from '@callstack/repack';
new ModuleFederationPlugin({});
this will be classified as v1 in current implementation, which is incorrect.
we can fix it like that:
| const version = match[1] === 'ModuleFederationPluginV2' ? 'v2' : 'v1'; | |
| aliases[version].add(match[2] as string); | |
| const importedName = match[1]; | |
| const alias = match[2]; | |
| const version = importedName === 'ModuleFederationPluginV2' ? 'v2' : 'v1'; | |
| const otherVersion = version === 'v2' ? 'v1' : 'v2'; | |
| aliases[otherVersion].delete(alias); | |
| aliases[version].add(alias); |
Summary
This PR introduces first-class Expo support for Re.Pack through a new
@callstack/repack-expopackage.It allows Expo SDK 56 applications using prebuild/CNG to use Rspack and the existing Re.Pack runtime, including ScriptManager and Module Federation v2, without changing the core Re.Pack packages.
The package remains private while we validate the initial integration contract.
What’s included
ExpoPluginfor Rspack that owns the required Expo/Re.Pack defaults:EXPO_PUBLIC_*environment variablesnpx @callstack/repack-expo initnpx @callstack/repack-expo doctorModule Federation v2
Module Federation remains explicit and application-owned, matching regular Re.Pack behavior.
The implementation supports:
React.lazy()chunks alongside remote widgetsRemote widgets cannot provide native dependencies. Native modules must already be installed and linked in the host application.
Native integration
@callstack/repack-expo initonly updates application-owned configuration.Native changes are applied through the Expo Config Plugin during
expo prebuild. The integration does not require users to maintain custom Swift, Kotlin, Gradle or Xcode changes manually.Development applications are launched with:
npm run repack:start npm run repack:ios # or npm run repack:android