Understanding System Bundles in Apps for Amazon Fire TV
Aug 27th 2025, by @cebratec, @Abhay_Jain, @Christian_Van_Boven1
Modern apps built on React Native often ship with large monolithic JavaScript bundles. These bundles include everything - from core React libraries to app-specific business logic. While convenient, this approach creates challenges: bigger app sizes, slower startup, and more friction when delivering bug fixes.
To address this, Vega introduces a split bundle system, where common libraries live on the system and apps only ship the code that’s unique to them. Let’s explore what this means, why it matters, and how it works behind the scenes.
What is a Split Bundle?
Traditionally, a React Native app ships a single App Bundle containing:
- Core libraries like
reactandreact-native - Vega-specific modules
- Third-party libraries
- Your app’s code
With split bundles, the app bundle is divided into two parts:
1/ System (Common) Bundle
Pre-installed on the device, this contains widely used libraries such as react, react-native, and @amzn/react-native-vega. For details, see system distributed libraries. It is kept in sync with the native Vega runtime.
2/ Split App Bundle
Your app’s bundle, minus the common libraries. This contains only app-specific code and any unique dependencies.
How the transformation works
Below is a simplified view of how a monolithic bundle is split:
┌────────────────────────────┐
│ Monolithic App Bundle │
│ (React, RN, Vega, App) │
└────────────────────────────┘
│
▼
┌────────────────────┐ ┌─────────────────────┐
│ System Bundle │ │ Split App Bundle │
│ (React, RN, Vega)│ │ (App-specific code) │
└────────────────────┘ └─────────────────────┘
At runtime, Vega loads the system bundle first, then stitches in your split app bundle.
Why Split Bundles Matter
Split bundles bring several key advantages:
Faster startup times (TTFD)
Core libraries are preloaded by the system, reducing app initialization time by ~100–150ms.
Smaller app size
By removing common libraries from your app package, you reduce the size by ~1MB or more.
Automatic upgrades and bug fixes
Since system libraries update independently, apps get bug fixes and performance improvements without requiring a new app release.
Consistent sync between native and JS
Both JS and native components live in the same system version, ensuring compatibility.
How the Split Bundle System Works
Behind the scenes, the magic relies on extending Metro, the JavaScript bundler used by React Native. Normally, Metro produces a single index.bundle with all your app’s code and dependencies. To enable splitting, Vega customizes Metro’s Serializer stage, which controls how modules are combined into bundles.
Two key hooks make this possible:
1/ processModuleFilter
- Filters out modules already provided by the system bundle.
- Ensures that app bundles don’t duplicate libraries like
reactorreact-native.
2/ createModuleIdFactory
- Assigns consistent, deterministic IDs to modules.
- This is critical because system and app bundles must reference the same module IDs when sharing code.
- Instead of Metro’s default “incremental +1” IDs, Vega uses a hash-based scheme (e.g. SHA256 of the module path) to guarantee stability across builds.
Build time Runtime
──────────────────► ───────────────►
┌─────────────┐ ┌──────────────┐ uses IDs in ┌───────────────┐
│ Source │─► │ Core Bundle │───────────────► │ Load Core │
│ (app + lib)│ │ (React, RN, │ │ (system) │
└─────────────┘ │ Vega ) │ └───────────────┘
└─────┬────────┘
│ writes modules.txt
▼
┌──────────────┐ uses IDs in ┌───────────────┐
│ Library Bndl │───────────────► │ Load Libraries│
│ (e.g. RNScrn)│ │ (system) │
└─────┬────────┘ └───────────────┘
│ reuses IDs / filters
▼
┌──────────────┐ ┌───────────────┐
│ App Bundle │───────────────► │ Load App │
│ (split) │ │ (on top) │
└──────────────┘ └───────────────┘
Types of Bundles
The split bundle system creates three main bundle types:
1st - Core Bundle (considered system)
- Contains fundamental libraries (
react,react-native,@amzn/react-native-vega). - Generated first, producing a
modules.txtfile mapping module paths to IDs. - This mapping ensures consistent referencing in dependent bundles.
2nd - Library Bundles (considered system)
- For popular libraries like
react-native-reanimatedorreact-native-screens. - Created using the
modules.txtfrom core to avoid duplicating already-bundled dependencies. - Additional
modules.txtfiles are generated for each library.
3rd - Application Bundle (Split App Bundle)
- Your app’s code, filtered to exclude anything already included in system bundles.
- Still references system-provided modules via the shared IDs.
At build time, metadata (e.g. keplerscript-app-system-bundles-config.json) is generated, telling Vega which system bundles your app requires. At runtime, the Vega runtime first loads the system bundles, then attaches the app bundle on top.
Handling Dependencies and Versions
A major challenge in split bundling is dependency versioning:
- If the system bundle has
react-native-screens@2.0.0but your app requires2.1.0, conflicts can arise. - To manage this, Vega uses versioned module paths internally (e.g.
react-native-screens__2/). - Only major versions are encoded into paths (e.g.
__2/), so bugfix upgrades (2.0.0 → 2.0.1) don’t break compatibility or force app rebuilds.
Additionally, when filtering dependencies:
- System bundles include their own dependencies (e.g.
lodash) but do not list them inmodules.txt. - This ensures that if an app also depends on the same version of
lodash, it’s still present in the app bundle, preventing accidental breakages when system libraries upgrade.
Same major, safe upgrade:
@amzn/react-native-screens__2/... (2.0.0 → 2.0.1 keeps the same path)
Different major, intentionally distinct:
@amzn/react-native-screens__3/...
This approach, combined with stable IDs, lets the system update frequently while apps remain compatible.
While this versioning strategy currently serves our needs well, we may explore additional optimization opportunities in the future. Any changes to the versioning scheme would be carefully planned and communicated to maintain backward compatibility.
Lifecycle of a System Bundle Build
Here’s the full lifecycle of a split bundle build:
- Core bundle is created (React, RN, Vega).
- Library bundles are created using dependency-aware filtering.
- App bundle is created with all system-provided modules removed.
- Metadata is generated listing which system bundles are required.
- At runtime, Vega loads system bundles first, then the app bundle.
The result is a modular system where apps stay small, fast, and resilient to library updates.
Split bundles are a powerful way to modernize JavaScript delivery for Vega apps. The users benefit from smaller and faster apps, developers get system bug fixes and performance improvements without re-releasing and system and app code stay in sync, reducing compatibility issues.
As this ecosystem matures, we’ll continue expanding support for more libraries in the system bundle while improving developer tooling around bundle creation and conflict resolution.
