---
title: Lerna Monorepo
description: How to set up a monorepo with Piral and Lerna.
audience: Architects, Developers
level: Proficient
section: Details
---

# Setting up a Monorepo with Piral and Lerna

Let's start a new monorepo and add a Piral instance with some pilets.

## Lerna Setup

We begin with a new fresh directory:

```sh
git init
npm init -y
npx lerna init --packages="packages/*"
```

With these three commands we

1. Initialize a new git repository
2. Start a new npm project
3. Apply Lerna to the new npm project

You might be wondering "what is this Lerna"? Lerna is a Node.js command line tool that allows you to manage monorepos. Classically, monorepos might be difficult to deal with as you might need to jump to different directories, patch multiple files at once, cannot really reference other packages in the monorepo, or have trouble with versioning of these packages. Lerna helps you with all of that - and more.

At this point we should see four items in the directory:

1. *.git* (from the first command, the directory where git stores its information)
2. *lerna.json* (from the third command, the file configuring Lerna)
3. *package.json* (from the second command, patched in the third command)
4. *packages* (from the third command, a directory to store the Piral instance and pilets)

Great! In general, we recommend using Yarn workspaces with Lerna. This makes Lerna even more efficient and robust. Instead of having Lerna to deal with packages, Lerna is only a tool runner, while the heavy burden of package management goes to Yarn (v1), which is a proven and fast solution.

To achieve this we need two things:

1. Change the *lerna.json* to contain `"npmClient": "yarn"` and `"useWorkspaces": true`
2. Add `"workspaces": ["packages/*"]` to the *package.json* and make sure that the *package.json* contains `"private": true`

All in all these files should now look similar to the ones in the [monorepo sample](https://github.com/piral-samples/piral-monorepo-sample).

Let's now install the Piral CLI as a shared dev dependency for this monorepo:

```sh
yarn add piral-cli --dev
```

## Add a Piral Instance

Now let's add a Piral instance. Make a new directory *app-shell* (or whatever you want to call it - we'll refer to it as *app-shell* from here on) in the *packages* directory:

```sh
mkdir packages/app-shell
npx piral new --no-install --target packages/app-shell --npm-client lerna
```

Now that we scaffolded an app shell we can modify its *package.json* (located in *packages/app-shell*). We started with:

```json
{
  "name": "app-shell",
  "version": "1.0.0",
  "description": "",
  "keywords": [
    "piral"
  ],
  "dependencies": {
    "piral": "0.14.24"
  },
  "scripts": {
    "start": "piral debug",
    "build": "piral build"
  },
  "app": "./src/index.html",
  "devDependencies": {
    "@types/node": "latest",
    "@types/react": "^17",
    "@types/react-dom": "^17",
    "@types/react-router": "^5",
    "@types/react-router-dom": "^5",
    "piral-cli": "0.14.24",
    "typescript": "latest"
  }
}
```

and we can change it to:

```json
{
  "name": "app-shell",
  "version": "1.0.0",
  "description": "",
  "keywords": [
    "piral"
  ],
  "dependencies": {
    "piral": "0.14.24"
  },
  "scripts": {
    "start": "piral debug",
    "declaration": "piral declaration",
    "build": "piral build"
  },
  "app": "./src/index.html",
  "types": "./dist/index.d.ts",
  "devDependencies": {}
}
```

where we moved the `devDependencies` to the top-level (monorepo root) *package.json*, added a script to create the declaration ("index.d.ts") file, and referenced the file in the `types` field. We'll also need some *.gitignore* rule for omitting the `dist` folder, but let's focus on the npm work right now.

The monorepo root *package.json* should now look close to the following:

```json
{
  "name": "monorepo-tutorial",
  "private": true,
  "workspaces": ["packages/*"],
  "scripts": {
    "postinstall": "lerna run declaration"
  },
  "license": "MIT",
  "devDependencies": {
    "@types/node": "latest",
    "@types/react": "^17",
    "@types/react-dom": "^17",
    "@types/react-router": "^5",
    "@types/react-router-dom": "^5",
    "lerna": "^4.0.0",
    "piral-cli": "^0.14.24",
    "piral-cli-webpack5": "^0.14.24",
    "typescript": "latest"
  }
}
```

We moved over the `devDependencies` and added a `postinstall` script that just runs the declaration command in each package. We also added the `piral-cli-webpack5` package, which will be required for any bundling needs, such as debugging or building the Piral instance or pilets.

::: tip: Run commands
`lerna run` is a utility to run one command (if available) in all packages. Any package that has, e.g., a `declaration` script will run it when `lerna run declaration` is triggered. This is very handy for building - only specify a `build` command in each package and now you can build all packages by using `lerna run build`.
:::

Now it's time to resolve the dependencies in our monorepo. We should always use the install command of the package manager for this, e.g., for Yarn:

```sh
yarn install
```

If everything seems fine you can try to build your app shell for the first time:

```sh
npx lerna run build
```

This may take a while. To make the output more "interactive" you could use `--stream`, like this: `npx lerna run build --stream`. Otherwise, Lerna will only present the output when a task has completed.

Now may be a good time for your first commit. So make sure to also add a *.gitignore* file. The following content is appropriate for now, but you can (and should) anything that makes sense for your repository:

```plaintext
node_modules
dist
```

When adding more items to the monorepo it may make sense to follow a certain naming convention. As an example, we could suffix the pilets with `-pilet`. The exact convention is up to you - we will refer to the `-pilet` for our convention in this tutorial. Just make sure to stay consistent.

## Add a Pilet

The following command creates a new pilet called `foo-pilet`:

```sh
npx pilet new app-shell --no-install --target packages/foo-pilet --npm-client lerna
```

Like beforehand, we may want to fiddle with the scaffolded *package.json*. Originally, it may look like that:

```json
{
  "name": "foo-pilet",
  "version": "1.0.0",
  "description": "",
  "keywords": [
    "pilet"
  ],
  "importmap": {
    "imports": {},
    "inherit": ["app-shell"]
  },
  "dependencies": {},
  "devDependencies": {
    "@types/react": "^17",
    "@types/react-dom": "^17",
    "@types/react-router": "^5",
    "@types/react-router-dom": "^5",
    "@types/node": "latest",
    "typescript": "latest",
    "history": "latest",
    "react": "^17",
    "react-dom": "^17",
    "react-router": "^5",
    "react-router-dom": "^5",
    "tslib": "^2",
    "app-shell": "1.0.0",
    "piral-cli": "^0.14.24"
  },
  "scripts": {
    "start": "pilet debug",
    "build": "pilet build",
    "upgrade": "pilet upgrade"
  },
  "main": "dist/index.js",
  "files": [
    "dist"
  ],
  "source": "src/index.tsx",
  "piral": {
    "comment": "Keep this section to use the Piral CLI.",
    "name": "app-shell"
  }
}
```

So we could modify it to be:

```json
{
  "name": "foo-pilet",
  "version": "0.0.0",
  "description": "",
  "keywords": [
    "pilet"
  ],
  "importmap": {
    "imports": {},
    "inherit": ["app-shell"]
  },
  "dependencies": {},
  "devDependencies": {
    "history": "latest",
    "react": "^17",
    "react-dom": "^17",
    "react-router": "^5",
    "react-router-dom": "^5",
    "tslib": "^2",
    "app-shell": "1.0.0"
  },
  "scripts": {
    "start": "pilet debug",
    "build": "pilet build",
    "upgrade": "pilet upgrade"
  },
  "main": "dist/index.js",
  "files": [
    "dist"
  ],
  "source": "src/index.tsx",
  "piral": {
    "comment": "Keep this section to use the Piral CLI.",
    "name": "app-shell"
  }
}
```

We adjusted the version number to be aligned with the other packages and removed the duplicated `devDependencies`.

With this in mind we can now add a few more convenience scripts to the monorepo root *package.json*:

```json
{
  "name": "monorepo-tutorial",
  "private": true,
  "workspaces": ["packages/*"],
  "scripts": {
    "start": "pilet debug packages/*-pilet",
    "build": "lerna run build",
    "build:shell": "lerna run build --stream --scope app-shell",
    "build:pilets": "lerna run build --scope *-pilet",
    "postinstall": "lerna run declaration"
  },
  "license": "MIT",
  "devDependencies": {
    "@types/node": "latest",
    "@types/react": "^17",
    "@types/react-dom": "^17",
    "@types/react-router": "^5",
    "@types/react-router-dom": "^5",
    "lerna": "^4.0.0",
    "piral-cli": "^0.14.24",
    "piral-cli-webpack5": "^0.14.24",
    "typescript": "latest"
  }
}
```

The `build` and `build:shell`/`build:pilets` scripts are all convenience scripts to avoid calling `lerna run ...` all the time. With these, you can just run

```sh
yarn build:pilets
```

to build all pilets. Likewise, just by calling `yarn start`, you can now debug *all* your pilets at once. If you only want to debug a single one you can still do that. For instance, for *foo-pilet* you could do:

```sh
lerna run start --scope foo-pilet --stream
```

## Conclusion

Setting up a monorepo with Piral using Lerna requires some additional knowledge. Nevertheless, using the right steps and keeping an eye on things such as versioning, `dependencies` and `devDependencies` it is possible to come up with a decent solution that scales really well. What we did not cover is the CI/CD aspect, which will certainly be more complex to implement correctly than in the independent repositories case.

In the next tutorial, we'll look into the details of setting up a monorepo using Rush.
