Motivation - Setup react monorepo project using NPM workspaces
👉 More about NPM Workspaces
Preferred structure
We're going to have next project structure:
.
└── root/
├── node_modules/
├── packages/
│ ├── app1/ (create-react-app)
│ │ ├── src/
│ │ └── package.json
│ └── common/ (shared component library)
│ └── components
│ └── utils
│ └── package.json
├── package.json
└── package.lock.json
Implementation details 📚
Project init via npm workspaces
- Step/1 - In the root project, create a package.json file with the following config.
{
"name": "npm_workspaces",
"private": "true",
"workspaces": ["./packages/*"]
}
Step/2 Create packages directory within the
root
folder-
Step/3 Create app1
- Navigate to the packages folder
- Create new CRA project using following command
npx create-react-app app1 --template typescript
-
Step/4 Once we have
app1
let's do a few changes ðŸ›- In
app1/package.json
rename project name fromapp1
to@ng_speedster/app1
. Why? Naming packages with @{domain}/{package-name} will group all the packages inside one common folder in node_modules. It allows for easier debugging if something some packages are not working properly or some else goes wrong. In my case, I have renamed the namekey in the app package as @ng_speedster/app1. - Move packages
dependencies
fromapp1/package.json
toroot/package.json
. I'm doing that, cause I would like to have consistency between my react projects. In short I just want to have the same versions of react related dependencies:
- In
{
"@testing-library/jest-dom": "^5.16.1",
"@testing-library/react": "^12.1.2",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.4.0",
"@types/node": "^16.11.21",
"@types/react": "^17.0.38",
"@types/react-dom": "^17.0.11",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-scripts": "5.0.0",
"typescript": "^4.5.4",
"web-vitals": "^2.1.3"
}
-
Step/5 Create common package
- Basically, this step repeats step 3 & 4;
- Navigate to the packages folder
- Create new CRA project using following command
npx create-react-app common --template typescript
- Move/merge packages
dependencies
fromcommon/package.json
toroot/package.json
.
-
Step/6
- Remove node_modules from
app1
andcommon
projects - Navigate to the root folder and run
npm install
- It will install all the dependencies across all the packages into the root. You can then check the root node_modules folder to locate your package.
- Remove node_modules from
Projects is added, what's next?
Transpiling JSX into js from the shared component package
As we are not transpiling (converting JSX into createElements) from our shared components package, we will rely on cra to do the transpilation step. It makes the process a whole lot easier. To do so, we have to customize babel by using react-app-rewired and customize-crapackages. It allows you to customize webpack configuration without ejecting react-scripts.
-
Step/1 Installing and configuring react-app-rewired and customize-cra
- Navigate to the root folder
npm workspace @ng_speedster/app add -D react-app-rewired customize-cra
- In the
app1/package.json
make the next changes:
{
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-scripts eject"
}
}
- Step/2 Override babel config by creating the config-overrides.js file in the app package. ```javascript
const path = require("path");
const { override, babelInclude } = require("customize-cra");
module.exports = function (config, env) {
return Object.assign(
config,
override(
babelInclude([
/* transpile (converting to es5) code in src/ and shared component library */
path.resolve("src"),
path.resolve("../common"),
])
)(config, env)
);
};
- It instructs babel to transpile all the files in the app's src directory and shared component package.
---
## So that's it, now you can create component inside common/and import inside app package.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pyexv7hnzcd23xoet66f.png)
# Overall
Honestly, to set up a monorepo project using npm workspaces is not that painful but requires
to dig into some areas:
- [Get familiar with npm workspaces](https://docs.npmjs.com/cli/v7/using-npm/workspaces#adding-dependencies-to-a-workspace)
- [react-app-rewire](https://www.npmjs.com/package/react-app-rewired)
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ule3knxb6gwtlpkhtozr.png)
- [Customize-cra](https://www.npmjs.com/package/customize-cra) takes advantage of `react-app-rewired`'s `config-overrides.js` file. By importing `customize-cra` functions and exporting a few function calls wrapped in our `override` function, you can easily modify the underlying config objects (`webpack`, `webpack-dev-server`, `babel`, etc.) that make up `create-react-app`.
I didn't bring up, how to set up CI/CD builds and configure TS/JS tools - eslint/tslint etc, or how to manage multiple run-time builds, and many other things, therefore any
additional customization depends on your needs, hence, there are no CLI solutions like CRA from scratch etc...
### See final source code [here](https://github.com/Madebyspeedster/monorepo_approaches)
Top comments (1)
Thanks Yuriy for sharing it. It was very helful.