--- url: /docs/getting-started/introduction.md --- # Introduction Before diving deep into Re.Pack and introducing it into project, it's important to understand when and why to use Re.Pack and how does it compare with alternatives. ## About Re.Pack Re.Pack is a modern toolkit that wraps Rspack and webpack to make them work seamlessly with React Native applications. The toolkit includes: - **React Native-specific plugins and loaders** that handle platform differences (platform specific files, assets, etc.) - **Integrated development server** with Hot Module Replacement (HMR) + React Refresh, and debugging capabilities through React Native Devtools - **Runtime modules** like ScriptManager for advanced features like code splitting and Module Federation ## Design goals Re.Pack is designed to be a **drop-in replacement for Metro** with the primary goal of making migration as easy as possible. The toolkit aims to: - Provide seamless integration with existing React Native projects - Maintain compatibility with Metro's expected behavior while offering enhanced capabilities - Minimize configuration changes needed during migration - Offer a smooth transition path from Metro to advanced bundling features This approach ensures teams can adopt Re.Pack incrementally, starting with basic Metro replacement and gradually leveraging advanced features like Module Federation and custom webpack configurations as needed. ## Why & when Re.Pack is particularly valuable in these scenarios: ### 1. **Microfrontends with Module Federation** Re.Pack provides first-class support for Module Federation, enabling you to build microfrontend architectures where different teams can develop and deploy parts of your mobile app independently. ### 2. **Advanced Tree Shaking** Leverage state-of-the-art tree shaking capabilities that can significantly reduce bundle size by eliminating unused code more effectively than Metro, helping you save valuable bytes in your final application bundle. ### 3. **High Customizability** Access to the full Rspack/webpack ecosystem means extensive customization options through loaders, plugins, and advanced configuration. You can implement complex build requirements that aren't possible with Metro. ### 4. **Build Performance** Rspack's Rust-based architecture provides significant performance improvements for large codebases, especially when combined with advanced caching strategies. Re.Pack is designed for teams that need these advanced capabilities and have developers willing to familiarize themselves with alternative bundling tools. :::tip Don't drop Metro just yet! If you're just starting with React Native, consider sticking with Metro - the default bundler for React Native applications. Re.Pack shines when you have specific requirements that Metro cannot address but is not compatible with tools that assume Metro as a bundler present in the project setup. ::: --- url: /docs/getting-started/quick-start.md --- # Quick start ## Pre-requisites If you're already familiar with JavaScript, React Native and webpack, then you'll be able to get moving quickly! Otherwise, it's highly recommended to get yourself familiar with these topic and then come back here: - [React Native documentation](https://reactnative.dev/) - [Webpack concepts](https://webpack.js.org/concepts/) ## Minimum requirements - Node >= 20 - React Native >= 0.77.0 :::note On React Native Version The absolute minimum required version of React Native is 0.74.0, which contains fixes enabling proper SWC transpilation. While versions lower than 0.74 might work, they will most likely only function with Webpack. ::: :::info Version Support Policy Re.Pack is designed to work with the latest React Native version and maintains backward compatibility with the two previous versions. While older versions might work, they are not officially supported. If you're interested in fixing compatibility issues for older versions, feel free to open a PR! ::: ## Installation To create a new React Native project with Re.Pack or adapt an existing one: ```sh [npx] npx @callstack/repack-init ``` ```sh [yarn] yarn dlx @callstack/repack-init ``` ```sh [pnpm] pnpm dlx @callstack/repack-init ``` ```sh [bunx] bunx @callstack/repack-init ``` In case the command above didn't work for any reason, you can follow the manual migration guide [here](/docs/migration-guides/metro.md). :::tip The defaults work well for most projects If you need custom settings (like additional loaders or plugins), check out our [configuration guide](/docs/guides/configuration.md). ::: ## Usage Now that you have Re.Pack configured, you should be able to use Re.Pack's development server and bundle your application. ### Running development server When developing your application, you want to run Re.Pack's development server to compile your source code with Rspack/webpack. To start the development server, you can use React Native Community CLI and run: ```sh [npm] npm run react-native start ``` ```sh [yarn] yarn react-native start ``` ```sh [pnpm] pnpm react-native start ``` ```sh [bun] bun react-native start ``` To learn about `start` command, check out the [start command documentation](/api/cli/start.md). ### Bundling the app When you are building the release version of your application, Re.Pack will be picked up automatically as the bundler. In case you want to create bundle manually, **the recommended way is to use React Native Community CLI** and run: ```sh [npm] npm run react-native bundle ``` ```sh [yarn] yarn react-native bundle ``` ```sh [pnpm] pnpm react-native bundle ``` ```sh [bun] bun react-native bundle ``` To learn about `bundle` command, check out the [bundle command documentation](/api/cli/bundle.md). :::tip 🎉 Congratulations! You've successfully set up Re.Pack in your project. We highly recommend to check out the following: - [Configuration Guide](/docs/guides/configuration.md) to learn how to configure Re.Pack to your project needs. - [API Reference](/api/index.md) to learn more about Re.Pack's API. - [Deployment of MiniApps](/docs/guides/deploy.md) to learn how to deploy your federated MiniApps with Zephyr Cloud. ::: --- url: /docs/getting-started/microfrontends.md --- # Microfrontends Microfrontends (MFEs) are an architectural approach that breaks down a web application’s frontend into smaller, independently deployable pieces that can be downloaded on demand. Think of them as the frontend equivalent of microservices: instead of one massive, tangled codebase, you get modular chunks that different teams can own, develop, and ship on their own timelines. It's important to note that we're not talking about producing a single JS bundle from multiple independent teams. It's about producing many JS bundles that can be later downloaded on demand by a mobile app. While web MFEs and microservices are often about using independent tech stacks, it's different on mobile iOS and Android ecosystems due to platform constraints and app store rules that typically prevent loading any compiled code. Re.Pack is largely designed to enable microfrontends on mobile. It's one of the key differences between other React Native bundlers, such as Metro, which don't support this architecture out of the box. ## Use Cases Mobile microfrontends shine in scenarios where complexity and team size grow beyond what a monolith can handle. Here are some use cases we found compelling for this archiecture: - **Strong Team Boundaries**: If you’ve got separate teams working on different parts of an app (say, one for product listing, another for user settings), and often at different geographical locations or even company divisions, MFEs let each team own their domain end-to-end without stepping on each other’s toes. - **Independent Deployments**: While React Native allows for over-the-air updates, MFEs offer a more targeted deployments possibility, instead of replacing the whole JavaScript bundle like all OTA solutions out there. - **Super Apps**: When building a mobile super app with features loaded on demand, MFEs let you ship lightweight containers that pull in functionality as users need it, without them to be available in the initial app bundle, reducing the overall app size. It's crucial to be sure about what you want to achieve with microfrontends. Adopting this architecture, as with any other engineering design choice, comes with its own complexity. Make sure the trade-offs are worth it. And avoid using microforntends because it's some trend to follow. ## Module Federation Re.Pack makes it possible to use Rspack, leveraging whole webpack ecosystem. Thanks to that you can use one of the key features built around this ecosystem, which was designed to solve common pains working with microfrontends: Module Federation. Module Federation is an architectural pattern for the decentralization of JavaScript applications. It allows you to share code and resources among multiple JavaScript applications (or microfrontends). Re.Pack supports Module Federation since v3 in its own flavor, that was different from using it on the web. However, since then, the Module Federation 2 was released, which adds dynamic type hinting, Manifest, Federation Runtime, and Runtime Plugin System. These new features allowed us to better integrate this architecture into React Native runtime—which differs from the web—while retaining the same API that web developers are used to. ## Limitations While Module Federation 2 enables Re.Pack to use the same conventions and APIs as on the web, mobile microfrontends are more limited due to native platform and app stores constraints: - You must use the same React, React Native and native dependencies versions across all MFEs - All native modules need to be available in the host application (the one that is released to app stores) - You can only dynamically load JavaScript code from a microfrontend – this code can reference and use native modules that are bundled within the host app ## Challenges One of the complexities of microfrontends is version management. It’s challenging to handle on your own and can be quite cumbersome based on our experience. If you’re open to third-party services, we recommend Zephyr Cloud, which simplifies this problem, allows for sub-second deploys, and officially [integrates with Re.Pack](https://docs.zephyr-cloud.io/recipes/repack-mf) in a form of a Rspack/webpack plugin. ## Examples Below you can find open source examples of how you can set up Re.Pack with microfrontends: - [Super App Showcase](https://github.com/callstack/super-app-showcase) – MFEs versioned using a custom script - [Zephyr Re.Pack Example](https://github.com/ZephyrCloudIO/zephyr-repack-example) – MFEs versioned using Zephyr Cloud --- url: /docs/features/module-resolution.md --- # Module resolution Module resolution is the process by which a bundler determines which file to load when you import a module. In React Native, this process has unique requirements—platform-specific files, scaled assets, and the `react-native` condition in package exports all need special handling. Re.Pack is designed to match [Metro's resolution behavior](https://metrobundler.dev/docs/resolution) as closely as possible, ensuring that projects migrating from Metro or using libraries designed for Metro work correctly. Getting module resolution right ensures that: - Platform-specific files (`.ios.js`, `.android.js`) load correctly for each target - Libraries with React Native-specific entry points work as expected - Scaled assets (`@2x`, `@3x` images) resolve properly ## How Re.Pack resolves modules Re.Pack configures the underlying bundler's resolver to match Metro's behavior. When using Rspack, resolution is handled by [rspack-resolver](https://github.com/unrs/rspack-resolver)—a Rust port of [enhanced-resolve](https://github.com/webpack/enhanced-resolve) with the same interface. When using webpack, the original enhanced-resolve is used. The [`getResolveOptions()`](/api/utils/get-resolve-options.md) utility returns the configuration needed to match Metro's resolution behavior: ```ts import * as Repack from "@callstack/repack"; export default (env) => { return { resolve: { ...Repack.getResolveOptions(), }, }; }; ``` Resolution happens per-platform—each build (iOS, Android, etc.) runs as a separate bundler process with platform-specific resolution configured automatically based on the target platform. ## Platform-specific resolution React Native allows you to create platform-specific versions of files using special extensions. Re.Pack resolves these extensions in the following order: 1. `.{platform}.{ext}` (e.g., `.ios.js`, `.android.js`) 2. `.native.{ext}` (when `preferNativePlatform: true`, which is the default) 3. `.{ext}` (base extension) ### Example Given a request for `./component` on iOS, Re.Pack will look for files in this order: ``` component.ios.js → Platform-specific (highest priority) component.native.js → Native fallback component.js → Base file (lowest priority) ``` The same request on Android: ``` component.android.js → Platform-specific (highest priority) component.native.js → Native fallback component.js → Base file (lowest priority) ``` ### Supported source extensions Re.Pack supports these source file extensions by default: - `.js`, `.jsx` - `.ts`, `.tsx` - `.json` Each of these can be combined with platform and native extensions: ``` .ios.js, .ios.jsx, .ios.ts, .ios.tsx, .ios.json .native.js, .native.jsx, .native.ts, .native.tsx, .native.json .android.js, .android.jsx, .android.ts, .android.tsx, .android.json ``` ### Disabling native extension fallback If you don't want `.native.*` files to be resolved as fallbacks, set `preferNativePlatform` to `false`: ```ts Repack.getResolveOptions({ preferNativePlatform: false, }); ``` :::tip This is useful when building for non-native platforms (like web) where `.native.*` files might contain React Native-specific code that won't work in a browser. ::: With this setting, a request for `./component` on a `web` platform would only check: ``` component.web.js → Platform-specific component.js → Base file (no .native.js fallback) ``` ## Package resolution (main fields) :::info What are main fields? Main fields are `package.json` properties that point to a package's entry file. Packages can specify different entry points for different environments—`main` for Node.js, `browser` for web, `react-native` for React Native. ::: When resolving a package's entry point, Re.Pack checks these fields in `package.json` in order: 1. `react-native` — React Native-specific entry point 2. `browser` — Browser-compatible entry point 3. `main` — Standard Node.js entry point This matches Metro's default configuration and ensures React Native-optimized code is preferred. ### Example Given this `package.json`: ```json { "name": "some-library", "main": "lib/index.js", "browser": "lib/browser.js", "react-native": "lib/native.js" } ``` Re.Pack will resolve to `lib/native.js` because `react-native` has the highest priority. :::tip For more details on how main fields work, see the [resolve.mainFields](https://rspack.dev/config/resolve#resolvemainfields) documentation. ::: ## Package exports (conditional exports) :::info What are package exports? Package exports (`exports` field in `package.json`) are a modern replacement for main fields. They let packages define multiple entry points (e.g., `pkg/utils`), serve different code per environment, and hide internal files. See the [Node.js docs](https://nodejs.org/api/packages.html#package-entry-points) for details. ::: Modern packages use the `exports` field in `package.json` to define entry points with conditions. Re.Pack supports this through the `enablePackageExports` option. :::caution Package exports support is **disabled by default** (`enablePackageExports: false`) to maintain backwards compatibility with existing React Native projects. Enable it explicitly if your dependencies require it. ::: ### Enabling package exports ```ts Repack.getResolveOptions({ enablePackageExports: true, }); ``` ### How conditional exports work When enabled, Re.Pack uses the `react-native` condition to resolve packages. The resolver also differentiates between ESM and CommonJS: - **ESM imports**: Uses conditions `['react-native', 'import']` - **CommonJS requires**: Uses conditions `['react-native', 'require']` :::tip These ESM/CJS conditions are always configured (via `byDependency`) to support [package imports](https://nodejs.org/api/packages.html#subpath-imports), even when `enablePackageExports` is `false`. ::: ### Example package with exports ```json { "name": "modern-library", "exports": { ".": { "react-native": "./dist/native/index.js", "import": "./dist/esm/index.js", "require": "./dist/cjs/index.js", "default": "./dist/index.js" }, "./utils": { "react-native": "./dist/native/utils.js", "import": "./dist/esm/utils.js", "require": "./dist/cjs/utils.js" } } } ``` With `enablePackageExports: true`, importing this package in React Native will resolve to the `react-native` condition entry points. :::warning Some packages may have `exports` configurations that work differently than their `main`/`react-native` field configurations. Test thoroughly when enabling this option in existing projects. ::: ## Asset resolution Re.Pack handles scaled assets (images with `@1x`, `@2x`, `@3x` suffixes) automatically using `extensionAlias` configuration. ### Supported scalable assets Images that support resolution scaling: - `bmp`, `gif`, `jpg`, `jpeg`, `png`, `psd`, `svg`, `webp`, `tiff` ### How scaled resolution works When you import an image like `./icon.png`, Re.Pack's `extensionAlias` configuration allows the resolver to find scaled variants: ``` icon@0.75x.png icon@1x.png icon@1.5x.png icon@2x.png icon@3x.png icon@4x.png icon.png ``` The actual asset selection (choosing the right scale for the device) happens at runtime through React Native's asset system. ```jsx // Import resolves to the appropriate scaled variant import icon from './assets/icon.png'; // React Native selects the correct scale at runtime ``` ### Other supported asset types Re.Pack also supports these non-scalable asset types: - **Video**: `m4v`, `mov`, `mp4`, `mpeg`, `mpg`, `webm` - **Audio**: `aac`, `aiff`, `caf`, `m4a`, `mp3`, `wav` - **Documents**: `html`, `pdf`, `yaml`, `yml` - **Fonts**: `otf`, `ttf` - **Other**: `zip`, `obj` ## Troubleshooting Most resolution issues can be solved with two configuration options: - **`resolve.alias`** — Redirect imports to a different module or file - **`module.rules`** — Control how specific modules are processed ```ts export default (env) => { return { resolve: { alias: { // Redirect problematic package to a compatible version 'legacy-package': 'legacy-package/dist/react-native', }, }, module: { rules: [ { // Force specific files through a loader test: /problematic-module/, use: 'babel-loader', }, ], }, }; }; ``` ### Package not resolving correctly 1. **Check the package's `package.json`** — Look at `main`, `react-native`, `browser`, and `exports` fields 2. **Verify platform extensions** — Ensure platform-specific files use correct naming (`.ios.js`, not `.iOS.js`) 3. **Check `enablePackageExports`** — Some modern packages require this to be `true` ### Platform-specific files not being picked up 1. **Verify file naming** — Extensions must be lowercase (`.ios.js`, not `.IOS.js`) 2. **Check the platform value** — Ensure you're building for the correct target platform 3. **Inspect resolve configuration** — Log the output of `getResolveOptions()` to verify extensions order ### Package exports compatibility issues When enabling `enablePackageExports`, some packages may resolve differently: 1. **Compare with Metro** — Test the same import in a Metro-bundled project 2. **Check condition order** — The `react-native` condition should take precedence 3. **Inspect the package** — Some packages have incorrect or incomplete `exports` configurations ## Related documentation - [getResolveOptions](/api/utils/get-resolve-options.md) — API reference - [Rspack resolve configuration](https://rspack.dev/config/resolve) — Rspack resolver options - [webpack resolve configuration](https://webpack.js.org/configuration/resolve/) — webpack resolver options - [Code Splitting](/docs/features/code-splitting.md) — For chunk resolution - [Glossary](/docs/resources/glossary.md) — Terminology reference --- url: /docs/features/code-splitting.md --- # Code splitting Code Splitting is a technique that splits the code into multiple files, which can be loaded on demand and in parallel. It can be used to: - Optimize the initial size of the application and to improve the startup performance by deferring the parsing (only with JSC) and execution (JSC and Hermes) of the non-critical code. - Dynamically deliver content and features to the users based on runtime factors: user's role, subscription plan, preferences etc. - **For developers and companies**: split and isolate pieces of the product to improve scalability and reduce coupling. Code Splitting is one of the most important features in Re.Pack, and it's based on Webpack's infrastructure as well as the native module that allows to execute the additional code on the same JavaScript context (same React Native instance). :::info For dynamic feature delivery, Code Splitting should be used as a mean to optimize the user experience by deferring the features or deliver existing features only to a subset of users. ::: Code Splitting with Re.Pack is not designed to add new features dynamically without doing the regular App Store or Play store release. It can be used to deliver fixes or tweaks to additional (split) code, similarly to Code Push, but you should not add new features with it. :::caution Using Code Splitting to deliver new features without a regular App Store release is likely going to violate Apple's App Store Terms and your application might be rejected or banned. ::: :::tip You should provide access to all the features for the App Store review process. Also, it might be beneficial to highlight that all split features are closely integrated with application and cannot work in isolation - you don't want to introduce confusion that your application might compete with Apple's App Store. On that note, you might want to avoid using terms like _mini-app_ or _mini-app store_ in favour of _modules_, _components_, _plugins_ or simply _features_. ::: ## Usage The specific implementation of Code Splitting in your application can be different and should account for your project's specific needs, requirements and limitations. In general, we can identify 3 main categories of implementation. All of those approaches are based on the same underlying mechanism: Re.Pack's [`ScriptManager`](/api/runtime/script-manager.md) and the native module for it. :::tip Use [Glossary of terms](/docs/resources/glossary.md) to better understand the content of this documentation. ::: ### Generic usage On a high-level, all functionalities that enable usage of Webpack's Code Splitting, are powered by Re.Pack's [`ScriptManager`](/api/runtime/script-manager.md), which consists of the JavaScript part and the native part. The [`ScriptManager`](/api/runtime/script-manager.md) has methods which allows to: 1. Download and execute script - [`loadScript`](/api/runtime/script-manager.md#loadscript) 2. Prefetch script (without executing immediately) - [`prefetchScript`](/api/runtime/script-manager.md#prefetchscript) 3. Resolve script location - [`resolveScript`](/api/runtime/script-manager.md#resolvescript) 4. Invalidate cache - [`invalidateScripts`](/api/runtime/script-manager.md#invalidatescripts) In order to provide this functionalities, a resolver has to be added using [`ScriptManager.shared.addResolver`](/api/runtime/script-manager.md#addresolver): ```ts import { ScriptManager, Script } from "@callstack/repack/client"; ScriptManager.shared.addResolver(async (scriptId, caller) => { // In dev mode, resolve script location to dev server. if (__DEV__) { return { url: Script.getDevServerURL(scriptId), cache: false, }; } return { url: Script.getRemoteURL( `http://somewhere-on-the-internet.com/${scriptId}` ), }; }); ``` If the `storage` is provided, the returned `url` from `resolve` will be used for cache management. You can read more about it in [Caching and Versioning](#caching-and-versioning). :::info Do not instantiate `ScriptManager` yourself - use `ScriptManager.shared` to get access to an instance. ::: Under the hood, the way a script gets loaded can be summarized as follows: 1. `ScriptManager.shared.loadScript(...)` gets called, either: - Automatically by the dynamic `import(...)` function handled by Webpack, when using [Async chunks approach](#async-chunks) - Manually when using [Scripts approach](#scripts) or [Module Federation](#module-federation) 2. `ScriptManager.shared.loadScript(...)` is called `scriptId` and `caller` arguments, which are either provided by: - Webpack, based on it's internal naming logic or a [magic comment: `webpackChunkName`](https://webpack.js.org/migrate/5/#using--webpackchunkname--) - Manually 3. `ScriptManager.shared.loadScript(...)` resolves the chunk location using `ScriptManager.shared.resolveScript(...)`. 4. The resolved location is compared against previous location of that script, if and only if, `storage` was provided and the script was resolved before. 5. The resolved location is passed to the native module, which downloads if necessary and executes the script. 6. Once the code has been executed the `Promise` returned by `ScriptManager.shared.loadScript(...)` gets resolved. :::info [`ScriptManager.shared.prefetchScript(...)`](/api/runtime/script-manager.md#prefetchscript) follows the same behavior except for #6, where it only downloads the file and doesn't execute it. ::: ### Approaches There are generally 3 approaches to Code Splitting with Webpack and Re.Pack. Keep in mind that the actual code you will have to create might be slightly different, depending on your project's requirements, needs and limitations. Those approaches should be used as a base for your Code Splitting implementation. :::tip It's recommended to read [Generic usage](#generic-usage) first, to understand it on a high-level and get the necessary context. ::: #### Async chunks Async chunks (or asynchronous chunks) are the easiest Code Splitting approach. They are usually created by using dynamic `import(...)` function, which makes them extremely easy to introduce it into the codebase. The async chunks are created alongside the main bundle as part of a single Webpack compilation, making it a great choice for a modular applications where all the code is developed in-house. The usage of async chunks essentially boils down to calling `import(...)` in your code, for example: ```js const myChunk = await import("./myChunk.js"); ``` Async chunks created by dynamic `import(...)` function can be nicely integrated using `React.lazy` and `React.Suspense`: ```jsx // MyChunk.js export default function MyChunk(props) { return /* ... */; } // App.js const MyChunk = React.lazy(() => import("./MyChunk.js")); function App() { return ( Loading...}> ); } ``` For each file in the dynamic `import(...)` function a new chunk will be created - those chunks will be remote chunks by default. :::tip You can learn more about local and remote chunks in the dedicated [Local vs Remote chunks guide](#local-vs-remote-chunks). ::: :::tip To learn more or use async chunks in your project, check out our [dedicated Async chunks guide](#guide-async-chunks). ::: :::tip To see `import(...)`, `React.lazy` and `React.Suspense` in action, check out [Re.Pack's `TesterApp`](https://github.com/callstack/repack/blob/main/apps/tester-app/src/asyncChunks/AsyncContainer.tsx). ::: :::caution Don't forget to add resolver using [`ScriptManager.shared.addResolver`](/api/runtime/script-manager.md#addresolver)! ::: #### Scripts This approach allows to execute arbitrary code in your React Native application. It's a similar concept as adding a new `