Source: jsilvax. A Beginner’s Guide to Lerna with Yarn Workspaces. Oct/6/2018

When combined, Lerna and Yarn Workspaces simplify and optimize the management of multi-package warehouses. Lerna makes versioning and publishing packages to NPM Org an easy experience by providing useful utility commands to handle task execution across multiple packages. Yarn Workspaces manages our dependencies. Instead of requiring multiple node_modules directories, it intelligently optimizes dependencies, installs them all together, and allows cross-linking dependencies in a monorePO. Yarn Workspaces provides tools like Lerna to manage multi-package warehouses.

To get started, let’s enable Yarn Workspaces

yarn config set workspaces-experimental true
Copy the code

Now we can illustrate these concepts by creating a simulation project

mkdir my-design-system && cd my-design-system
Copy the code

Then, we initialize the project

yarn init
Copy the code

And add Lerna as a development dependency.

yarn add lerna --dev
Copy the code

You will then want to initialize Lerna, which will create a lerna.json and a package directory

lerna init
Copy the code

To set Lerna to enable the Yarn workspace, we need to configure lerna.json. Let’s add YARN as our NPM client and specify that we use the YARN workspace. In this tutorial, we will version our packages independently.

// lerna.json
{
  "packages": ["packages/*"]."version": "independent"."npmClient": "yarn"."useWorkspaces": true
}
Copy the code

At this point we should have only one root package.json. In the root package.json, we need to add workspaces and private to true. Setting private to true will prevent the root project from being published to NPM.

// package.json
{
  "name": "my-design-system"."private": true."workspaces": [
     "packages/*"]}Copy the code

The process of creating a new package

You need to create a new package in the package directory. Let’s create a mock form package

cd packages
Copy the code

Once we are in the correct directory, we can create and CD it into our new package

mkdir my-design-system-form && cd my-design-system-form
Copy the code

We then create a new package.json by running YARN Init

yarn init
Copy the code

The name of the new package should follow our NPM Org scope naming, for example: @my-scope-name. It is also important that the new package starts with a version like 0.0.0, because once we make our first release with Lerna, it will be released as 0.1.0 or 1.0.0.

// package.json
{
  "name": "@my-scope-name/my-design-system-form"."version" : "0.0.0"."main" : "index.js"
}
Copy the code

If you have an NPM Org account that supports private packages, you can add the following to your module’s separate package.json.

"publishConfig": {
    "access": "restricted"
}
Copy the code

Add local sibling dependencies to a specific package

Now that we know the process for creating a new package, let’s say our final structure looks like this.

my-design-system/
    packages/
        my-design-system-core/
        my-design-system-form/
        my-design-system-button/
Copy the code

If we want to add my-design-system-button dependencies to my-design-system-form and have Lerna symlink them, we can do this by CD to the package.

cd my-design-system-form 
Copy the code

Then run the following.

lerna add @my-scope-name/design-system-button --scope=@my-scope-name/my-design-system-form
Copy the code

This will update the package.json of @my-scope-name/my-design-system-form. Our package.json should look like this.

// package.json
{
  "name": "@my-scope-name/my-design-system-form"."version": "1.0.0"."main": "index.js"."dependencies": {
    "@my-scope-name/my-design-system-button": "^ 1.0.0"}}Copy the code

Now, you can reference this local dependency in index.js, as in

import Button from '@my-scope-name/my-design-system-button';
Copy the code

Add a “common” dependency for all packages

This is similar to the previous command. But this is for /packages/*. It doesn’t matter whether the dependency you want to add is a local sibling or a dependency from NPM.

lerna add the-dep-name
Copy the code

If you have common development dependencies, it is best to specify them in root package.json for workspace. For example, dependencies can be Jest, Husky, Storybook, Eslint, Prettier, and so on

yarn add husky --dev -W
Copy the code

* Add the -w flag to make it clear that we want to add dependencies to the workspace root.

Delete rely on

If you have a dependency that all packages use but you want to remove, Lerna has the exec command, which can run an arbitrary command in each package. With this knowledge, we can use exec to remove all package dependencies.

lerna exec -- yarn remove dep-name
Copy the code

Run the test

Lerna provides the run command, which runs the NPM script in each package that contains it. For example, suppose that all of our packages follow the my-design-system-form structure.

my-design-system-form/
    __tests__/
        Form.test.js
Copy the code

In each package.json, we have an NPM script to test.

"name": "@my-scope-name/my-design-system-form"."scripts": {
    "test": "jest"
}
Copy the code

Lerna can then be executed by running each test script.

lerna run test --stream
Copy the code

*-stream This flag provides the output of the child process.

Published to the NPM

manual

First, you need to make sure you’re logged in. You can verify that you are logged in by doing the following.

npm whoami // myusername
Copy the code

If you are not logged in, run the following and follow the instructions.

npm login
Copy the code

Once logged in, you can publish by running Lerna.

lerna publish
Copy the code

Lerna will prompt you for the updated version number.

automatic

Lerna supports automatic semantic versioning in CI environments using Conventional Commits Standard. This enables developers to commit as follows

git commit -m "fix: JIRA-1234 Fixed minor bug in foo"
Copy the code

Then in a CI environment, the version of the package can be updated and published to NPM based on the above commit. This can be done by configuring your CI environment.

lerna publish --conventional-commits --yes 
Copy the code

If you don’t want to pass flags, you can add the following to your lerna.json file.

"command": {
    "publish": {
       "conventionalCommits": true."yes": true}}Copy the code

Execute Conventional Commits

If you want to enforce Conventional Commits, I recommend adding Commitlint to the ROOT of the project.

yarn add @commitlint/cli @commitlint/config-conventional husky cross-env --dev
Copy the code

Then create a publishing script in the root package.json

"scripts": {
    "release": "cross-env HUSKY_BYPASS=true lerna publish"
}
Copy the code

The release script will run in a CI environment. Note that we configured the traditional commit and “yes “flag in the lerna.json file. Since the CI environment will commit the version changes, we don’t want to trigger the commit message inting. We do this by adding an environment variable called HUSKY_BYPASS, which we will set to true using cross-env. We also need to add further configuration in root package.json.

"husky": {
    "hooks": {
    "commit-msg": "[[ -n $HUSKY_BYPASS ]] || commitlint -E HUSKY_GIT_PARAMS"}},"commitlint": {
    "extends": ["@commitlint/config-conventional"]}Copy the code

For husKY, we added a commitlint/ config-Conventional commit-msg hook, which checks for the HUSKY_BYPASS environment variable we added above. If this variable is false, Commitlint/config-Conventional to simplify the commit message.

Separate version control from release

If for any reason you want full control over version control, Lerna has the ability to split version control and publishing into two commands. You can run it manually.

lerna version
Copy the code

Then update each version number as prompted. You can then have a step to read the latest tags (which are manually updated) published to NPM.

lerna publish from-git --yes
Copy the code

Local development with multiple contributors

You must run the YARN command every time a new contributor makes a Git clone of your project, or you need to pull the latest changes to your team.

yarn
Copy the code

In most Lerna tutorials, the use of the Lerna bootstrap command is advocated, however this is unnecessary and redundant when enabling the YARN workspace

lerna bootstrap when you're using Yarn workspaces is literally redundant? All lerna bootstrap --npm-client yarn --use-workspaces (CLI equivalent of your lerna.json config) does is call yarn Install in the root. -- Issue 1308Copy the code

For more information, see github.com/lerna/lerna…

Local development across projects

In our case, we are building a multi-package design system. If developers want to create a new component in the design system, but also test it in a local client application before publishing, they can do so by using yarn’s link command.

Symlink to establish local dependencies

Suppose we want to use our native My-design-system-core in our my-client-app. Let’s CD the package we’re going to use for another project.

cd ~/path/to/my-design-system/my-design-system-core
Copy the code

Then we create a Symlink

yarn link
Copy the code

You should see this output

success Registered "@my-scope-name/my-design-system-core".
info You can now run `yarn link "@my-scope/my-design-system-core"` in the projects where you want to use this module and it will be used instead.
Copy the code

Now that our package has a symbolic link, we can use it in our my-client-app.

cd ~/path/to/my-client-app 
yarn link @my-scope-name/my-design-system-core
Copy the code

Any changes in/Packages /my-design-system-core are reflected in my-client-app. Now, developers can easily develop locally on both projects and see it reflected.

Links to remove local dependencies

When the developer is finished and no longer wants to use the native package, we need to unlink it. CD into the package we want to unlink

cd ~/path/to/my-design-system/my-design-system-core
Copy the code

Run the unlink command to delete the local symlink

yarn unlink
Copy the code

You’ll see something like this output

success Unregistered "@my-scope-name/my-design-system-core".
info You can now run `yarn unlink "@my-scope-name/my-design-system-core"` in the projects where you no longer want to use this module.
Copy the code

Now, we can CD into our my-client-app and unlink.

cd ~/path/to/my-client-app
yarn unlink @my-scope-name/my-design-system-core
Copy the code

conclusion

Lerna is a good combination with Yarn Workspaces. Lerna adds utility functionality to Yarn Workspaces for handling multiple packages. The yarn workspace allows all dependencies to be installed together, making caching and installation faster. It allows us to easily publish dependencies on NPM with a single command, automatically update package.json for sibling dependencies when their version changes, and generally make installation, versioning, and publishing a painless experience.