You.i Platform has always supported plain bundling for a You.i React Native app, but as app complexity increases, plain bundling is not enough to optimize the start-up performance of an app. With RAM bundling, you can significantly reduce a You.i React Native app’s start-up time. To know more about the differences between plain bundling and RAM bundling, see Bundling in the Metro documentation.
When you use the RAM bundling option to generate your project, a JSON output file is created. The JSON file helps you filter the JavaScript (JS) modules required at start time or run time. The JSON file contains JS module information to help you reduce the start-up time performance.
Use the following command to generate a project with RAM bundling on macOS:
youi-tv build -p osx --ram_bundle --file index.youi.js --bundler_configuration <App Path>/config.js --sourcemap_output sourcemap_out.json
Where:
--ram_bundle
instructs the Metro bundler to generate a RAM bundle instead of a plain bundle.--bundler_configuration <PATH>
allows you to supply a custom Metro configuration for RAM bundling options.
In RNSampleApp, the config.js
file is located at RNSampleApp/packager
, but it could be located anywhere in a project as long as it is part of the --bundler_configuration
flag.--sourcemap_output <PATH>
generates a JSON output at the given path for debugging dynamic JS module loading.You can also specify default values for --sourcemap_output
and --bundler_configuration
flags in package.json as sourcemapOutput
and bundlerConfiguration
.
For details, see Build System Arguments.
An output JSON file is generated after successful project generation.
The file can be found in the directory you specified with the --sourcemap_output
flag.
This JSON file is used to determine the JS modules required dynamically or at start time, based on their ID.
The following snippet is an example of the x_metro_module_paths array from the output JSON file. Each line in the array is an index that matches the module to an ID.
"x_metro_module_paths": [
"<AppPath>/node_modules/@youi/react-native-youi/Libraries/Core/InitializeCore.js",
"<AppPath>/index.youi.js",
"__prelude__",
"<AppPath>/node_modules/metro/src/lib/polyfills/require.js",
"<AppPath>/node_modules/@youi/react-native-youi/Libraries/polyfills/Object.es6.js",
"<AppPath>/node_modules/@youi/react-native-youi/Libraries/polyfills/console.js",
"<AppPath>/node_modules/@youi/react-native-youi/Libraries/polyfills/error-guard.js",
"<AppPath>/node_modules/@youi/react-native-youi/Libraries/polyfills/Number.es6.js",
"<AppPath>/node_modules/@youi/react-native-youi/Libraries/polyfills/String.prototype.es6.js",
"<AppPath>/node_modules/@youi/react-native-youi/Libraries/polyfills/Array.prototype.es6.js",
"<AppPath>/node_modules/@youi/react-native-youi/Libraries/polyfills/Array.es6.js",
"<AppPath>/node_modules/@youi/react-native-youi/Libraries/polyfills/Object.es7.js",
"<AppPath>/node_modules/react/index.js",
"<AppPath>/node_modules/react/cjs/react.production.min.js",
"<AppPath>/node_modules/object-assign/index.js",
"<AppPath>/node_modules/@youi/react-native-youi/Libraries/react-native/react-native-implementation.js",
"<AppPath>/node_modules/@youi/react-native-youi/node_modules/fbjs/lib/invariant.js",
"<AppPath>/node_modules/@youi/react-native-youi/Libraries/Components/AccessibilityInfo/AccessibilityInfo.ios.js",
"<AppPath>/node_modules/@youi/react-native-youi/Libraries/BatchedBridge/NativeModules.js",
"<AppPath>/node_modules/@youi/react-native-youi/Libraries/BatchedBridge/BatchedBridge.js",
"<AppPath>/node_modules/@youi/react-native-youi/Libraries/BatchedBridge/MessageQueue.js",
"<AppPath>/node_modules/@youi/react-native-youi/Libraries/vendor/core/ErrorUtils.js",
"<AppPath>/node_modules/@youi/react-native-youi/Libraries/Performance/Systrace.js",
"<AppPath>/node_modules/@youi/react-native-youi/Libraries/Utilities/deepFreezeAndThrowOnMutationInDev.js",
"<AppPath>/node_modules/@youi/react-native-youi/Libraries/Utilities/stringifySafe.js",
"<AppPath>/node_modules/@youi/react-native-youi/Libraries/Utilities/defineLazyObjectProperty.js",
"<AppPath>/node_modules/@youi/react-native-youi/Libraries/Promise.js",
"<AppPath>/node_modules/@youi/react-native-youi/node_modules/fbjs/lib/Promise.native.js",
"<AppPath>/node_modules/promise/setimmediate/es6-extensions.js",
"<AppPath>/node_modules/promise/setimmediate/core.js",
"<AppPath>/node_modules/promise/setimmediate/done.js",
"<AppPath>/node_modules/@youi/react-native-youi/Libraries/EventEmitter/RCTDeviceEventEmitter.js",
"<AppPath>/node_modules/@youi/react-native-youi/Libraries/vendor/emitter/EventEmitter.js",
"<AppPath>/node_modules/@youi/react-native-youi/Libraries/vendor/emitter/EmitterSubscription.js",
"<AppPath>/node_modules/@youi/react-native-youi/Libraries/vendor/emitter/EventSubscription.js",
"<AppPath>/node_modules/@youi/react-native-youi/Libraries/vendor/emitter/EventSubscriptionVendor.js",
"<AppPath>/node_modules/@youi/react-native-youi/node_modules/fbjs/lib/emptyFunction.js",
"<AppPath>/node_modules/@youi/react-native-youi/Libraries/Components/ActivityIndicator/ActivityIndicator.js",
"<AppPath>/node_modules/@youi/react-native-youi/Libraries/Utilities/Platform.ios.js",
"<AppPath>/node_modules/@youi/react-native-youi/Libraries/react-native/React.js",
For example, if the Loading RAM bundle module with ID: (0023)
message is generated in the console log, the ID:0023
is for the Systrace module.
Keep repeating the process of matching the correct JS modules from x_metro_module_paths
, until you’re done with all JS modules that are required at start time.
When done matching the module information with the IDs, select the JS modules to load at start time and remove everything before node_modules
in their path.
Create a JavaScript file with a name such as modulePaths.js
with an empty array called module.exports
.
Fill the array with the selected JS modules, including their paths.
You can place modulePaths.js
anywhere in your project as long as it gets picked up by the bundler configuration file (config.js
).
Following is an example of a modulePaths.js
file with the JS modules information added to the module.exports
array:
module.exports = [
'node_modules/@youi/react-native-youi/Libraries/Core/InitializeCore.js',
'node_modules/@youi/react-native-youi/Libraries/Core/setUpGlobals.js',
'node_modules/@youi/react-native-youi/Libraries/Core/polyfillES6Collections.js',
'node_modules/@youi/react-native-youi/Libraries/vendor/core/_shouldPolyfillES6Collection.js',
'node_modules/@youi/react-native-youi/Libraries/Core/setUpSystrace.js',
'node_modules/@youi/react-native-youi/Libraries/Core/setUpErrorHandling.js',
'node_modules/@youi/react-native-youi/Libraries/Core/ExceptionsManager.js',
]
For more details, see the RNSampleApp’s modulePaths.js
file located at <youiengineone>/<release>/samples/RNSampleApp/packager/modulePaths.js
.
Sometimes a module that needs to load at run time has additional dependencies which must be loaded in advance.
For these kind of use-cases, create moduleGroups.js
to include all the dependencies simultaneously.
In the following example of moduleGroups.js
, the dependencies related to the youtube-stream-url
are loaded at once:
module.exports = [
'node_modules/youtube-stream-url/src/index.js'
]
Now, update the config.js
file with modulePaths.js
and moduleGroups.js
.
The following config.js
example can also be found at <youiengineone>/<release>/samples/RNSampleApp/packager/
:
const modulePaths = require('./modulePaths');
const groupPaths = require('./moduleGroups');
const resolve = require('path').resolve;
const fs = require('fs');
const ROOT_FOLDER = resolve(__dirname, '..');
const blacklist = require('metro-config/src/defaults/blacklist');
var path = require('path');
const reactNativeYouiPath = fs.realpathSync(
path.resolve(require.resolve('@youi/react-native-youi/package.json'), '..')
);
const config = {
resolver: {
platforms: ['youi'],
blacklistRE: blacklist([
/\/youi\/build\/.*/,
/node_modules\/react-native\/.*/
]),
extraNodeModules: {
// Redirect react-native to react-native-youi
'react-native': reactNativeYouiPath,
'@youi/react-native-youi': reactNativeYouiPath
}
},
transformer: {
getTransformOptions: () => {
const moduleMap = {};
modulePaths.forEach(path => {
if (fs.existsSync(path)) {
moduleMap[resolve(path)] = true;
}
});
const groupArray = [];
groupPaths.forEach(path => {
if (fs.existsSync(path)) {
groupArray.push(resolve(path));
}
});
return {
preloadedModules: moduleMap,
transform: { inlineRequires: { blacklist: moduleMap } },
ramGroups: groupArray,
};
},
},
projectRoot:ROOT_FOLDER,
};
module.exports = config;
When using RAM bundling, prioritize running some JS modules before the app’s entry-point to avoid JS exceptions.
For example, setup the global initialization JS module before the app’s entry-point as it initializes the global.GLOBAL
and global.window
variables.
Add the JS module including the path in config.js as part of the serializer’s getModulesRunBeforeMainModule
array.
Ensure that the same JS module is added to the transformer’s preloadedModules
map.
JS modules won’t be added to the bundle if they’re not detected as dependencies at the app’s entry-point, even if they’re added to getModulesRunBeforeMainModule
and preloadedModules
.
To know more about getModulesRunBeforeMainModule
, see Configuring Metro.
The following is a config.js
example with a global initialization JS module added to getModulesRunBeforeMainModule
:
const config = {
serializer: {
getModulesRunBeforeMainModule: () => [
resolve('node_modules/react-native/Libraries/Core/InitializeCore.js')
]
}
resolver: {
platforms: ['youi'],
blacklistRE: blacklist([
/\/youi\/build\/.*/,
/node_modules\/react-native\/.*/
]),
extraNodeModules: {
// Redirect react-native to react-native-youi
'react-native': reactNativeYouiPath,
'@youi/react-native-youi': reactNativeYouiPath
}
},
transformer: {
getTransformOptions: () => {
const moduleMap = {};
modulePaths.forEach(path => {
if (fs.existsSync(path)) {
moduleMap[resolve(path)] = true;
}
});
const groupArray = [];
groupPaths.forEach(path => {
if (fs.existsSync(path)) {
groupArray.push(resolve(path));
}
});
return {
preloadedModules: moduleMap,
transform: { inlineRequires: { blacklist: moduleMap } },
ramGroups: groupArray,
};
},
},
projectRoot:ROOT_FOLDER,
};
Great! You’re done with the most tedious part of setting up your project for RAM bundling.
However, to improve your app’s start-up performance, you need to add inline calls for dynamic JS module loading.
Following is an example of an inline call for the youtube-stream-url
JS module:
Without Inline Call:
import React, { Component, Fragment } from 'react';
import Youtube from 'youtube-stream-url'
import ListItem from './listitem.youi.js';
import Timeline from './timeline.youi.js';
export default class PDP extends Component {
constructor(props) {
super(props);
...
if (asset.videos.results.length > 0) {
Youtube.getInfo({ url: 'https://www.youtube.com/watch?v=' + asset.videos.results[0].key })
.then(video => { console.log(video); this.video = video });
}
With Inline Call:
import React, { Fragment } from 'react';
import ListItem from './listitem.youi.js';
import Timeline from './timeline.youi.js';
let Youtube = null;
export default class PDP extends React.Component {
constructor(props) {
super(props);
...
if (asset.videos.results.length > 0) {
if (Youtube == null) {
Youtube = require('youtube-stream-url');
}
Youtube.getInfo({ url: 'https://www.youtube.com/watch?v=' + asset.videos.results[0].key })
.then(video => { console.log(video); this.video = video });
}
As required, add inline calls for other JS modules that are required dynamically in your app code.
You can always switch back to plain bundling even after adding inline calls to JS modules. But to optimize the start-up performance of your app, inline calls to the JS module are a must.
You.i React Native allows you to use the React Information widget in the Dev Panel to see whether or not any JS module is loaded dynamically at run time. The Loaded RAM Modules field in the React Information widget increments if a JS module is loaded dynamically.
You can also view RAM module requests as event marks in the EasyProfiler GUI.
Any time a JS module is required dynamically, a marker is added with the index shown as a number.
As mentioned before, this number can be cross-referenced with the x_metro_module_paths
array in the generated output JSON file.
For example, in the following image, you can see that the number displayed is 231, which can be cross-referenced with the JS module at index[231]
of the x_metro_module_paths
array.
The following images from EasyProfiler GUI illustrate the start-time performance for RNSampleApp before RAM bundling and after RAM bundling:
Before RAM Bundling:
In the following image from the EasyProfiler GUI for RNSampleApp before RAM bundling, notice the first frame is drawn at ~2700 ms.
After RAM Bundling:
In the following image from the EasyProfiler GUI for RNSampleApp after RAM bundling, notice the first frame is drawn at ~1950 ms, which is a significant improvement over ~2700 ms.
You can refer to RNSampleApp (<youiengineone>/<version>/samples/RNSampleApp
) to know more about config.js
, modulePaths.js
, and moduleGroups.js
.
Also, see RAM Bundling in React Native to get more information on RAM bundling in RN apps.