Mastering Monorepo: Boost Code Reuse and Collaboration in JavaScript Projects
This article explains the monorepo strategy, its advantages and drawbacks, and provides step‑by‑step guidance on setting up a project‑level monorepo using tools like Volta, Yarn workspaces, Lerna, scripty, and commitlint, helping developers streamline code reuse, dependency management, and version synchronization across multiple JavaScript packages.
0. Introduction
During recent development I faced a situation where project A depends on an already released project B, but I often need to modify B’s code without publishing it. I needed a way to keep changes in B synchronized with A during development and to handle version upgrades gracefully after A goes live. The best solution I found is the monorepo strategy.
1. What is the monorepo strategy?
Monorepo is a software development strategy that stores the code of multiple projects in a single repository ("mono" meaning single, "repo" short for repository). Major companies such as Google, Facebook, and open‑source projects like Babel use monorepo to manage their code.
Babel uses the monorepo strategy to manage its code.
The article explores the benefits of monorepo for code managers and developers, and how to practice it in daily work.
2. Advantages and disadvantages of monorepo
A typical monorepo directory structure looks like this:
<code>. ├── lerna.json ├── package.json └── packages/ ├── project_1/ │ ├── index.js │ ├── node_modules/ │ └── package.json ├── project_2/ │ ├── index.js │ ├── node_modules/ │ └── package.json ...</code>2.1 Advantages
Easy code reuse : With all projects in one repository, shared components or tools can be extracted and referenced via TypeScript, Lerna, or other tools.
Simplified dependency management : Because inter‑project references are internal, it is easy to track which projects are affected by a change and to automate version bumps.
Convenient refactoring : Developers can see the impact range of a change and run unified tests, encouraging continuous optimization.
Promotes open, transparent, and shared culture : Team members are encouraged to view and modify each other’s code, fostering responsibility for maintenance and testing, which improves overall code quality.
2.2 Disadvantages
Complex project‑level permission management : VCS systems often lack satisfactory solutions for fine‑grained permissions within a monorepo.
Higher onboarding cost for new developers : Newcomers must understand the relationships among many sub‑projects, which may require additional documentation effort.
Large‑scale monorepos need specialized VFS and refactoring tools : Companies like Google can afford dedicated infrastructure; smaller teams may find the overhead prohibitive.
2.3 Summary: How to choose?
There is no silver bullet. Successful monorepo adoption depends on team schedule, organizational culture, and individual influence. Even if a full monorepo is impractical, applying the strategy at the project level can still capture most benefits without the heavy cost of a company‑wide monorepo.
3. Monorepo implementation
3.1 Environment lock: Volta
Volta is a JavaScript tool manager that locks Node, npm, and Yarn versions per project. After installing Volta, run
volta pinin the project root; the specified versions will be used automatically, even if the global versions differ.
3.2 Reusing packages: workspaces
Using monorepo yields two major benefits:
Avoid duplicate package installations, saving disk space and reducing build time.
Internal code can reference each other like regular npm modules.
These benefits are achieved with Yarn (>=1.0) or npm (>=7.0)
workspacesfeature.
Three steps are required:
Adjust the directory structure, placing related projects under a
packagesfolder.
In the root
package.json, add a
workspacesfield that points to the folder.
Set
private: truein the root
package.jsonto prevent accidental publishing.
After configuration, the directory looks like:
<code>. ├── package.json └── packages/ ├── @mono/project_1/ │ ├── index.js │ └── package.json └── @mono/project_2/ ├── index.js └── package.json</code>Running
npm installor
yarn installcreates a single
node_modulesfolder at the root, containing both shared and project‑specific packages, allowing cross‑project imports.
3.3 Unified configuration: ESLint, TypeScript, Babel
To avoid repetition, place shared configuration files at the repository root and let each sub‑project extend them.
3.3.1 TypeScript
Create
packages/tsconfig.setting.jsonwith common settings, then in each sub‑project’s
tsconfig.jsonuse:
<code>{ "extends": "../tsconfig.setting.json", "compilerOptions": { "composite": true, "outDir": "dist", "rootDir": "src" }, "include": ["src"] }</code>3.3.2 ESLint
Place a root
.eslintrcand let each sub‑project extend it:
<code>{ "extends": "../../.eslintrc", "parserOptions": { "project": "tsconfig.json" } }</code>3.3.3 Babel
Similarly, each sub‑project’s
.babelrccan simply extend the root config:
<code>{ "extends": "../.babelrc" }</code>Resulting project layout:
<code>. ├── package.json ├── .eslintrc └── packages/ ├── tsconfig.settings.json ├── .babelrc ├── @mono/project_1/ │ ├── index.js │ ├── .eslintrc │ ├── .babelrc │ ├── tsconfig.json │ └── package.json └── @mono/project_2/ ├── index.js ├── .eslintrc ├── .babelrc ├── tsconfig.json └── package.json</code>3.4 Unified scripts: scripty
Instead of duplicating
scriptsin every sub‑project, use
scriptyto define commands in separate files and reference them from
package.json:
<code>{ ... "scripts": { "test": "scripty", "lint": "scripty", "build": "scripty" }, "scripty": { "path": "../../scripts/packages" }, ... }</code>Scripts can be organized under
scripts/packages(project‑level) and
scripts/workspaces(global).
3.5 Unified package management: Lerna
Lerna simplifies managing many inter‑dependent packages. After
npx lerna init, a
lerna.jsonis created:
<code>{ "packages": ["packages/*"], "version": "0.0.0" }</code>Typical adjustments:
<code>{ "packages": ["packages/*"], "npmClient": "yarn", "version": "independent", "useWorkspaces": true }</code>Key Lerna commands:
lerna bootstrap– links local packages and installs dependencies.
lerna run– executes a script in all packages respecting dependency order.
lerna exec– runs any command in each package.
lerna publish– publishes changed packages.
lerna add– adds a dependency between packages.
Advanced options such as
--concurrency,
--scope, and
--streamprovide more control.
3.5.2 Local npm registry: Verdaccio
Install Verdaccio globally (
npm install -g verdaccio) and run
verdaccioto start a local npm proxy at
http://localhost:4873. Configure
.npmrcto point to this registry for publishing with Lerna.
3.6 Commit message formatting
Use
commitlintto enforce conventional commit messages (e.g.,
feat:,
fix:,
chore:,
refactor:,
style:). Install with:
<code>npm i -D @commitlint/cli @commitlint/config-conventional @commitlint/config-lerna-scopes commitlint husky lerna-changelog</code>Configure Husky in
package.json:
<code>{ ... "husky": { "hooks": { "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" } }, ... }</code>Add
commitlint.config.jswith:
<code>module.exports = { extends: [ "@commitlint/config-conventional", "@commitlint/config-lerna-scopes" ] };</code>This ensures clear, expressive commit logs across all sub‑projects.
4. Migrating from multirepo to monorepo
Use
lerna importfor local projects (preserving commit history). For remote repositories, use the
tomonoshell script: create a
repos.txtlisting repo URLs, names, and target paths, then run
cat repos.txt | ~/tomono/tomono.sh.
<code>// 1. Git repo URL 2. Sub‑project name 3. Destination path [email protected]/backend.git @mono/backend packages/backend [email protected]/frontend.git @mono/frontend packages/frontend [email protected]/mobile.git @mono/mobile packages/mobile</code>5. Conclusion
The article covered what monorepo is, its pros and cons, and detailed best‑practice steps for setting up a project‑level monorepo with Volta, Yarn workspaces, Lerna, scripty, and commitlint. Even if a full monorepo is not feasible now, the tools and ideas presented can improve current workflows.
Taobao Frontend Technology
The frontend landscape is constantly evolving, with rapid innovations across familiar languages. Like us, your understanding of the frontend is continually refreshed. Join us on Taobao, a vibrant, all‑encompassing platform, to uncover limitless potential.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.