With the increasing of team building and related businesses, more and more NPM packages need to be maintained by multiple people. As each project is related, it has to switch between multiple editors and debug through NPM link, which restricts the efficiency of development. Is there a way to solve the current pain point? The answer is Monorepo!

Bytedance’s internal encyclopedia entry defines Monorepo as follows:

Monorepo is a software development strategy that stores code for multiple projects in a repository.

At present, Lerna, as a multi-package manager of JavaScript projects, has been relatively mature and verified by modern enterprises. Therefore, a Lerna-based Monorepo management environment will be built step by step in the next step, hoping to help everyone to land in the business of each department and achieve cost reduction and efficiency improvement.

In my experience, Monorepo will significantly increase developer pleasure, so get started!

The main structure of this article is as follows, friends can eat as needed:

1. Why Lerna

If Monorepo can be defined as a policy, it must be a solution that can solve problems. The multi-package management mode of Monorepo based on Lerna implementation can solve the following problems (advantages) :

  1. flat: Under the same warehouse (project), the unified management and maintenance of multiplepackage
  2. concentrated: in the root directorynode_modules/Folder to maintain allpackageTripartite dependence of
  3. To simplify the: Execute commands uniformly according to file changes, send packages as required, automatically upgrade version, write back warehouse, typetag
  4. Efficiency: Interdependent projects can be directly correlated, avoiding developers switching between multiple warehouses

Of course, after a long time of use of Lerna, some problems are also exposed in the production environment, such as:

  1. Invalid build: Every time before the package will be on allpackageTo build
  2. Invalid rely on: All packages are installed each time you send thempackageThe dependency of
  3. Ghost rely on:Phantom dependenciesIn dependent ascension (hoist)

The problems listed here do not mean that Lerna should be abandoned, but that we should be clear about the advantages and disadvantages of the technical scheme and make some choices based on the actual situation of the project. The disadvantages mentioned above are not elegant in construction, but do not affect Lerna as a feasible Monorepo scheme.

Initialize a Monorepo project

We’ll build a clean lerna-based Monorepo project from zero to one, complete with ESlint validation for the team collaboration specification, Prettier automatic formatting, and git commit Message specification.

2.1 Initializing the project structure

First, install Lerna globally:

yarn global add lerna
// or
npm install lerna -g
Copy the code

Then create a new project directory and initialize a basic structure using Lerna

mkdir dyboy-lerna-project
cd dyboy-lerna-project/
lerna init --independent
Copy the code

After doing this, we have the following file directory structure:

. ├ ─ ─ lerna. Json// Lerna configuration file├ ─ ─ package. Json// A description file for the current project└ ─ ─ packages /// The folder where all packages are stored
Copy the code

When Lerna initializes the project, it appends a –independent parameter, which means to use independent mode.

In Lerna, there are two modes:

  1. Fixed modeAll:packageThe version number of each update is the same, and the package is issued in full
  2. Stand-alone mode: eachpackageThe version number is independent and does not affect each other. Each update will be delivered as required

Generally, we will choose the independent mode to avoid frequent package delivery under multiple packages, especially under some projects with frequent business changes, the package delivery pressure is terrible 😱.

2.2 Lerna + Yarn Workspaces

Lerna uses NPM as the default package manager, but using YARN as the default package manager for Lerna is more recommended.

Workspaces was already supported in Yarn 1.0, and its benefits and relationship to Lerna can be found in this article at the time: WorkSpaces

The combination of Yarn Workspaces makes Lerna scheme complement its shortcomings like adding wings to a tiger.

First, modify the lerna.json configuration to the following:

{ 
  "version": "independent"."npmClient": "yarn"."useWorkspaces": true
}
Copy the code

Then specify the workspaces field in the package.json file:

+ "workspaces": ["packages/*"],
Copy the code

After that, regardless of which folder we run Yarn, all dependencies in packages/ will be analyzed and installed to node_modules/ in the root directory.

2.3 ESlint + Prettier + Commit Rules

The above rules are uniform in any project. The configuration process is described in the previous article and will not be described here.

For details on how to initialize the configuration rules, see Steps 5 to 7 in “Build a TS+Rollup Initial Development Environment”.

After the above configuration, our project is roughly initialized!

2.4 NPM team account

Because sending packets requires an account, Monorepo managed several or dozens of packages at the same time, and all needed to maintain sending packets.

If you use your personal account to send a package to the Registry established by the company, the warehouse will become a “ghost warehouse” in case the student quits his job.

Of course, we can ask the Registry maintainer inside the company to change the corresponding package directly, but it is always a troublesome thing.

To do this, you can apply for a public account for the team, create a permission token through the NPM Token create, and put it in the.npmrc file in the project root directory.

After which no matter which developer maintenance, will default to use the team account to send package updates.

The final initialized project file structure is as follows:

Version release

As mentioned earlier, Lerna can centrally manage all packages, so we can specify shortcut instructions directly in the package.json file in the root directory to deliver packages on demand

Note: Lerna defaults to ignoring private packages with “private”: true set in package.json.

3.1 Project package compilation

Before the release of a new package version, it is generally needed to package and compile the product, in Monorepo under the release of multiple packages, certainly need to be packaged first.

(1). Learn Run

With the run command provided by Lerna, it is possible to have all projects with directives defined in package.json -> scripts execute this command before the package is sent

For example, run lerna exec build

Json -> scripts if build is defined, execute if it is, otherwise skip (run NPM run build on all packages that contain build).

One problem with this approach is that all pcKages are built before each package is shipped, increasing the time it takes to pack and publish.

Is there a more elegant way?

(2).npm Scripts lifecycle

The custom scripts field in the package.json file contains the default two life cycles pre and POST

By executing NPM run build, NPM run prebuid is automatically executed first, then NPM run build, and then NPM run postbuild.

In addition to the custom commands in package.json -> scripts, there are some scripts that come with NPM, such as NPM publish.

The life cycle of the NPM publish command includes:

  1. prepublishOnly
  2. prepare
  3. prepublish
  4. publish
  5. postpublish

Prepare will not be executed on NPM publish –dry-run.

Note: NPM 6.x and 7.x versions have different life cycles. The above version is 6.x. Considering the difference between 6.x and 7.x, it is recommended to place the action before sending the package into the prepublishOnly command.

For more information, see: scripts – NPM 6.x official documentation & Scripts – NPM 7.x Official documentation

(3). According to the need to Build

Json -> scripts for projects that need to be built at package time. Json -> scripts:

"scripts": {
    "build": "rollup -c"."prepublishOnly": "yarn build"
}
Copy the code

When NPM publish is performed, the yarn Build in prepublishOnly is executed, the project is compiled and packaged, and then the package is published.

In this way, on demand, the package can be very elegant, smooth done!

3.2 Project outsourcing

At the package delivery stage, we add something to the package.json file in the root directory:

"scripts": {
  "release": "lerna publish"."release:beta": "lerna publish --canary --pre-dist-tag=beta --preid beta --yes"
},
Copy the code

Yarn release Is used to release the official version

Yarn relase: Beta is used to release a test version for testing during development

Json -> name specifies the name of the package.json -> name field in the root directory. The default value is root. @dyboy/root to let other developers know that this is a scoped Monorepo project, even though the name field does nothing.

conclusion

The mental cost of the Monorepo project constructed based on Lerna is not high, but it requires us to have a certain understanding and grasp of the process, life cycle, NPM Scripts and other knowledge. It requires the builder to be able to find common requirements and constraint norms in process and management, so as to provide a solution to reduce cost and improve efficiency for the team.

This paper describes a solution of Monorepo based on the construction process. In front-end engineering, the builder also needs to consider whether there is room for optimization and consider details. For example, write a README. Md document that is easy to understand, think about how to make it easier for newcomers, try to solve problems in the process and actively explore new Monorepo technology solutions such as Rush, PNPM…

TODO: Can Monorepo automatically send the official version of the package? Interested friends can follow the follow-up (wechat official account: DYBOY) share!