Frontend Development 27 min read

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.

Taobao Frontend Technology
Taobao Frontend Technology
Taobao Frontend Technology
Mastering Monorepo: Boost Code Reuse and Collaboration in JavaScript Projects

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>.&#10;├── lerna.json&#10;├── package.json&#10;└── packages/ &#10;    ├── project_1/&#10;    │   ├── index.js&#10;    │   ├── node_modules/&#10;    │   └── package.json&#10;    ├── project_2/&#10;    │   ├── index.js&#10;    │   ├── node_modules/&#10;    │   └── package.json&#10;    ...</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 pin

in 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)

workspaces

feature.

Three steps are required:

Adjust the directory structure, placing related projects under a

packages

folder.

In the root

package.json

, add a

workspaces

field that points to the folder.

Set

private: true

in the root

package.json

to prevent accidental publishing.

After configuration, the directory looks like:

<code>.&#10;├── package.json&#10;└── packages/&#10;    ├── @mono/project_1/ &#10;    │   ├── index.js&#10;    │   └── package.json&#10;    └── @mono/project_2/&#10;        ├── index.js&#10;        └── package.json</code>

Running

npm install

or

yarn install

creates a single

node_modules

folder 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.json

with common settings, then in each sub‑project’s

tsconfig.json

use:

<code>{&#10;  "extends": "../tsconfig.setting.json",&#10;  "compilerOptions": {&#10;    "composite": true,&#10;    "outDir": "dist",&#10;    "rootDir": "src"&#10;  },&#10;  "include": ["src"]&#10;}</code>

3.3.2 ESLint

Place a root

.eslintrc

and let each sub‑project extend it:

<code>{&#10;  "extends": "../../.eslintrc",&#10;  "parserOptions": {&#10;    "project": "tsconfig.json"&#10;  }&#10;}</code>

3.3.3 Babel

Similarly, each sub‑project’s

.babelrc

can simply extend the root config:

<code>{&#10;  "extends": "../.babelrc"&#10;}</code>

Resulting project layout:

<code>.&#10;├── package.json&#10;├── .eslintrc&#10;└── packages/&#10;    ├── tsconfig.settings.json&#10;    ├── .babelrc&#10;    ├── @mono/project_1/&#10;    │   ├── index.js&#10;    │   ├── .eslintrc&#10;    │   ├── .babelrc&#10;    │   ├── tsconfig.json&#10;    │   └── package.json&#10;    └── @mono/project_2/&#10;        ├── index.js&#10;        ├── .eslintrc&#10;        ├── .babelrc&#10;        ├── tsconfig.json&#10;        └── package.json</code>

3.4 Unified scripts: scripty

Instead of duplicating

scripts

in every sub‑project, use

scripty

to define commands in separate files and reference them from

package.json

:

<code>{&#10;  ...&#10;  "scripts": {&#10;    "test": "scripty",&#10;    "lint": "scripty",&#10;    "build": "scripty"&#10;  },&#10;  "scripty": {&#10;    "path": "../../scripts/packages"&#10; },&#10;  ...&#10;}</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.json

is created:

<code>{&#10;  "packages": ["packages/*"],&#10;  "version": "0.0.0"&#10;}</code>

Typical adjustments:

<code>{&#10;  "packages": ["packages/*"],&#10;  "npmClient": "yarn",&#10;  "version": "independent",&#10;  "useWorkspaces": true&#10;}</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

--stream

provide more control.

3.5.2 Local npm registry: Verdaccio

Install Verdaccio globally (

npm install -g verdaccio

) and run

verdaccio

to start a local npm proxy at

http://localhost:4873

. Configure

.npmrc

to point to this registry for publishing with Lerna.

3.6 Commit message formatting

Use

commitlint

to 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>{&#10;  ...&#10;  "husky": {&#10;    "hooks": {&#10;      "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"&#10;    }&#10;  },&#10;  ...&#10;}</code>

Add

commitlint.config.js

with:

<code>module.exports = {&#10;  extends: [&#10;    "@commitlint/config-conventional",&#10;    "@commitlint/config-lerna-scopes"&#10;  ]&#10;};</code>

This ensures clear, expressive commit logs across all sub‑projects.

4. Migrating from multirepo to monorepo

Use

lerna import

for local projects (preserving commit history). For remote repositories, use the

tomono

shell script: create a

repos.txt

listing 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&#10;[email protected]/backend.git @mono/backend packages/backend&#10;[email protected]/frontend.git @mono/frontend packages/frontend&#10;[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.

FrontendjavascriptMonorepolernaYARNworkspacescommitlint
Taobao Frontend Technology
Written by

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.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.