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, to improve startup time by removing unused code.
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:
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:
%%placeholder-start%%Empty on Android %%placeholder-end%%
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.
Platform shaking is enabled by default in Expo SDK 50 and greater.
To remove code based on the Platform
module from react-native
, add the following to metro.config.js:
const { getDefaultConfig } = require('expo/metro-config');
const config = getDefaultConfig(__dirname);
config.transformer.getTransformOptions = async () => ({
transform: {
experimentalImportSupport: true,
},
});
module.exports = config;
Then, configure 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,
},
},
],
],
};
};
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.
1
For example, the following code snippet will be removed from the production bundle:
if (process.env.NODE_ENV === 'development') {
console.log('Hello in development');
}
if (__DEV__) {
console.log('Another development-only conditional...');
}
2
After constants folding takes place, the conditions can be evaluated statically:
if ('production' === 'development') {
console.log('Hello in development');
}
if (false) {
console.log('Another development-only conditional...');
}
3
The unreachable conditions are removed during minification:
%%placeholder-start%%Empty file %%placeholder-end%%
To improve speed, Expo CLI only performs code elimination in production builds. Conditionals from the above code snippet are kept in development builds.
With Expo SDK 50, 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:
1
EXPO_PUBLIC_DISABLE_FEATURE=true;
if (!process.env.EXPO_PUBLIC_DISABLE_FEATURE) {
console.log('Hello from the feature!');
}
2
The above input code snippet is transformed to the following after babel-preset-expo
:
if (!'true') {
console.log('Hello from the feature!');
}
3
The above code snippet is then minified, which removes the unused conditional:
// Empty file
EXPO_PUBLIC_
environment variables as they only run in application code for security reasons.As of Expo SDK 50, unused imports and exports are not removed from the production bundle. We plan to add this feature to all platforms in a future release.
As of Expo SDK 50, there are no built-in optimizations for barrel files.
Barrel files export all of the modules in a directory. They are used to make importing modules easier. For example, a component-based icon library does the following:
export { default as IconA } from './IconA';
export { default as IconB } from './IconB';
export { default as IconC } from './IconC';
To reduce the bundle size, identify which of these modules you are using and try to import them directly. Learn more in analyzing bundle size.
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.
If you import react-native
using the static import
syntax, the barrel file will be removed.
import { View, Image } from 'react-native';
import View from 'react-native-web/dist/exports/View';
import Image from 'react-native-web/dist/exports/Image';
If you import react-native
using require()
, the barrel file will be left as-is in the production bundle.
const { View, Image } = require('react-native');
const { View, Image } = require('react-native-web');