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:

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": truePrivate, which will not be published, is managed as a whole project, decoupled from being published to NPM"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
Copy the code

{
  "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

node_modules

3. Lerna + Monorepo Best practices

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 clone the project, use YARN install to create the project. Sometimes, postinstall hooks are needed to implement 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 # Equivalent to lerna bootstrap --npm-client yarn --use-workspaces
Copy the code

3.1.2 Clearing the Environment

In the case of a dependency mess or engineering mess, clean up the dependency

  • 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 # Clear all node_modules
yarn workspaces run clean # Clean all packages
Copy the code

3.1.3 install | delete dependent

  • Common project: Yarn add and YARN Remove can be used to install and remove dependent libraries

  • Monorepo: Generally, there are three scenarios

    • To install a dependency on a package, run the following command: Yarn Workspace packageB add packageA Install packageA as a dependency on 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 Upgrading and Sending packages

Once the project tests are complete, it’s time to release a version, which typically 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.2 Elegant submission

3.2.1 commitizen && cz – lerna – changelog

Commitizen is a tool for formatting Git Commit messages. It provides an inquisitiveway to get the required commit information.

Cz-lerna-changelog is a submission specification tailored specifically for LERNA projects. In the process of inquiry, there will be similar choices about which packages will be affected. As follows:

commitizen
cz-lerna-changelog

Because this is a development dependency for the entire project, install it in the root directory:

yarn add  -D commitizen
yarn add  -D cz-lerna-changelog
Copy the code

After the installation, add the config field in package.json to configure cz-Lerna-Changelog to Commitizen. Also, because Commitizen is not globally secure, you need to add scripts to execute git-cz

{
  "name": "root"."private": true."scripts": {
    "commit": "git-cz"
  },
  "config": {
    "commitizen": {
      "path": "./node_modules/cz-lerna-changelog"}},"devDependencies": {
    "commitizen": "^ 3.1.1." "."cz-lerna-changelog": "^ 2.0.2"."lerna": "^ 3.15.0"}}Copy the code

In normal development, you can use YARN Run commit to commit the code step by step as prompted.

3.2.2 commitlint && husky

Above we used Commitizen to regulate commit, but this depends on the development initiative to use YARN Run commit. What if you forget or just commit with Git commit? The answer is to verify the submission information at the time of submission, and if it does not meet the requirements, it will not be submitted and prompt. Commitlint performs the validation, and Husky specifies the timing. Husky inherits all hooks from Git, and when it triggers hooks, husky can prevent illegal commit,push, etc.

Install commitLint and the specifications to follow

yarn add -D @commitlint/cli @commitlint/config-conventional
Copy the code

Add a configuration file (commitlint.config.js) to the project root directory (commitlint.config.js) to specify specifications for commitLint

module.exports = { 
	extends: ['@commitlint/config-conventional']}Copy the code

Install the husky

yarn add -D husky
Copy the code

Add the following configuration to package.json

 "husky": { 
 		"hooks": { 
    		"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"}}Copy the code

Commit-msg “commit-msg” is the commitlit hook that git uses to verify the commit message when it is committed. After the installation and configuration is complete, you cannot commit using Git Commit or other third-party tools as long as the commit information does not comply with the specification. This constrains developers to commit using YARN Run commit.

3.2.3 eslint && lint – staged

In addition to the specification submission information, the code itself certainly lacks the specification to unify the style.

The installation

yarn add  -D standard lint-staged
Copy the code

Eslint is a complete set of JavaScript (typescript) code specifications that come with linter & code auto-correction. Automatically formats and fixes code, spotting style and programming problems in advance, and also supports typescript code specification validation, eslintrc.json configuration:

{
    "extends": [
        "yayajing"."plugin:@typescript-eslint/recommended"]."parser": "typescript-eslint-parser"."plugins": ["@typescript-eslint"]."rules": {
        "eqeqeq":"off"."@typescript-eslint/explicit-function-return-type": "off"."no-template-curly-in-string": "off"}}Copy the code

Lint-staged (STAGED) is a Git concept that represents a staging area, and Lint-staged means that only files in the staging area are checked and corrected. One is to improve the efficiency of the verification, and the other is to bring great convenience to the old project.

Package. The json configuration

// package.json
{
  "name": "root"."private": true."scripts": {
    "c": "git-cz"
  },
  "config": {
    "commitizen": {
      "path": "./node_modules/cz-lerna-changelog"}},"husky": {
    "hooks": {
      "pre-commit": "lint-staged"."commit-msg": "commitlint -E HUSKY_GIT_PARAMS"}},"lint-staged": {
    "*.ts": [
      "eslint --fix"."git add"]},"devDependencies": {
    "@commitlint/cli": "^ 8.1.0"."@commitlint/config-conventional": "^ 8.1.0"."commitizen": "^ 3.1.1." "."cz-lerna-changelog": "^ 2.0.2"."husky": "^ 3.0.0"."lerna": "^ 3.15.0"."lint-staged": "^ 9.2.0"}}Copy the code

After the installation is complete, add lint-staged configuration to package.json, as shown above, to perform ESLint –fix validation on js files in the staging area and automatically fix them. When to verify it, we’ll use the previous husky installation to add pre-commit hooks to the husky configuration to perform lint-Staged validation.

When the TS file is submitted, the error is automatically corrected and verified. It guarantees the uniform code style and improves the code quality.

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 LERna Version Updated version

  • 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 LERNA 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.

lerna version
lerna publish
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

Of course, the construction of a set of perfect warehouse management mechanism may not be measurable by some quantitative indicators, and there is no direct value output, but it can greatly improve work efficiency in daily work, liberate productivity, and save a lot of human costs.

In this paper, most content reproduced in: mp.weixin.qq.com/s/NlOn7er0i…