Using Lerna to manage SPFx projects with library components

Hydra is attacking porcupine? Well, actually not. Because Hydra is Lerna.js and porcupine is a SharePoint Framework solution with library components. Most likely you've heard about SharePoint Framework and library component, but not about Lerna. Lerna is

A tool for managing JavaScript projects with multiple packages.

Sounds simple, but probably still not very clear. First of all, it works only with JavaScript (and of course, TypeScript, for simplicity I use JavaScript term) projects. Some companies have JavaScript projects with lots if modules developed internally or publicly (it doesn't matter) in separate git repositories. These modules usually reference each other in the corresponding package.json files. Making changes across different modules is an extremely difficult and messy task. To solve these and other issues, some companies organize their projects in multi-packaged repositories (i.e. one git repository with many JavaScript packages):

Lerna is a tool that optimizes the workflow around managing multi-package repositories with git and npm.

Now it becomes a little bit clear what is Lerna. However, how does it correlate with SharePoint Framework and library components?

Imagine you have a solution with SharePoint Framework web parts and you want to add a new library component to share code across web parts. A new library component always creates a new JavaScript package (package.json). In the end, you will have two JavaScript packages in your repository - one for SharePoint Framework web parts and another one for the library component. So you have a multi-package repository and it's a good place to add Lerna to reduce the mess!

Actually, you're not limited in using Lerna with library components only. If you have a few separate SharePoint Framework solutions in one git repository, you can add Lerna to simplify package management.

Lerna is not the only tool of such matter in the world. There is also an alternative from Microsoft called Rush. If you want to learn more, check out this awesome post by Microsoft MVP Vardhaman Deshpande - Using Microsoft Rush to manage SPFx projects with library components. Lerna has a bit longer history, thus more stars and downloads. The purpose of this post is to add more pieces info the picture of managing multi-package SharePoint Framework repositories with different third-party tools.

The source code for this post is available here at GitHub

Lerna.js

How Lerna helps you to manage JavaScript repositories with multiple packages? It gives a few pretty good things:

  1. You can install dependencies for all your JavaScript modules using just one command, you don't have to think about dependencies in each project individually 
  2. With Lerna, you can move your common dependencies on the top level to save space (sometimes it doesn't work, read further notes on SharePoint Framework)
  3. Lerna automatically manages dependencies between your modules using symlinks
  4. You can run npm scripts from multiple JavaScript projects simultaneously 
  5. Lerna manages the publishing of changed modules

Who uses Lerna?

A lot of companies and open source projects. The most interesting:

Things to keep in mind when working with Lerna

  1. Lerna works only with JavaScript projects and only with git repositories.
  2. Lerna bootstrap command with --hoist option (moves common dependencies to the root node_modules folder) doesn't work with SharePoint Framework. And in general, it's recommended to use this option with caution

Project setup

For our project, we need to folders - one with SharePoint Framework solutions and one with SharePoint Framework library components. Every solution is a package, every library component is a package. Schematically it looks like on the image below:

Create a git repository

Create a new git repository to store all the library components along with SharePoint Framework solutions. 

Init library component

Inside a newly created git repository create a new folder spfx-library. This is a generic folder which might act as a container for multiple library components. 

Create new folder utils inside spfx-library. Init library component inside the utils project. For that purpose run:

yo @microsoft/sharepoint --plusbeta --skip-install

Take a note on --plusbeta (library components is in preview on a moment of writing) and --skip-install because Lerna will manage dependencies for us. Please pick options like on the image below: 

Init SharePoint Framework solution

Inside git repository root, create a new folder spfx-apps. This is a generic folder which might act as a container for multiple SharePoint Framework solutions.

Create a new folder org-app and initialize a new SharePoint Framework solution inside: 

yo @microsoft/sharepoint --plusbeta --skip-install

This time your options for yeoman will look like:

This is how your resulting project structure looks like in VSCode: 

Next, we need to add a dependency to our org-app on our library component. To do that, open spfx-apps\org-app\package.json and add "spfx-utils": "^0.0.1" line under "dependencies" node:

Now it's time to add Lerna into play.

Add Lerna

First of all, install Lerna globally:

npm install lerna -g

In your root of git repository run 

lerna init

It initializes a new Lerna project in your folder. Open lerna.json (created by the lerna init) and change it to be:

{
  "packages": [
    "spfx-library/**",
    "spfx-apps/**"
  ],
  "version": "0.0.0"
}

What does it mean? It tells Lerna that we have two folders with potential packages inside. Lerna automatically searches all JavaScript projects inside specified folders. Read more about lerna.json here

Run 

lerna bootstrap

That's one of the core Lerna commands. It restores dependencies in all found JavaScript projects, takes care about cross-dependencies via symlinks. Everything without user interaction. Nice!

IMPORTANT: By default, Lerna installs dependencies in each project's node_modules folder. You can move some common package outside with --hoist parameter, but it doesn't work with SharePoint Framework. And in general, it's recommended to use this option with caution

To test everything works run 

lerna run build

This command is equivalent to 

npm run build

which runs in each individual repository. You can use --parallel flag to run it guess how - yes, in parallel:

Start development

To make development maximum convenient possible, let's make some updates to our library component and SharePoint Framework web part. We're going to have "gulp serve" process running simultaneously for all packages. Once you change something in library component, SharePoint Framework projects immediately receive an update and refresh workbench. 

Open spfx-library\utils\config\serve.json and change the default port. As said we're running serve process, they should use different ports. Technically we don't need any ports and web servers for library component, but it uses it anyway. So change it to be, say 4322

{
  "$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
  "port": 4322,
  "https": true
}

Now open spfx-library\utils\package.json and add a new npm script called serve, so your scripts section will look like below:

"scripts": {
    "build": "gulp bundle",
    "clean": "gulp clean",
    "test": "gulp test",
    "serve": "gulp serve --nobrowser"
  },

Open spfx-apps\org-app\package.json and add almost the same serve command (however without --nobrowser): 

"scripts": {
    "build": "gulp bundle",
    "clean": "gulp clean",
    "test": "gulp test",
    "serve": "gulp serve"
  },

Finally, we're ready to start development. Simply run 

lerna run --parallel serve

See how it works in action. When you have changed the library component, it will refresh your workbench:

Useful links: