You can read my article about Share React Native components to multiple projects, which I suggest 3 ways for sharing code between projects.
My case was almost exactly as yours. I have a React Native project, a ReactJS web app, a shared Library and the server code which is Google Cloud Firebase Functions for nodejs. I went with the third method using Babel and Metro Bundler.
Method 1: Git for npm packages
We can create a git repo for our Library directory and then having NPM to install it directly from the repo. That will work, but we’ll have to synchronize (commit/push) git repos after every change to the Library which is not so practical for working on local projects. Check this Q/A for more details How to install an npm package from GitHub directly?.
Git submodules is a really nice way to have shared code inside a project that it needs to be changed often. I was looking for this solution and I was excited when I saw the potential of using this method, but then I thought that I am not so familiar with git submodules and of course didn’t want to do anything and mess with the git repos and then having some hard time fixing git issues. If you are familiar using git submodules, then this is the way to go.
Method 3 (Babel, Metro): Using .babelrc module resolver plugin for React with Metro Bundler resolver for React Native
We can use the module-resolver package to configure directory aliases. We create both projects normally for RN and ReactJS and we initialize the firebase functions project, the Library does not has to be initialized as a nodejs project:
Directory structure:
MyProject-RNApp\
MyProject-ReactJSApp\
MyProject-Backend\
MyProject-Library\
Every package that is used, it has to be installed for the project that it will be used. For example, if we have a common function inside our library that uses validator, then we'll have to npm install
that package for each node project that uses it.
Configure module-resolver for all Babel projects
First inside .babelrc
we define the aliases for each directory. Here we can add directories that are outside of the RN app root directory:
{
"presets": ["module:metro-react-native-babel-preset"],
"env": {
"production": {
"plugins": ["transform-remove-console", "react-native-paper/babel"]
}
},
"plugins": [
"@babel/plugin-transform-runtime",
[
"module-resolver",
{
"root": ["./"],
"extensions": [".js", ".jsx", ".ts", ".tsx"],
"stripExtensions": [".js", ".jsx", ".ts", ".tsx"],
"alias": {
"@components": "./components",
"@screens": "./screens",
"@utils": "./utils",
"@data": "./data",
"@assets": "./assets",
"@app": "./",
"@myprojectlib": "../MyProject-Library",
"@myprojecti18n": "../MyProject-Library/i18n/rn",
"@myprojectbackend": "../MyProject-Backend/firebase/functions",
}
}
]
]
}
Here you have to be careful with the paths, but relative paths also work well. Also you don't have to add aliases for each package when configuring the RN project because you'll do that below using the Metro Bundler configuration. You'll have to create package aliases just for the ReachJS and Backend Babel projects:
{
"plugins": [
[
"module-resolver",
{
"root": "../",
"alias": {
"@app": "./resources/js/app",
"@graphics": "./resources/js/app/graphics",
"@styles": "./resources/js/app/styles",
"@components": "./resources/js/app/components",
"@screens": "./resources/js/app/screens",
"@panels": "./resources/js/app/panels",
"@stores": "./resources/js/app/stores",
"@utils": "./resources/js/app/utils",
"@dialogs": "./resources/js/app/dialogs",
"@data": "./resources/js/app/data",
"@myprojectlib": process.env.MYPROJECT_LIBRARY_PATH,
"@myprojectbrain": process.env.MYPROJECT_BRAIN_PATH,
"@myprojecti18n": `${process.env.MYPROJECT_LIBRARY_PATH}/i18n`,
// Add module paths for external myproject, else it wont work!
"react": "./node_modules/react",
"react-flag-icon-css": "./node_modules/react-flag-icon-css",
"react-hot-loader": "./node_modules/react-hot-loader",
"react-localization": "./node_modules/react-localization",
"prop-types": "./node_modules/prop-types",
"@material-ui/pickers": "./node_modules/@material-ui/pickers",
"@material-ui/core": "./node_modules/@material-ui/core",
"classnames": "./node_modules/classnames",
"qs": "./node_modules/qs",
"luxon": "./node_modules/luxon",
"@babel/runtime": "./node_modules/@babel/runtime",
}
}
]
]
}
Then for RN we create a metro.config.js
file in the project root directory in order to configure the RN Metro Bundler to resolve each directory and shared npm package like this:
var path = require("path");
var config = {
projectRoot: path.resolve(__dirname),
watchFolders: [
// Let's add the root folder to the watcher
// for live reload purpose
path.resolve(__dirname, "../MyProject-Library"),
path.resolve(__dirname, "../MyProject-Backend")
],
resolver: {
sourceExts: ['js', 'jsx', 'ts', 'tsx'],
extraNodeModules: {
// Here I reference my upper folder
"myprojectlib": path.resolve(__dirname, "../MyProject-Library"),
"myprojectbackend": path.resolve(__dirname, "../MyProject-Backend/firebase/functions"),
// Important, those are all the dependencies
// asked by the "../MyProject-Library" but which
// are not present in the ROOT/node_modules
// So install it in your RN project and reference them here
// "expo": path.resolve(__dirname, "node_modules/expo"),
// "lodash.merge": path.resolve(__dirname, "node_modules/lodash.merge"),
"dinero.js": path.resolve(__dirname, "node_modules/dinero.js"),
"luxon": path.resolve(__dirname, "node_modules/luxon"),
"validator": path.resolve(__dirname, "node_modules/validator"),
"react-native-reanimated": path.resolve(__dirname, "node_modules/react-native-reanimated"),
"react-native-gesture-handler": path.resolve(__dirname, "node_modules/react-native-gesture-handler"),
"react-native-vector-icons": path.resolve(__dirname, "node_modules/react-native-vector-icons"),
"react-native-navigation": path.resolve(__dirname, "node_modules/react-native-navigation"),
"react-native-firebase": path.resolve(__dirname, "node_modules/react-native-firebase"),
"prop-types": path.resolve(__dirname, "node_modules/prop-types"),
"react-native": path.resolve(__dirname, "node_modules/react-native"),
"react": path.resolve(__dirname, "node_modules/react"),
"@babel/runtime": path.resolve(__dirname, "node_modules/@babel/runtime"),
"@jsassets": path.resolve(__dirname, "./jsassets"),
"@data": path.resolve(__dirname, "./data"),
"@components": path.resolve(__dirname, "./components"),
"@app": path.resolve(__dirname),
}
}
}
module.exports = config;
We have to define each shared package and common directory in resolver.extraNodeModules
; We have to define even react
and react-native
in order for the shared code inside our library to be able to have components and access React and RN features.
When we run yarn start
(or npm run start
), we'll see Metro Bundler looking for JS files inside all fc
+RNApp, Library, Backend
:
