# Bundling

## Metro bundler

Learn about different Metro bundler configurations that can be customized.

Expo CLI uses [Metro](https://metrobundler.dev/) during [`npx expo start`](/more/expo-cli/#develop) and [`npx expo export`](/more/expo-cli/#exporting) to bundle your JavaScript code and assets. Metro is built and optimized for React Native and used for large-scale applications such as Facebook and Instagram.
## Customizing
You can customize the Metro bundler by creating a **metro.config.js** file at the root of your project. This file should export a [Metro configuration](https://metrobundler.dev/docs/configuration/) that extends [`expo/metro-config`](https://github.com/expo/expo/tree/main/packages/@expo/metro-config). Import `expo/metro-config` instead of `@expo/metro-config` to ensure version consistency.
Run the following command to generate the template file:
```sh
$ npx expo customize metro.config.js
```
The **metro.config.js** file looks as below:
```js metro.config.js
const { getDefaultConfig } = require('expo/metro-config');
const config = getDefaultConfig(__dirname);
module.exports = config;
```
See [**metro.config.js** documentation](https://metrobundler.dev/docs/configuration/) for more information.
## Assets
Metro resolves files as either source code or assets. Source code is JavaScript, TypeScript, JSON, and other files used by your application. [Assets](/develop/user-interface/assets/) are images, fonts, and other files that should not be transformed by Metro. To accommodate large-scale codebases, Metro requires all extensions for both source code and assets to be explicitly defined before starting the bundler. This is done by adding the `resolver.sourceExts` and `resolver.assetExts` options to the Metro configuration. By default, the following extensions are included:
- [`resolver.assetExts`](https://github.com/facebook/metro/blob/7028b7f51074f9ceef22258a8643d0f90de2388b/packages/metro-config/src/defaults/defaults.js#L15)
- [`resolver.sourceExts`](https://github.com/facebook/metro/blob/7028b7f51074f9ceef22258a8643d0f90de2388b/packages/metro-config/src/defaults/defaults.js#L53)
### Adding more file extensions to `assetExts`
The most common customization is to include extra asset extensions to Metro.
In the **metro.config.js** file, add the file extension (without a leading `.`) to `resolver.assetExts` array:
```js metro.config.js
const { getDefaultConfig } = require('expo/metro-config');
const config = getDefaultConfig(__dirname);
config.resolver.assetExts.push(
  // Adds support for `.db` files for SQLite databases
  'db'
);
module.exports = config;
```
## Aliases
Sometimes you want an import to be redirected to another module or file. This is called an alias. Due to the way Metro bundles for multiple platforms simultaneously, we recommend using a custom resolver to handle aliases.
In the following example, we'll add an alias for `old-module` to `new-module`:
```js metro.config.js
const { getDefaultConfig } = require('expo/metro-config');
/** @type {import('expo/metro-config').MetroConfig} */
const config = getDefaultConfig(__dirname);
const ALIASES = {
  'old-module': 'new-module',
};
config.resolver.resolveRequest = (context, moduleName, platform) => {
  // Ensure you call the default resolver.
  return context.resolveRequest(
    context,
    // Use an alias if one exists.
    ALIASES[moduleName] ?? moduleName,
    platform
  );
};
module.exports = config;
```
If you want to only apply the alias on a certain platform, you can check the `platform` argument:
```js metro.config.js
config.resolver.resolveRequest = (context, moduleName, platform) => {
  if (platform === 'web') {
    // The alias will only be used when bundling for the web.
    return context.resolveRequest(context, ALIASES[moduleName] ?? moduleName, platform);
  }
  // Ensure you call the default resolver.
  return context.resolveRequest(context, moduleName, platform);
};
```
You will see the changes the next time you restart the dev server. Resolutions are never cached and do not need the `--clear` flag to update. If you use a transform-based system like `babel-plugin-module-resolver`, you will need to clear the cache to see changes applied.
## Bundle splitting
Expo CLI automatically splits bundles based on async imports (web-only).
This technique can be used with Expo Router to automatically split the bundle based on route files in the **app** directory. It will only load the code required for the current route, and defer loading additional JavaScript until the user navigates to different pages. See [Async Routes](/router/web/async-routes) for more information.
## Tree shaking
## Minification
## Web support
Expo CLI has support for bundling websites using Metro. This is the same bundler used for native apps, and it is designed to be universal across platforms. It is the recommended bundler for web projects.
### Expo webpack versus Expo Metro
If you previously wrote your website using the deprecated `@expo/webpack-adapter`, see the [migration guide](/router/migrate/from-expo-webpack) and [comparison chart](/router/migrate/from-expo-webpack#expo-cli).
### Adding Web support to Metro
Modify your [app config](/workflow/configuration) to enable the feature using the `expo.web.bundler` field:
```json app.json
{
  "expo": {
    "web": {
      "bundler": "metro"
    }
  }
}
```
#### Development
To start the development server run the following command:
```sh
$ npx expo start --web
```
Alternatively, press <kbd>W</kbd> in the Expo CLI terminal UI.
#### Static files
Expo's Metro implementation supports hosting static files from the dev server by putting them in the root **public/** directory. It is similar to many other web frameworks.
When exporting with `npx expo export`, the contents of the **public** directory are copied into the **dist/** directory. It means your app can expect to fetch these assets relative to the host URL. The most common example of this is the **public/favicon.ico** which is used by websites to render the tab icon.
You can overwrite the default **index.html** in Metro web by creating a **public/index.html** file in your project.
In the future, this will work universally across platforms with EAS Update hosting. Currently, the feature is web-only based on the static host used for the native app, for example, the legacy Expo service updates do not support this feature.
## TypeScript
Expo's Metro config supports the `compilerOptions.paths` and `compilerOptions.baseUrl` fields in the project's **tsconfig.json** (or **jsconfig.json**) file. This enables absolute imports and aliases in the project. See [TypeScript](/guides/typescript) guide for more information.
This feature requires additional setup in bare projects. See the [Metro setup guide](/versions/latest/config/metro#bare-workflow-setup) for more information.
## CSS


## Analyzing JavaScript bundles with Expo Atlas and Lighthouse

Learn about improving the production JavaScript bundle size of Expo apps and websites with Expo Atlas and Lighthouse.

Bundle performance varies for different platforms. For example, web browsers don't support precompiled bytecode, so the JavaScript bundle size is important for improving startup time and performance. The smaller the bundle, the faster it can be downloaded and parsed.
## Analyzing bundle size with Expo Atlas
The libraries used in a project influence the size of the production JavaScript bundle. You can use [Expo Atlas](https://github.com/expo/expo-atlas#readme) to visualize the production bundle and identify which libraries contribute to the bundle size.
### Using Atlas with `npx expo start`
You can use Expo Atlas with the local development server. This method allows Atlas to update whenever you change any code in your project.
Once your app is running using the local development server on Android, iOS, and/or web, you can open Atlas through the [dev tools plugin menu](/debugging/devtools-plugins/#using-a-dev-tools-plugin) using <kbd>shift</kbd> + <kbd>m</kbd>.
```sh
$ EXPO_ATLAS=true npx expo start
```
#### Changing development mode to production
By default, Expo starts the local development server in [development mode](/workflow/development-mode/#development-mode). Development mode disables some optimizations that are enabled in [production mode](/workflow/development-mode/#production-mode). You can also start the local development server in production mode to get a more accurate representation of the production bundle size:
```sh
$ EXPO_ATLAS=true npx expo start --no-dev
```
### Using Expo Atlas with `npx expo export`
You can also use Expo Atlas when generating a production bundle for your app or EAS Update. Atlas generates a **.expo/atlas.jsonl** file during export, which you can share and open without having access to the project.
```sh
$ EXPO_ATLAS=true npx expo export
$ npx expo-atlas .expo/atlas.jsonl
```
You can also specify the platforms you want to analyze using the `--platform` option. Expo Atlas will gather the data for the exported platforms only.
### Analyzing transformed modules
Inside Atlas, you can hold <kbd>â Cmd</kbd> and click on a graph node to see the transformed module details. This feature helps you understand how a module is transformed by Babel, which modules it imports, and which modules imported it. This can be used to trace the origin of a module across the dependency graph.
## Analyzing bundle size with source-map-explorer
> Alternative method for **SDK 50 and earlier**.
If you are using SDK 50 or below, you can use the [`source-map-explorer`](https://www.npmjs.com/package/source-map-explorer) library to visualize and analyze the production JavaScript bundle.
Step 1: 
To use source map explorer, run the following command to install it:
```sh
$ npm i --save-dev source-map-explorer
```
Step 2: 
Add a script to **package.json** to run it. You might have to adjust the input path depending on the platform or SDK you are using. For brevity, the following example assumes the project is Expo SDK 50 and does not use Expo Router `server` output.
```json package.json
{
  "scripts": {
    "analyze:web": "source-map-explorer 'dist/_expo/static/js/web/*.js' 'dist/_expo/static/js/web/*.js.map'",
    "analyze:ios": "source-map-explorer 'dist/_expo/static/js/ios/*.js' 'dist/_expo/static/js/ios/*.js.map'",
    "analyze:android": "source-map-explorer 'dist/_expo/static/js/android/*.js' 'dist/_expo/static/js/android/*.js.map'"
  }
}
```
If you are using the SDK 50 `server` output for web, then use the following to map web bundles:
```sh
$ npx source-map-explorer 'dist/client/_expo/static/js/web/*.js' 'dist/client/_expo/static/js/web/*.js.map'
```
Web bundles are output to the **dist/client** subdirectory to prevent exposing server code to the client.
Step 3: 
Export your production JavaScript bundle and include the `--source-maps` flag so that the source map explorer can read the source maps. For native apps using Hermes, you can use the `--no-bytecode` option to disable bytecode generation.
```sh
$ npx expo export --source-maps --platform web
$ npx expo export --source-maps --platform ios --no-bytecode
```
This command shows the JavaScript bundle and source map paths in the output. In the next step, you will pass these paths to the source map explorer.
> Avoid publishing source maps to production as they can cause both security issues and performance issues (a browser will download the large maps).
Step 4: 
Run the script to analyze your bundle:
```sh
$ npm run analyze:web
```
On running this command, you might see the following error:
```text
You must provide the URL of lib/mappings.wasm by calling SourceMapConsumer.initialize({ 'lib/mappings.wasm': ... }) before using SourceMapConsumer
```
This is probably due to a [known issue](https://github.com/danvk/source-map-explorer/issues/247) in `source-map-explorer` in Node.js 18 and above. To resolve this, set the environment variable `NODE_OPTIONS=--no-experimental-fetch` before running the analyze script.
You might encounter a warning such as `Unable to map 809/13787 bytes (5.87%)`. This occurs because source maps often exclude bundler runtime definitions (for example, `__d(() => {}, [])`). This value is consistent and not a reason for concern.
## Lighthouse
Lighthouse is a great way to see how fast, accessible, and performant your website is. You can test your project with the **Audit** tab in Chrome, or with the [Lighthouse CLI](https://github.com/GoogleChrome/lighthouse#using-the-node-cli).
After creating a production build with `npx expo export -p web` and serving it (using either `npx serve dist`, or production deployment, or custom server), run Lighthouse with the URL your site is hosted at.
```sh
$ npm install -g lighthouse
$ npx lighthouse <url> --view
```


## Tree shaking and code removal

Learn about how Expo CLI optimizes production JavaScript bundles.

Tree shaking (also referred to as _dead code removal_) is a technique to remove unused code from the production bundle. Expo CLI employs different techniques, including [minification](/guides/minify), to improve startup time by removing unused code.
## Platform shaking
Expo CLI employs a process known as **platform shaking** for app bundling, where it creates separate bundles for each platform (Android, iOS, web). It ensures that the code is only used on one platform and is removed from other platforms.
Any code that is used conditionally based on the `Platform` module from `react-native` is removed from the other platforms. However, this exclusion specifically applies to instances where `Platform.select` and `Platform.OS` are directly imported from react-native in each file. If these are re-exported through a different module, they will not be removed during the bundling process for different platforms.
For example, consider the following transformation input:
```js Input
import { Platform } from 'react-native';
if (Platform.OS === 'ios') {
  console.log('Hello on iOS');
}
```
The production bundle will remove the conditional based on the platform:
```js Output (Android)
```
```js Output (iOS)
console.log('Hello on iOS');
```
This optimization is production only and runs on a per-file basis. If you re-export `Platform.OS` from a different module, it will not be removed from the production bundle.
The `process.env.EXPO_OS` can be used to detect the platform that the JavaScript was bundled for (cannot change at runtime). This value does not support platform shaking imports due to how Metro minifies code after dependency resolution.
## Remove development-only code
In your project, there might be code designed to help with the development process. It should be excluded from the production bundle. To handle these scenarios, use the `process.env.NODE_ENV `environment variable or the non-standard `__DEV__` global boolean.
Step 1: 
For example, the following code snippet will be removed from the production bundle:
```js Input
if (process.env.NODE_ENV === 'development') {
  console.log('Hello in development');
}
if (__DEV__) {
  console.log('Another development-only conditional...');
}
```
Step 2: 
After _constants folding_ takes place, the conditions can be evaluated statically:
```js Post constants folding
if ('production' === 'development') {
  console.log('Hello in development');
}
if (false) {
  console.log('Another development-only conditional...');
}
```
Step 3: 
The unreachable conditions are removed during [minification](/guides/minify):
```js Output (production)
```
To improve speed, Expo CLI only performs code elimination in production builds. Conditionals from the above code snippet are kept in development builds.
## Custom code removal
`EXPO_PUBLIC_` environment variables are inlined before the minification process. This means they can be used to remove code from the production bundle. For example:
Step 1: 
```js .env
EXPO_PUBLIC_DISABLE_FEATURE=true;
```
```js Input
if (!process.env.EXPO_PUBLIC_DISABLE_FEATURE) {
  console.log('Hello from the feature!');
}
```
Step 2: 
The above input code snippet is transformed to the following after `babel-preset-expo`:
```js Post babel-preset-expo
if (!'true') {
  console.log('Hello from the feature!');
}
```
Step 3: 
The above code snippet is then minified, which removes the unused conditional:
```js Post minifier
// Empty file
```
- This system does not apply to server code as environment variables are not inlined in server bundles.
- Library authors should not use `EXPO_PUBLIC_` environment variables as they only run in application code for security reasons.
## Removing server code
It's common to use `typeof window === 'undefined'` to conditionally enable or disable code for server and client environments.
`babel-preset-expo` will transform `typeof window === 'undefined'` to `true` when bundling for server environments. By default, this check remains unchanged when bundling for web client environments. This transform runs in both development and production but only removes conditional requires in production.
You can configure `babel-preset-expo` to enable this transform by passing `{ minifyTypeofWindow: true }`.
By default, this transform remains disabled even for web environments since web workers won't have a `window` global.
Step 1: 
```js Input
if (typeof window === 'undefined') {
  console.log('Hello on the server!');
}
```
Step 2: 
The input code from the previous step is transformed to the following code snippet after `babel-preset-expo` when bundling for server environments (API routes, server rendering):
```js Post babel-preset-expo (bundling for server)
if (true) {
  console.log('Hello on the server!');
}
```
Bundling client code for web or native apps will not replace `typeof window` unless `minifyTypeOfWindow: true` is set:
```js Post babel-preset-expo
if (typeof window === 'undefined') {
  console.log('Hello on the server!');
}
```
Step 3: 
For server environments, the above code snippet is then minified which removes the unused conditional:
```js Post minifier (server)
console.log('Hello on the server!');
```
```js Post minifier (client)
if (typeof window === 'undefined') {
  console.log('Hello on the server!');
}
// Empty file
```
## React Native web imports
`babel-preset-expo` provides a built-in optimization for the `react-native-web` barrel file. If you import `react-native` directly using ESM, then the barrel file will be removed from the production bundle.
For ESM: 
If you import `react-native` using the static `import` syntax, the barrel file will be removed.
```js Input
import { View, Image } from 'react-native';
```
```js Output (web)
import View from 'react-native-web/dist/exports/View';
import Image from 'react-native-web/dist/exports/Image';
```
For CJS: 
If you import `react-native` using `require()`, the barrel file will be left as-is in the production bundle.
```js Input
const { View, Image } = require('react-native');
```
```js Output (web)
const { View, Image } = require('react-native-web');
```
## Remove unused imports and exports
> Experimentally available in SDK 52 and later.
You can experimentally enable support for automatically removing unused imports and exports across modules. This is useful for speeding up native OTA downloads and optimizing web performance where JavaScript must be parsed and executed using a standard JavaScript engine.
Consider the following example code:
```js index.js
import { ArrowUp } from './icons';
export default function Home() {
  return <ArrowUp />;
}
```
```js icons.js
export function ArrowUp() {
  /* ... */
}
export function ArrowDown() {
  /* ... */
}
export function ArrowRight() {
  /* ... */
}
export function ArrowLeft() {
  /* ... */
}
```
Since only `ArrowUp` is used in `index.js`, the production bundle will remove all other components from `icons.js`.
```js icons.js (Output)
export function ArrowUp() {
  /* ... */
}
```
This system scales up to automatically optimize all `import` and `export` syntax in your app, across all platforms. While this results in smaller bundles, processing JS still requires time and computer memory so avoid importing millions of modules.
- Tree-shaking only runs in production bundles and can only run on modules that use `import` and `export` syntax. Files that use `module.exports` and `require` will not be tree-shaken.
- Avoid adding Babel plugins such as `@babel/plugin-transform-modules-commonjs` which convert `import`/`export` syntax to CJS. This will break tree-shaking across your project.
- Modules that are marked as side-effects will not be removed from the graph.
- `export * from "..."` will be expanded and optimized unless the export uses `module.exports` or `exports`.
- All modules in the Expo SDK are shipped as ESM and can be exhaustively tree-shaken.
## Enabling tree shaking
> Experimentally available in SDK 52 and later.
Step 1: 
Ensure `experimentalImportSupport` and ensure your app builds and runs as expected.
> **info** **Note**: Enabled by default in SDK 54 and later.
Note: How to enable import support in older SDK versions?
---
```js metro.config.js
const { getDefaultConfig } = require('expo/metro-config');
const config = getDefaultConfig(__dirname);
config.transformer.getTransformOptions = async () => ({
  transform: {
    experimentalImportSupport: true,
  },
});
module.exports = config;
```
Experimental import support uses a custom version of the `@babel/plugin-transform-modules-commonjs` plugin. This drastically reduces the number of resolutions and simplifies your output bundle. This feature can be used with `inlineRequires` to further optimize your bundle experimentally.
---
Step 2: 
Toggle on the environment variable `EXPO_UNSTABLE_METRO_OPTIMIZE_GRAPH=1` to keep modules around until the entire graph is created. Ensure your app builds and runs as expected in production with this feature enabled before continuing.
```sh .env
EXPO_UNSTABLE_METRO_OPTIMIZE_GRAPH=1
```
This will only be used in production mode.
Step 3: 
Toggle on the environment variable `EXPO_UNSTABLE_TREE_SHAKING=1` to enable the feature.
```sh .env
EXPO_UNSTABLE_TREE_SHAKING=1
```
This will only be used in production mode.
Step 4: 
Bundle your app in production mode to see the effects of tree shaking.
```sh
$ npx expo export
```
This feature is very experimental because it changes the fundamental structure of how Metro bundles code. By default, Metro bundles everything on-demand and lazily to ensure the fastest possible development times. In contrast, tree shaking requires some transformation to be delayed until after the entire bundle has been created. This means less code can be cached, which is generally fine because tree shaking is a production-only feature and production bundles often don't use transform caches.
## Barrel files
> Experimentally available in SDK 52 and later.
With Expo tree shaking, star exports will automatically be expanded and shaken based on usage. For example, consider the following code snippet:
```js Input
export * from './icons';
```
The optimization pass will crawl `./icons` and add the exports to the current module. If the exports are unused, they will be removed from the production bundle.
```js Expanded
export { ArrowRight, ArrowLeft } from './icons';
```
This will be shaken according to standard tree shaking rules. If you only import `ArrowRight`, then `ArrowLeft` will be removed from the production bundle.
If the star export pulls in ambiguous exports such as `module.exports.ArrowUp` or `exports.ArrowDown`, then the optimization pass will not expand the star export and no exports will be removed from the barrel file. You can use [Expo Atlas](/guides/analyzing-bundles/#analyzing-bundle-size-with-atlas) to inspect the expanded exports.
You can use this strategy with libraries like `lucide-react` to remove all icons that are not used in your app.
## Recursive optimizations
> Experimentally available in SDK 52 and later.
Expo optimizes a module by recursing through the graph exhaustively to find unused imports. Consider the following code snippet:
```js Input
export function foo() {
  // Because bar is used here, it cannot be removed.
  bar();
}
export function bar() {}
```
In this case, `bar` is used in `foo`, so it cannot be removed. However, if `foo` is not used anywhere in the app, then `foo` will be removed and the module will be scanned again to see if `bar` can be removed. This process recurses 5 times for a given module before bailing out due to performance reasons.
## Side-effects
Expo CLI respects module side-effects according to the [Webpack system](https://webpack.js.org/guides/tree-shaking/#mark-the-file-as-side-effect-free). Side-effects are generally used for defining global variables (`console.log`) or modifying prototypes (avoid doing this).
You can mark if your module has side-effects in the **package.json**:
```json package.json
{
  "name": "library",
  "sideEffects": ["./src/*.js"]
}
```
Side-effects will prevent the removal of unused modules and disable module inlining to ensure JS code runs in the expected order. Side-effects will be removed if they're empty or contain only comments and directives (`"use strict"`, `"use client"`, and so on).
When Expo tree shaking is enabled, you can safely enable `inlineRequires` in your **metro.config.js** for production bundles. This will lazily load modules when they're evaluated, leading to faster startup time. Avoid using this feature without Expo tree shaking as it will move modules around in ways that can change the execution order of side-effects.
```js metro.config.js
const { getDefaultConfig } = require('expo/metro-config');
const config = getDefaultConfig(__dirname);
config.transformer.getTransformOptions = async () => ({
  transform: {
    experimentalImportSupport: true,
    inlineRequires: true,
  },
});
module.exports = config;
```
## Optimizing for tree shaking
Before Expo tree shaking, React Native libraries would remove imports by wrapping them in conditional blocks such as:
```js
if (process.env.NODE_ENV === 'development') {
  require('./dev-only').doSomething();
}
```
This is problematic because you don't have accurate TypeScript support and it makes the graph ambiguous since you cannot statically analyze the code. With Expo tree shaking enabled, you can restructure this code to use ESM imports:
```js Input
import { doSomething } from './dev-only';
if (process.env.NODE_ENV === 'development') {
  doSomething();
}
```
In both cases, the entire module will be empty in production bundles.


## Minifying JavaScript

Learn about customizing the JavaScript minification process in Expo CLI with Metro bundler.

Minification is an optimization build step. It removes unnecessary characters such as collapses whitespace, removes comments, and shortens static operations, from the source code. This process reduces the final size and improves load times.
## Minification in Expo CLI
In Expo CLI, minification is performed on JavaScript files during the production export (when `npx expo export`, `npx expo export:embed`, `eas build`, and so on, commands run).
For example, consider following code snippet in a project:
```js Input
// This comment will be stripped
console.log('a' + ' ' + 'long' + ' string' + ' to ' + 'collapse');
```
This will be minified by the Expo CLI:
```js Output
console.log('a long string to collapse');
```
> **info** **Tip:** Comments can be preserved by using the `/** @preserve */` directive.
The default minification of Expo CLI is sufficient for most projects. However, you can customize the minifier to optimize for speed or remove additional features like logs.
## Remove console logs
You can remove console logs from your production build. Use the `drop_console` option in the Terser minifier config.
```js metro.config.js
const { getDefaultConfig } = require('expo/metro-config');
const config = getDefaultConfig(__dirname);
config.transformer.minifierConfig = {
  compress: {
    // The option below removes all console logs statements in production.
    drop_console: true,
  },
};
module.exports = config;
```
You can also pass an array of console types to drop if you want to preserve certain logs. For example: `drop_console: ['log', 'info']` will remove `console.log` and `console.info` but preserve `console.warn` and `console.error`.
## Customizing the minifier
Different minifiers have tradeoffs between speed and compression. You can customize the minifier used by Expo CLI by modifying the **metro.config.js** file in your project.
### Terser
> [`terser`](https://github.com/terser/terser) is the default minifier ([Metro@0.73.0 changelog](https://github.com/facebook/metro/releases/tag/v0.73.0)).
Step 1: 
To install Terser in a project, run the command:
```sh
$ yarn add --dev metro-minify-terser
```
Step 2: 
Set Terser as a minifier with `transformer.minifierPath`, and pass in [`terser` options](https://github.com/terser/terser#compress-options) to `transformer.minifierConfig`.
```js metro.config.js
const { getDefaultConfig } = require('expo/metro-config');
const config = getDefaultConfig(__dirname);
config.transformer.minifierPath = 'metro-minify-terser';
config.transformer.minifierConfig = {
  // Terser options...
};
module.exports = config;
```
### Unsafe Terser options
For additional compression that may not work in all JavaScript engines, enable the [`unsafe` `compress` options](https://terser.org/docs/miscellaneous/#the-unsafe-compress-option):
```js metro.config.js
const { getDefaultConfig } = require('expo/metro-config');
const config = getDefaultConfig(__dirname);
config.transformer.minifierPath = 'metro-minify-terser';
config.transformer.minifierConfig = {
  compress: {
    // Enable all unsafe optimizations.
    unsafe: true,
    unsafe_arrows: true,
    unsafe_comps: true,
    unsafe_Function: true,
    unsafe_math: true,
    unsafe_symbols: true,
    unsafe_methods: true,
    unsafe_proto: true,
    unsafe_regexp: true,
    unsafe_undefined: true,
    unused: true,
  },
};
module.exports = config;
```
### esbuild
[`esbuild`](https://esbuild.github.io/) is used to minify exponentially faster than `uglify-es` and `terser`. For more information, see [`metro-minify-esbuild`](https://github.com/EvanBacon/metro-minify-esbuild#usage) usage.
### Uglify
You can use [`uglify-es`](https://github.com/mishoo/UglifyJS) by following the steps below:
Step 1: 
To install Uglify in a project, run the command:
```sh
$ yarn add --dev metro-minify-uglify
```
> Make sure the version of `metro-minify-uglify` matches the version of `metro` in your project.
Step 2: 
Set Uglify as a minifier with `transformer.minifierPath`, and pass in [options](https://github.com/mishoo/UglifyJS#compress-options) to `transformer.minifierConfig`.
```js metro.config.js
const { getDefaultConfig } = require('expo/metro-config');
const config = getDefaultConfig(__dirname);
config.transformer.minifierPath = 'metro-minify-uglify';
config.transformer.minifierConfig = {
  // Options: https://github.com/mishoo/UglifyJS#compress-options
};
module.exports = config;
```


## Why Metro?

Learn why Metro is the future of universal bundling in React Native and how it benefits developers.

[Metro](https://metrobundler.dev/) is the official bundler for Expo and React Native. It's a central build tool in the Expo framework. Bundlers comprise thousands of opinions and trade-offs. This document outlines the key reasons why Expo is developed around Metro and how it benefits developers.
## Official Meta bundler
Metro is maintained by Meta, the maintainers of React, React Native, Yoga, and Hermes. It's used for developing some of the world's largest apps, ranging across all categories in the app stores.
Meta engineers actively develop Metro with the express requirement of bundling all their apps, across 400k+ source files, while remaining fast and reliable.
By having first-class Metro support, we ensure Expo developers have continuity across Meta's tools and get instant access to emerging features. This includes:
- React Fast Refresh was [first introduced](https://reactnative.dev/blog/2019/09/18/version-0.61) as a Metro feature in 2019. The React web community adopted it the following year via Webpack.
- Transforming JavaScript to Hermes bytecode for instant native startup.
- React Native DevTools, including first-class support for [network and JS debugging](/debugging/tools/#debugging-with-react-native-devtools), is exclusively available with Metro and Hermes.
- React Compiler was initially rolled out as a Metro-compatible Babel plugin.
New and upcoming features that are planned to come to Metro include:
- Compiling Flow code to native machine code with Static Hermes. Learn more in the [Static Hermes](https://www.youtube.com/watch?v=GUM64b-gAGg) talk by Tzvetan Mikov.
- Data fetching, streaming, React Suspense, server rendering, and build-time static rendering with universal React Server Components for all platforms. Learn more in the [Universal React Server Components](https://www.youtube.com/watch?v=djhEgxQf3Kw) talk at React Conf 2024.
The Expo team collaborates with Meta to develop Metro for Expo Router, adding features like [file-based routing](/develop/file-based-routing/), [web support](/guides/customizing-metro/#web-support), [bundle splitting](/guides/customizing-metro/#bundle-splitting), [tree shaking](/guides/tree-shaking/), [CSS](/versions/latest/config/metro/#css), [DOM components](/guides/dom-components/), server components, and [API routes](/router/web/api-routes/).
## Battle-tested at scale
Nearly every React Native app in the world uses Metro, making it a battle-tested solution optimized for large-scale projects. This makes it suitable for developers of all sizes, from hobbyists to large companies. Metro is designed specifically to handle large-scale Meta apps, which is why it has features such as native file watching with Watchman and [shared remote caches](https://metrobundler.dev/docs/caching).
## On-demand processing
In development, Metro doesn't perform any platform-specific work until requested. This allows developers to work on large projects without paying a performance cost for the number of platforms they support. In conjunction with aggressive caching and [async routes](/router/web/async-routes/), developers can incrementally bundle only the parts of the app that they're actively working on.
## Multi-dimensional
Unlike traditional bundlers, which create multiple instances to bundle server and client code, Metro maximizes resource reuse across platforms and environments (server, client, DOM components). This architecture is ideal for multiplatform and server development.
## Reusable transform memoization
Metro is incremental and can create cached transform artifacts that can be used across machines. This enables large teams to reuse work from remote builders, a technique used at Meta for all large projects.
## Optimized for custom runtimes
While other bundlers are designed around the static specification of web browsers, Metro is optimized for the flexibility of React Native. This enables features like generating the specific set of supported language features required for Hermes bytecode compilation which enables faster app startup in production. This will also extend to Static Hermes, which will compile static type information into machine code for native apps.
## Cross-technology support
Expo leverages Metro's technology to create novel functionality like [DOM components](/guides/dom-components/). This allows a React component in a native app to be dynamically bundled as an entire website with all the same defaults as the parent app, on-demand.
## Native asset exports
Unlike traditional bundlers, where the end result is a fully hosted app, Metro's configuration options support exporting bundles to embed as native artifacts in standalone app binaries. This leverages OS-specific optimizations such as `xcassets` on Apple platforms.
## Concurrent processing
All AST transformation in Metro is performed concurrently across all available threads, maximizing the use of hardware.
## Comparison with other approaches
While Metro is designed for universal app development, it's often compared to other web-only bundlers. Here are some key differences:
### Browser ESM versus bundling
While bundlers like Vite leverage built-in ESM support in the browser, this approach can lead to slower practical development times at medium to large scales due to thousands of cascading network requests. Metro performs bundling in local development, which aligns the development results much closer to the production results and is better suited for React Native's larger module count.
### JavaScript versus native languages
Several bundlers are opting to write their core in Rust for performance reasons, but this comes with some trade-offs, such as more challenging contributions, patches, and development. Metro uses a mix of technologies based on the operation:
- The core bundler and utilities are written in JS/Flow.
- File watching is written in C++ via Watchman, with a JS fallback. Watchman is then used across projects on your computer.
- AST is parsed with Hermes parser (WebAssembly) to a Babel-compatible format.
- AST transformation is done with Babel. This maximizes developer customization.
- Minification uses Hermes on native platforms and Terser (with optional ESBuild support) for web.
- CSS parsing and minification is performed with [LightningCSS](https://lightningcss.dev/) (Rust).
This approach aligns with Meta and community tools while allowing easier debugging, profiling, and patching for developers.