
Dynamic Module Loading and Runtime Execution for React Native using Metro
metro-requirex
is a utility for dynamically loading modules and executing JavaScript code in a React Native environment, leveraging Metro Bundler. With metro-requirex
, developers can bypass traditional static require()
statements, allowing for runtime module resolution and dynamic code execution.
metro-requirex
allows for dynamic module loading and evaluation in React Native without modifying Metro itself. It ensures consistent and efficient resolution of modules at runtime, providing a seamless experience for developers who need runtime flexibility within their applications.
- Dynamic Module Resolution: Load and execute modules dynamically at runtime, even for modules that are bundled with Metro.
- Eval Support: Execute JavaScript dynamically using
evalx()
with support for module imports and exports. - React Component Support: Dynamically load and render React Native components at runtime without needing to rebuild.
- Zero Metro Modifications: Utilizes Metro’s internal module resolution and
__r()
function for compatibility. - Safe Execution: Uses
new Function()
for sandboxed execution, ensuring performance is not impacted.
Install metro-requirex
via npm or yarn:
yarn add @metro-requirex/react-native
yarn add -D @metro-requirex/metro-config
or
npm install @metro-requirex/react-native
npm install @metro-requirex/metro-config --save-dev
To ensure that metro-requirex
works correctly with your React Native project, you need to update your metro.config.js file. This step integrates the necessary configuration provided by @metro-requirex/metro-config
.
Update your metro.config.js as follows:
const {getDefaultConfig} = require('@react-native/metro-config');
const {withMetroRequirexConfig} = require('@metro-requirex/metro-config');
module.exports = withMetroRequirexConfig(getDefaultConfig(__dirname));
If you already have a custom Metro configuration, you can merge it with the configuration returned by withMetroRequirexConfig(...)
. This updated configuration ensures the proper resolution of module IDs and supports dynamic module loading at runtime.
You can load Metro-bundled modules dynamically using the requirex()
function:
import {requirex} from 'metro-requirex';
const lodash = requirex('lodash');
console.log(lodash.camelCase('hello world')); // Output: "helloWorld"
Execute arbitrary JavaScript code dynamically with module imports using evalx()
:
import {evalx} from 'metro-requirex';
const code = `
const _ = require("lodash");
module.exports = _.kebabCase("React Native Rocks!");
`;
console.log(evalx(code)); // Output: "react-native-rocks"
You can dynamically create and render React Native components at runtime:
import {evalx} from 'metro-requirex';
import {View, Text} from 'react-native';
const componentCode = `
module.exports = () => {
return React.createElement("Text", null, "Hello from a dynamic component!");
};
`;
const DynamicComponent = evalx(componentCode);
export default function App() {
return (
<View>
<DynamicComponent />
</View>
);
}
Dynamically loads a module within Metro Bundler.
moduleName
(string): The name of the module to load.
- The module’s exports if found.
null
if the module cannot be resolved.
const moment = requirex('moment');
console.log(moment().format('YYYY-MM-DD'));
Executes JavaScript code dynamically, supporting module imports.
code
(string): The JavaScript code to execute.
- The value of
module.exports
from the executed code.
const result = evalx(`
module.exports = "Dynamic Execution!";
`);
console.log(result); // Output: "Dynamic Execution!"
-
MD5 Hashing for Module ID: We generate deterministic numeric IDs for modules using MD5 hashing of the module path. This ensures that modules always receive the same ID across builds and executions.
-
External Modules: For modules in
node_modules
, we compute a hash of the module's relative path withinnode_modules
to resolve it dynamically. -
Internal Modules: For internal project files, we compute the relative path from the project root and map it to a unique ID to ensure consistent resolution across different builds.
-
Module Resolution at Runtime: Once the correct module ID is computed, we use Metro’s internal
__r()
function to dynamically load the module during runtime.
-
Sandboxed Execution: The
evalx()
function usesnew Function()
to execute JavaScript code in a secure, isolated environment. -
Module Imports: The evaluated code has access to
requirex()
, allowing it to dynamically import modules. -
Return Value: The function returns
module.exports
from the executed code, mimicking the CommonJS module system.
We welcome contributions to metro-requirex
! If you’d like to contribute, please follow the steps below:
- Fork the repository on GitHub.
- Clone your forked repository to your local machine.
- Create a new branch for your feature or bug fix.
- Make your changes and commit them with descriptive messages.
- Push your changes to your forked repository.
- Open a pull request to the main repository.
For more detailed contributing instructions, check out the Contributing Guide.
This project is licensed under the MIT License. See the LICENSE.md file for more details.