Lerna Monorepo Mocha Setup (TypeScript)

In this article I show you how to setup a lerna TypeScript monorepo for testing with mocha (MochaJS).

Step 1. Install lerna globally

Install lerna globally using npm.

If you don’t have npm installed, click here.

  • Open up a terminal window
  • Run the following (the $ indicates that you are at a command line prompt - you should only type in what comes after the symbol):
$ npm install -g lerna 

Because the package is installed globally, you won’t need to do this again for new projects.

Step 2. Create a monorepo project

  • Change to a parent folder where you keep all of your projects
  • Create a folder for a new lerna monorepo project:
$ mkdir lerna-typescript-example
$ cd lerna-typescript-example

Step 3. Decide how you want to handle versioning

There are two ways to handle package versioning in lerna:

  • fixed - (the default) every package is published with the same version
    • for fixed versioning you would initialize with lerna init
  • independent - packages are published independently with their own version
    • for independent versioning you would initialize with lerna init --independent

The focus of this article is on testing and not publishing. So for this example you can just go with the default (fixed).

  • Initialize lerna:
$ lerna init
$ npm install
  • Verify that the following files and folders are listed:
lerna.json
node_modules
package-lock.json
package.json
packages

Step 4. Create a .gitignore file

This article won’t cover using git. But you should always start a project with a .gitignore file.

  • Create a new file called .gitignore
  • On a Mac you can use this command:
$ touch .gitignore
  • Open .gitignore in a text editor
  • Paste the following into the file and save it:
node_modules/
lerna-debug.log
npm-debug.log
packages/*/lib

Unlike a JavaScript lerna project, the TypeScript version does NOT save the lib folder. That’s because it is created when the code is transpiled.

Step 5. Install TypeScript

Install TypeScript and the supporting types package in the root of your project:

$ npm install typescript @types/node --save-dev

Step 6. Create a root tsconfig.json file

Because this is a TypeScript project, you will need to create a tsconfig.json file.

  • In the root of your project, create the file:
  • On a Mac you can do it like this:
$ touch tsconfig.json
  • Edit tsconfig.json in a text editor
  • Paste in this code and save it:
{
 "compilerOptions": {
   "module": "commonjs",
   "declaration": true,
   "noImplicitAny": false,
   "removeComments": true,
   "noLib": false,
   "emitDecoratorMetadata": true,
   "experimentalDecorators": true,
   "target": "es6",
   "sourceMap": true,
   "lib": [
     "es6"
   ]
 },
 "exclude": [
   "node_modules",
   "**/*.test.ts"
 ]
}

Step 7. Create a package

  • Create a new package called math-tools (replace YOUR-SCOPE with your npm scope, if any):
$ lerna create @YOUR-SCOPE/math-tools
  • Fill in the details (replace my scope (@mitchallen) with your scope, hit enter for defaults):
package name: (@mitchallen/math-tools)
version: (0.0.0) 
description: My math tools library
keywords: 
homepage: 
license: (ISC) 
entry point: (lib/math-tools.js) 
git repository: 

You should see a message about a new packages folder and child package.json file.

The path for the json file should be something like this:

  • lerna-typescript-example/packages/math-tools/package.json

The contents should be echoed to the console and look something like this:

{
  "name": "@mitchallen/math-tools",
  "version": "0.0.0",
  "description": "My math tools library",
  "author": "Mitch Allen",
  "homepage": "",
  "license": "ISC",
  "main": "lib/math-tools.js",
  "directories": {
    "lib": "lib",
    "test": "__tests__"
  },
  "files": [
    "lib"
  ],
  "publishConfig": {
    "access": "public"
  },
  "scripts": {
    "test": "echo \"Error: run tests from root\" && exit 1"
  }
}

Step 8. Create a child package tsconfig.json file

  • Make sure that you are in the new child package folder:
$ cd packages/math-tools
  • Create a child package tsconfig.json file that points back to the root parent file
  • On a Mac:
$ touch tsconfig.json
  • Open tsconfig.json in a text editor
  • Paste this code into the child package tsconfig.json file and save it:
{
 "extends": "../../tsconfig.json",
 "compilerOptions": {
   "outDir": "lib"
 },
 "include": [
   "src"
 ]
}

Step 9. Create a src folder

If this were a JavaScript project you would just edit .js files in the lib folder directly.

But since this is a TypeScript project you need to put your .ts files elsewhere.

When you run a build command (like tsc) the .ts files will be transpiled to .js. The new files will be written to the outDir, specified above in the config as lib.

The configuration above lists a src folder to include. That’s where the build process should look for your .ts files.

  • Create a src folder in the package folder (packages/math-tools):
$ mkdir src

Step 10. Create a TypeScript file

  • Create a .ts file in the src folder that matches the name of the main .js file that was specfied in package.json
  • For example in this package:
    • package.json main points to lib/math-tools.js
  • So then you should create src/math-tools.ts
$ touch src/math-tools.ts
  • When the package is transpiled lib/math-tools.js will be created
  • Open packages/math-tools/src/math-tools.ts in a text editor
  • Paste in the following and save it:
export const add = (a,b) => a + b;
export const subtract = (a,b) => a - b;

The package exports two simple functions to add and subtract values passed to them.

Step 11. Update package.json

  • Open the child package.json in a text editor and add the following:

  • typings in the form of lib/MAIN-FILE-NAME.d.ts:

"typings": "lib/math-tools.d.ts",
  • In the scripts section of package.json, add a build script:
"build": "tsc",

Step 12. Build the package

  • Save all files
  • Run this command:
    $ lerna run build
    
  • Verify that you see a success message for the number of packages built (for now should just be one)
  • Verify in the lib folder that these files were generated:
math-tools.d.ts
math-tools.js
math-tools.js.map

Step 13. Add a test script

  • Open package.json in a text editor
  • Replace the test script with this:
"test": "mocha -r ts-node/register __tests__/*.test.ts"

Step 14. Create a TypeScript test file

By default lerna create will have written a default .js file in the __tests__ folder.

  • Delete it:
$ rm __tests__/*.test.js
  • Create a new TypeScript version:
$ touch __tests__/math-tools.test.ts
  • Open __tests__/math-tools.test.ts in a text editor
  • Paste in this code and save it:
import {add, subtract} from '..';

var assert = require('assert');

describe('math-tools', function () {
  context('smoke test', function () {
    it('add should add two numbers together', function (done) {
      assert.strictEqual(add(100,200),300);
      done();
    });
    it('subtract should subtract one number from another', function (done) {
      assert.strictEqual(subtract(100,200),-100);
      done();
    });
  });
});

Step 15. Install mocha and ts-node

$ lerna add mocha --dev 
$ lerna add @types/mocha --dev
$ lerna add ts-node
$ lerna bootstrap --hoist

Step 16. Run the tests

Run the following:

$ lerna run test

Verify that the tests passed.

Step 17. Add a second package

  • Change to the root of the project:
$ cd ../..
  • Create a new project (replace YOUR-SCOPE with your npm scope, if any):
$ lerna create @YOUR-SCOPE/math-demo

Step 18. Add a dependency

The power of a monorepo is that you can create dependencies without linking or publishing.

In lerna you can create a dependency with a command formatted like this:

  • lerna add @YOUR-SCOPE/LIBRARY –scope=@YOUR-SCOPE/PACKAGE

  • Run this command (replacing my scope (@mitchallen) with yours):

$ lerna add @mitchallen/math-tools --scope=@mitchallen/math-demo
  • Verify this was added to the package.json for math-demo:
  "dependencies": {
    "@mitchallen/math-tools": "^0.0.0"
  }
  • Change to the new child package folder:
$ cd packages/math-demo/
  • Open package.json in a text editor and add the following:

  • typings in the form of lib/MAIN-FILE-NAME.d.ts:

"typings": "lib/math-demo.d.ts",
  • In the scripts section of package.json, add a build script:
"build": "tsc",
  • Save the file

  • Copy tsconfig.json from the math-tools package folder:

$ cp ../math-tools/tsconfig.json .
  • Create the src/math-demo.ts file
$ mkdir src
$ touch src/math-demo.ts
  • Open src/math-demo.ts in a text editor
  • Replace the code with the following and save it:
import {add, subtract} from '@mitchallen/math-tools';

export function demoFormula(a: number, b: number, c: number) {
  // a + b - c
  return subtract( add(a, b), c );
};
  • Run the build:
$ lerna run build
  • Verify there were no errors

Step 19. Add tests for the new package

  • Edit the child package.json
  • Replace the test script in package.json with this:
"test": "mocha -r ts-node/register __tests__/*.test.ts"
  • Replace the .test.js file with math-demo.test.ts:
$ rm __tests__/*.test.js
$ touch __tests__/math-demo.test.ts
  • Open __tests__/math-demo.test.ts in a text editor
  • Paste in this code:
import {demoFormula} from '..';

var assert = require('assert');

describe('math-demo', function () {
  context('smoke test', function () {
    it('should add two numbers and subtract a third', function (done) {
      const a = 100, b = 200, c = 300;
      const expectedResult = a + b - c;
      assert.strictEqual( demoFormula(a, b, c), expectedResult );
      done();
    });
  });
});

Run all tests with:

$ lerna run test

NOTE: If you try to run npm test you may run into module issues, but lerna run test will not.

To run the tests for just one package, use the scope parameter:

$ lerna run test --scope @mitchallen/math-tools

Build before testing

If there were code changes, you should alway build your packages before testing.

You can build and test with one line like this:

$ lerna run build && lerna run test

Troubleshooting

No rights to install lerna globally

  • On a Mac or Linux, try putting sudo in front of the command

Get the Error: run tests from root

  • This actually might mean that you forgot to add the test script for a package

Conclusion

In this article you learned how to:

  • Setup a lerna TypeScript monorepo for testing using mocha (MochaJS)
  • Add multiple packages
  • Add mocha for testing
  • Create dependencies between packages without the need for linking or publishing

References

  • Lerna - A tool for managing JavaScript projects with multiple packages [1]
  • Setting up a monorepo with Lerna for a TypeScript project - [2]
  • A Beginner’s Guide to Lerna with Yarn Workspaces - [3]



About the Author

Mitch Allen tests cloud services for a robotics company in New England.