This is the third day of my participation in the August More text Challenge. For details, see:August is more challenging

1. Monorepo management

For students who maintain multiple packages (with similar functions), they will encounter a multiple choice question: should these packages be maintained in one warehouse or maintained separately in multiple warehouses? Multirepo is more traditional, where each package is managed with a separate repository. Monorepo is a way of managing project code by managing multiple modules/packages in a project repository (REPo), rather than the common practice of having a repo per module.

There are many large open source projects that use this approach, such as Babel, React, Meteor, Ember, Angular,Jest, Umijs, Vue, creation-React-app, React-Router, etc. Almost all the warehouses we are familiar with without exception adopt the monorepo method. It can be seen that the content of the first level directory of these projects is mainly scaffolding, and the main content is managed in packages directory, which is divided into multiple packages.

The directory structure is as follows:

├ ─ ─ packages | ├ ─ ─ two packages pkg1 | | ├ ─ ─ package. The json | ├ ─ ─ pkg2 | | ├ ─ ─ package. The json ├ ─ ─ package. The jsonCopy the code

The main benefits of Monorepo are unified workflow and Code Sharing. For example, if I want to look at the code of a Pacakge and understand a piece of logic, I don’t need to find its repo, I just go to the current repo. When a requirement changes multiple Pacakge, you do not need to go to each repo to change, test, release, or NPM link. Instead, you can change the pacakge in the current repo, test, and release the pacakge in the same way. You can manage (build, test, release) multiple packages by simply building a set of scaffolding.

A picture is worth a thousand words:

Of course, which management style is better is a matter of opinion. While the former allows for diversification (projects can have their own build tools, dependency management strategies, and unit testing methods), the latter wants to centralize management and reduce the communication costs associated with differences between projects.

While dismantling a molecular repository and dismantling a molecular NPM package is a natural way to isolate a project, there is no more efficient way to debug when repository contents are associated than to keep the source code together.

Combining the practical scenarios and business needs of the shop-Service portal, the natural MonoRepo! An ideal development environment can be abstracted like this:

“You only care about the business code, you can reuse directly across the business regardless of how you reuse it, and everything is in the source code when you debug it.”

In a front-end development environment, multiple Git repos and multiple NPMS are ideal for this resistance, resulting in reuse concerns about version numbers and debugging requiring NPM links. These are MonoRepo’s greatest strengths.

The use of relevant tools mentioned above is the protagonist of today’s Lerna! Lerna is the most well-known Monorepo management tool in the industry with complete functions.

2.lerna

Lerna is a tool for managing multiple NPM modules. It is a project that Babel uses to maintain its own Monorepo and open source. Optimize the workflow for maintaining multiple packages to solve the problem that multiple packages depend on each other and that publishing requires manual maintenance of multiple packages.

2.1 installation

A global installation is recommended because the LERna command is often used

npm i -g lerna
Copy the code

2.2 Initializing the project

lerna init
Copy the code

Package. json & lerna.json:

/ / package. Json {" name ":" root ", "private" : true, / / private and will not be released, is to manage the whole project, and to publish to NPM decoupling "devDependencies" : {" lerna ": "^ 3.15.0"}} / / lerna. Json {" packages ": [" packages / *"], "version" : "0.0.0"}Copy the code

2.3 Creating an NPM package

Add two packages

lerna create @mo-demo/cli
lerna create @mo-demo/cli-shared-utils
Copy the code

2.4 Adding Module Dependencies

Add dependent modules to the respective package

Lerna add Semver --scope @mo-demo/cli-shared-utils // yes @mo-demo/cli-shared-utils adds semver module lerna add @mo-demo/cli-shared-utils --scope @mo-demo/cli // adds dependencies between internal modulesCopy the code

2.5 release

lerna publish
Copy the code

2.6 Dependency Package Management

Steps 1-5 have covered the entire life cycle of Lerna, but when we maintain the project, we need to install dependencies for each package after the new repository code is pulled down.

We also found in step 4 lerna add that the packages installed for a package are placed in the node_modules directory of the package directory. In this way, packages that multiple packages depend on will be installed multiple times by multiple packages, and node_modules will be maintained under each package, which is not clean. So we use –hoist to raise the dependencies under each package to the project root to reduce installation and management costs.

lerna bootstrap –hoist

To save the trouble of having to enter the –hoist parameter every time, you can set it in lerna.json:

{" packages ": [" packages / *"], "command" : {" bootstrap ": {" hoist" : true}}, "version" : "0.0.1 - alpha. 0"}Copy the code

After the configuration, if the dependencies have already been installed in each package, we just need to clean up the installed dependencies:

lerna clean
Copy the code

Then run lerna bootstrap to see that the package’s dependencies are installed in the root directory of node_modules.

3. Lerna + monorepo practice

Lerna is not responsible for building, testing and other tasks. It proposes a directory mode of centralized management of package and provides a set of automated management program, so that developers do not need to go deep into specific components to maintain content, and they can control the whole project in the root directory. Based on NPM scripts, users can complete component construction in a good way. Code formatting and other operations. Let’s take a look at best practices for building Monorepo projects based on Lerna in combination with other tools.

The most common Monorepo solution is the Workspaces feature of Lerna and Yarn, based on the Monorepo workflow of Lerna and Yarn workspace. Since FUNCTIONS of YARN and LERNA overlap, we use yarn to deal with dependency problems and LERNA to deal with release problems. Do with YARN what can be done with YARN

3.1 yarn workspace

3.1.1 Setting up the Environment Common projects: After the clone project is installed, use YARN install to set up the project. Sometimes, postinstall hooks are required for automatic compilation or other Settings.

Monorepo: There are dependencies between libraries. For example, A depends on B. Therefore, we usually need to link B to node_module of A

Solution: In the workspace, yarn install automatically helps troubleshoot installation and link problems

Yarn install # is equivalent to lerna bootstrap --npm-client yarn --use-workspacesCopy the code

3.1.2 Cleaning the Environment In the case of dependencies or engineering chaos, clean up dependencies

Normal projects: simply remove node_modules and the compiled artifacts.

Monorepo: Not only do you need to remove root’s node_modules build artifacts, but you also need to remove each package’s node_modules and build artifacts

Solution: Use lerna clean to remove all node_modules, and use Yarn workspaces run clean to perform all package cleanup

# lerna clean clean up all the node_modules yarn machine-specific run clean # perform all installation package of the clean operating 3.1.3 | delete rely on common project: Yarn Add and YARN Remove are used to solve the installation and removal of dependency libraries

Monorepo: Generally, there are three scenarios

To install dependencies on a package:

yarn workspace packageB add packageA 
Copy the code

Install packageA as a dependency of packageB

Install dependencies for all packages: Use Yarn workspaces add lodash to install dependencies for all packages

Install dependencies on root: Common development tools such as typescript are installed on root. We use yarn add-w-d typescript to install dependencies on root

The corresponding three scenarios have the following deletion dependencies

yarn workspace packageB remove packageA
yarn workspaces remove lodash
yarn remove -W -D typescript
Copy the code

3.1.4 Project construction

Common project: Create a build NPM script and use YARN build to complete the project construction

Monorepo: What distinguishes a normal project is that there are dependencies between packages, such as packageB that can only be built after packageA is built, otherwise it will be wrong, which actually requires us to build in a topological order.

We can build our own topological collation rules. Unfortunately, the Yarn workspace does not support topological collation commands, even though the RFC has been accepted. Fortunately, LERNA supports topological collation, and the –sort parameter controls the execution of commands in topological collation

lerna run --stream --sort build
Copy the code

3.1.5 After the completion of the project test, version release is involved, which generally involves the following steps

Conditional validation: such as whether validation tests are passed, whether uncommitted code exists, and whether release operations are performed on the main branch

Version_bump: The version number needs to be updated when the version is released. In this case, how to update the version number is a problem.

Generate Changelog: In order to easily view the functions solved by each version of each package, we need to generate a changelog for each package to facilitate users to view the functional changes of each version.

Generate Git Tags: It is usually necessary to create a Git tag for each version to facilitate subsequent rollback and troubleshooting

Git releases: Each release requires a separate COMMIT record to mark the milestone

Release the NPM package: After you release Git, you need to release the updated version to NPM for external users

We found it cumbersome and error-prone to perform these operations manually, but fortunately LERNA was able to help us solve these problems

Yarn does not officially support the release process. It only needs to provide the package management tool. Therefore, lerNA is required to support this part

Lerna provides the Publish and Version functions to support version upgrade and release. The publish function can either include version work or simply publish.

3.3 Publishing Automatically generated Logs

With the previous specification submissions in place, automatic log generation comes naturally. Take a closer look at what lerna Publish does:

3.3.1

Find the packages that have changed since the last release

Prompt the developer to determine the version number to release

Update the version field of package.json in all updated packages

Update the dependency version numbers in packages that depend on the updated package

Update the version field in lerna.json

Commit the above changes with a tag

Push to a Git repository

 

3.3.2 Use NPM Publish to push new releases to NPM

CHANGELOG clearly corresponds to version one by one, so it is necessary to look at the description of LERna version command, and you will see a configuration parameter called Conventional Commits. Yes, as long as we submit according to the specification, the current version of CHANGELOG will be automatically generated during lerNA version process. For convenience, instead of having to input parameters every time, you can configure them in lerna.json, as follows:

{ "packages": [ "packages/*" ], "command": { "bootstrap": { "hoist": true }, "version": { "conventionalCommits": True}}, "ignoreChanges" : / / *. * * md ", "version" : "0.0.1 - alpha. 1"}Copy the code

The LERna version will detect changes since the last release, but there are some file commits that we do not want to trigger version changes. For example, a change to the.md file does not actually cause a change in package logic and should not trigger version changes. This can be excluded using the ignoreChanges configuration. As above.

The actual LERNA version is rarely used directly because it is included in LerNA Publish.

3.4 Perfect test cases

Monorepo project: There are two ways to test

Unified JEST test configuration is used to facilitate global jEST running. The advantage is that it is convenient to calculate the test coverage of all codes. The disadvantage is that if the package is relatively heterogeneous (such as small programs, front-end, node server, etc.), unified test configuration is not easy to write

Each package supports the test command independently. If you run the YARN workspace run test command, the disadvantage is that the test coverage of all codes cannot be collected in a unified manner

If you use JEST to write test cases that support typescript, you need to initialize jest.config.js:

module.exports = {
preset: 'ts-jest',
moduleFileExtensions: ['ts'],
testEnvironment: 'node'
}
Copy the code

4 Practice Summary

At this point, you have basically built the best practices for monorepo projects based on Lerna and Yarn Workspace.

Perfect workflow

Typescript support

Uniform style coding

Complete unit testing

One-click publishing mechanism

Perfect update log