1, the introduction

What Is a Monorepo? : Monorepo is a unified source code repository used by an organisation to host as much of its code as possible.

Monorepo is a way of managing team code that abandons the modular repository approach and instead manages as many modules as possible in one repository.

In the process of team development, we usually build multiple warehouses for different projects for management, but once there is A mutual reference relationship between each project, we will cause great trouble every time we modify. Let’s take A Package A Package and its dependent UI Package as an example, their development flow should be like this:

Of course, this process also involves the management of warehouse permissions and publish permissions. If Tom wants to test whether the UI Package Jerry is developing meets the requirements, he must get a test Package and NPM can install it locally to see the effect. If he still wants to test and console.log to see the effect, he can only ask Jerry to add the code and send a package again.

So what happens if we use Monorepo mode development:

Tom could just drag the branch Jerry was developing to the local area and write whatever he wanted.

Before you start using, listen to the downsides

  • As the project iterations, inMonorepoIn eachpackageIt’s going to be huge, and it’s going to be a challenge to build tools. (Existing solutions: Google’s, for exampleBazel, FacebookBuckAnd TwitterPantsBut they are not well supportedJSPackaging)
  • Because projects depend on each other, you must always ensure good code structure, compile specifications, and test cases.
  • In addition to the challenges to build tools, the IDE can break down when the project reaches a certain size.

Of course, well-known open source projects such as Babel, React, vue-Next, and others are using Monorepo for source management, so it’s a good time to get on the bus.

Why is Babel a monorepo?

Lerna & YARN Workspace — Mature Menorepo management solution

2-1. “Wuhu, Take off” — Project initialization

First let’s use lerNA to initialize the project:

// Step 1
npm install -g lerna

// Step 2
git init learn-lerna

// Step 3
cd learn-lerna && lerna init
Copy the code

After running, we see the following structure generated under the learn-lerna/ folder:

Json is the configuration file of Lerna, packages/ is the storage folder of the project. With a view to automating the build, create a new examples folder in the root directory. For the reasons and benefits of doing this, let’s take a look.

Open lerna.json and add some configuration:

["packages/*", "examples/*"], // current version number "version": The default value is NPM. Here we need to configure yarn" npmClient": "yarn", // Whether to use workspace working mode "useWorkspaces": true }Copy the code

For LERna, its main function is version control and distribution, so it also needs to work with NPM, YARN, and Git. Lerna also supports fixed/locked and independent modes:

  • Fixed mode (default) : All packages share a version number, which is recorded in the root directory after each releasechangelogIn the
  • Standalone mode: Each package manages its own version

Since UI Package and Utils Package are generally issued independently, we need to manually adjust the project to independent mode (just change the version in the configuration to independent). If you want to know more about the differences between the two modes, Refer to lerna’s official documentation for the distinction between fixed and independent schemas.

Although Lerna can do some dependent installs (in fact, the installation is handled by YARN or NPM), since it can’t do build, test, etc., we prefer it to focus on version control and distribution, and combine NPM and YARN to do other tasks

Next open package.json and configure our workspace:

{" name ":" root ", "private" : true, "devDependencies" : {" lerna ":" ^ 3.22.0 "}, / * new, define workspace * / "workspaces" : [ "packages/*", "examples/*" ] }Copy the code

In addition to being a package management tool, YARN takes longer to install dependencies on large projects than NPM. Yarn also supports Monorepo’s package management mode natively, which makes it easier to install dependencies (locally or remotely).

To sum up, we recommend lerna as the version release and control management tool, and YARN + Yarn Workspace for package management, build and test tools. See Why Lerna and Yarn Workspaces is a Perfect Match for Building Mono-repos? .

2-2, “up, down, left and right BABA” — establishment of multi-package dependencies

Next we initialize the UI and utils project in packegs/ and blog project in examples/ :

Then it’s time for our Yarn Workspace to step in and add dependencies for each package:

  • Adding external dependencies

    // add react react-dom --save yarn workspace blogCopy the code
  • Adding local dependencies

    Note that when adding local dependencies, we need to add the version number after the package, otherwise YARN will search the remote registry instead of the package in the workspace

    // Add local dependencies for blog. @zg/ UI is the package name yarn workspace blog add @zg/[email protected] --saveCopy the code
  • Adding global dependencies

    // Add dependency yarn for Babel dev globally add -w Babel --devCopy the code

To delete, change add to remove.

After these operations, we can find that all dependencies are not installed separately in each project, but in a unified root directory node_modules management, effectively avoiding the problem of repeated dependency installation. Our local dependencies correspond to individual projects in Packages/via symbolic links:

2-3. “Don’t stop before you drink” — the addition of construction tools

For the installation and configuration of Babel, there are various mature configurations on the web, which will not be described here. For Monorepo project, we can actually use the overrides attribute of Babel to carry out personalized configuration for each project:

//. Babelrc {"presets": ["@babel/preset-env", "@babel/preset-react"], // ["packages/ui"], "plugins": [ ["import", { "libraryName": "antd", "libraryDirectory": "es", "style": "css" }] ] }, { "test": ["packages/utils"], "plugins": [ ["module-resolver", { "alias": { "~": "./src/scripts" } }] ] } ] }Copy the code

We created two workspaces: Packages/and examples/. We store UI and UITLs in Packages/as static libraries, while in Examples/we are a SPA application blog. Since they are designed to reside in different workspaces, we can build with different tools for this separation. For SPA applications, we need tools that support hot module replacement (HMR) to help us develop, so we can use WebPack for packaging; For static library development, we choose rollupJS with faster packaging speed and smaller compression volume to build.

For more on the difference between Rollup and WebPack, poke this link: Rollup: Next Generation ES module packaging tool.

Rollupjs and WebPack configurations are not described here, but you can Google them. (Although rollupJS’s packaging analysis is much better and more intuitive than WebPack’s.)

Lerna and YARN Workspaces recommend that scripts be stored in the root directory scripts/ folder:

2-4. “Mission Complete” — Version update and release

  • Version update — Lerna version

    Lerna Version makes it easy to manage version submission records for individual packages in Packages /, while automatically generating Changelog in each package directory.

    Git cz git cz git Cz git CZ git CZ

    // Package version lerna version -- Xstraw-commits are automatically generated based on commit informationCopy the code

    Next, lerNA will convert according to the following rules according to our submission record:

    Submit type Version number conversion (version number isMAJOR.MINOR.PATCH)
    fix PATCHDistribution + 1
    feat MINORDistribution + 1
    BREAKING CHANGE Whatever type it is, it will be converted toMAJORDistribution + 1

    Note that:

    1. For Lerna, whether independent mode or fixed/locked mode is set, when a package changes, other packages that depend on that package will update their versions. The difference between the two modes is whether the MAJOR release has the same number.

    2. BREAKING CHANGE is written at the beginning of the body or footer of a Git commit.

      <type> (the < scope >) : < subject > / / short line < body > / / short line < footer >Copy the code

      If you submit using Git CZ, you can automatically select BREAKING CHANGE as prompted

  • Publish – lerna publish

    After doing the above, we have pushed the changes to the remote repository, and finally, the exciting moment has arrived.

    Of course, you need to choose an open source LICENSE and install the LICENSE in the root directory of your project.

    Then, if you publish scope packages in @xxx/some-package format, add publishConfig to package.json for each package:

    {" name ":" @ XXX/some - package ", "version" : "2.0.0", "main" : "index. Js", "license" : "MIT", / * the following is a new * / "publishConfig" : {// Specify scope package access permission not set to public, pay to upgrade NPM account! Npmignore "directory": ""}}Copy the code

    Finally, you need to use NPM login to login, or use NPM whoami to determine whether you have logged in successfully. If there is no problem, please solemnly type the following command:

    lerna publish from-package
    Copy the code

    Note: If you use itscope, please make sure that your organizations are availableNPMOr you may encounter error 403 as follows:

    If registered, let’s publish again:

    We have successfully published all the packages in the workspace to NPM.

    And you’re done! 🎇 🎉 🎈 🎈 🎈 🎉 🎇

3, summarize

The Concept of Monorepo really gives us a new way to think about development, and when it comes to pros and cons, it’s up to developers to weigh the pros and cons. In fact, the initial example of this article can also be solved by nPM-link based on Multirepo, but it is not as elegant as lerna + YARN Workspaces, which has a smooth build and debug process, as well as automatic Changelog and version update publishing.