Learn about different Metro bundler configurations that can be customized.
Expo CLI uses Metro during npx expo start
and npx expo export
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.
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 that extends 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:
-
npx expo customize metro.config.js
The metro.config.js file looks as below:
const { getDefaultConfig } = require('expo/metro-config');
const config = getDefaultConfig(__dirname);
module.exports = config;
See metro.config.js documentation for more information.
Metro resolves files as either source code or assets. Source code is JavaScript, TypeScript, JSON, and other files used by your application. 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:
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:
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;
To strip code in production or development, use the process.env.NODE_ENV
environment variable. For example:
if (process.env.NODE_ENV === 'development') {
console.log('Hello in development');
}
'development' === process.env.NODE_ENV && console.log('Hello in development');
%%placeholder-start%%Empty file %%placeholder-end%%
Metro also supports removing code based on the __DEV__
global boolean, however this is less common in the JavaScript ecosystem.
To remove code based on the Platform
module from react-native
, you need to add the following to your metro.config.js:
const { getDefaultConfig } = require('expo/metro-config');
const config = getDefaultConfig(__dirname);
config.transformer.getTransformOptions = async () => ({
transform: {
experimentalImportSupport: true,
},
});
module.exports = config;
And configure the babel.config.js
to preserve import/export syntax:
module.exports = function (api) {
api.cache(true);
const disableImportExportTransform = true;
return {
presets: [
[
'babel-preset-expo',
{
native: {
disableImportExportTransform,
},
web: {
disableImportExportTransform,
},
},
],
],
};
};
Now the following transformation input:
import { Platform } from 'react-native';
if (Platform.OS === 'ios') {
console.log('Hello on iOS');
}
Will produce the following output:
console.log('Hello on iOS');
%%placeholder-start%%Empty on Android %%placeholder-end%%
The source code is optimized for readability and debugging. To optimize your application for performance, the source code is automatically minified when compiling and exporting. You can also minify your code during development with npx expo start --minify
. This is sometimes useful for testing production optimizations.
terser
is the default minifier in SDK 48 and above: (Metro@0.73.0 changelog).
For projects SDK 47 and below, you can use terser
instead of uglify-es
to mangle and compress your project.
1
Install Terser in your project by running the following command:
-
yarn add --dev metro-minify-terser
2
Set Terser as a minifier with transformer.minifierPath
, and pass in terser
options via transformer.minifierConfig
.
const { getDefaultConfig } = require('expo/metro-config');
const config = getDefaultConfig(__dirname);
config.transformer.minifierPath = 'metro-minify-terser';
config.transformer.minifierConfig = {
// Terser options...
};
module.exports = config;
Minification collapses whitespace, removes comments, and shortens static operations. For example, the following code:
// This comment will be stripped
console.log('a' + ' ' + 'long' + ' string' + ' to ' + 'collapse');
Is minified to:
console.log('a long string to collapse');
Comments can be preserved by using the /** @preserve */
directive.
For additional compression that may not work in all JavaScript engines, you can enable the unsafe
compress
options:
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;
You can use esbuild
to minify exponentially faster than uglify-es
and terser
. For more information, see metro-minify-esbuild
usage.
uglify-es
is the default minifier in SDK 47 and below, this used the following options.
For projects SDK 48 and above, you can use uglify-es
like so:
1
Install Uglify in your project by running the following command:
-
yarn add --dev metro-minify-uglify
Ensure the version of
metro-minify-uglify
matches the version ofmetro
in your project.
2
Set Uglify as a minifier with transformer.minifierPath
, and pass in options to transformer.minifierConfig
.
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;
A useful way to debug your source code is by exploring the source maps with react-native-bundle-visualizer
.
To use it, run the following command:
-
npx react-native-bundle-visualizer
This will show an interactive breakdown of what makes up your JavaScript bundle. Using this, you can find large packages that you may not have intended to bundle into your project. The smaller the bundle, the faster your app will start.
Metro web support is under development and only works with the local Expo CLI and Expo SDK 46 or newer. It's recommended you use it with Expo Router.
By default, Expo CLI uses webpack as the bundler on web platforms because Metro historically did not support web. Using different bundlers across platforms leads to some critical divergence in how an app works across platforms. Features such as Fast Refresh which works on native don't work on the web, and important production functionality such as assets are treated differently across bundlers.
By utilizing Metro across all platforms you can have a more universal development experience. You also get to utilize shared cached chunks across platforms meaning faster iteration speed when working across platforms. Project upgrades can also be easier since there are fewer dependencies (webpack
, webpack-dev-server
) you need to update between versions.
All Metro web features should be universal (bundling, static files, assets) making the DX easier to understand and faster across the app development process.
Learn once, bundle everywhere!
Universal Expo Metro is designed to be fully universal, meaning any web bundling features should also work on native too. Because of this, we make some breaking changes between the two bundler implementations, carefully check the difference if you're moving from webpack to Metro.
Feature | Metro | webpack |
---|---|---|
Start command | npx expo start | npx expo start |
Bundle command | npx expo export | npx expo export:web |
Output folder | dist | web-build |
Static folder | public | web |
Config file | metro.config.js | webpack.config.js |
Default config | @expo/metro-config | @expo/webpack-config |
Multi-platform | ||
Fast Refresh | (@expo/metro-runtime ) | |
Error Overlay | (@expo/metro-runtime ) | |
Deferred Bundling | (@expo/metro-runtime ) | |
Global CSS | (Beta • SDK 49) | |
CSS Modules | (Beta • SDK 49) | |
Bundle Splitting | (pending native) | |
Tree Shaking | ||
Asset Manifests |
Note that aliases, resolution, and other bundler features are now universal across platforms as well!
To enable Metro web support, make sure your project is using Expo SDK 46 or newer. Then modify your app config to enable the feature using the expo.web.bundler
field:
{
"expo": {
"web": {
"bundler": "metro"
}
}
}
To start the development server run the following command:
-
npx expo start --web
Alternatively, press W in the Expo CLI terminal UI.
Tip Use
npx expo export
instead ofnpx expo export:web
orexpo build:web
.
You can bundle the project for hosting as it is done for native:
-
npx expo export
In the terminal, after running the above command, you'll see a similar output as shown below:
$ npx expo export
Starting Metro Bundler
iOS ./index.tsx ░░░░░░░░░░░░░░░░ 4.0% ( 8/132)
Android ./index.tsx ░░░░░░░░░░░░░░░░ 0.5% ( 3/129)
Web ./index.tsx ░░░░░░░░░░░░░░░░ 4.0% ( 5/5)
You can also skip bundling for other platforms by using the --platform
flag:
-
npx expo export --platform web
The output is found in the dist directory. To test it locally and see it will work in production, use Serve CLI.
-
npx serve dist
You can deploy the website using any popular web host by following a guide from publishing websites, and substitute the web-build directory for dist.
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. In Expo webpack, the web directory is default.
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.
Available in SDK 49 and higher.
Expo's Metro config has experimental support for 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. Learn more in the TypeScript guide.
These feature requires additional setup in bare projects. See the versioned Metro setup guide for more information.
Available in SDK 49 and higher.
Expo's Metro config offers support for CSS on web. See the versioned Metro CSS guide for more information.