NPM Workspaces Monorepo Setup (JavaScript)
In this article, I show you how to set up a JavaScript monorepo using npm workspaces.
In this article, I show you how to set up a JavaScript monorepo using npm workspaces. With the latest versions of npm you no longer need third-party tools to manage your packages in one repo.
These instructions were written for a Mac.
What is an npm workspace?
From the online documentation (see references at the end of this article):
Workspaces is a generic term that refers to the set of features in the npm cli that provides support to managing multiple packages from your local file system from within a singular top-level, root package.
This set of features makes up for a much more streamlined workflow handling linked packages from the local file system. Automating the linking process as part of npm install
and avoiding manually having to use npm link
in order to add references to packages that should be symlinked into the current node_modules
folder.
We also refer to these packages being auto-symlinked during npm install
as a single workspace, meaning it's a nested package within the current local file system that is explicitly defined in the package.json
workspaces
configuration.
If you've ever wrestled with npm link to test one local package that depends on another local package, you may appreciate workspaces as an alternative.
Step 1. Install the latest npm and mocha globally
You must ensure that you have the latest version of npm installed in order to use workspaces.
If you don’t have npm installed, click here.
Upgrade to the latest version of npm and install the mocha testing framework globally:
- Open up a terminal window
- Run the following (only use sudo in the front if you have rights issues):
sudo npm install --location=global npm@latest mocha
That command installs the latest version of npm and mocha - a test automation utility.
Because the packages are installed globally, you won’t need to do this again for new projects. Though on occasion you might want to upgrade them to the latest version.
Step 2. Create a monorepo project
Create a new folder for your project:
- Change to a parent folder for all of your projects
- Create a new folder for a new monorepo / workspace project:
mkdir workspace-js
cd workspace-js
Step 3. Create a .gitignore file
To prevent build folders and files from being checked in:
- Create a new file called .gitignore
- On a Mac, you can use this command:
touch .gitignore
- Open .gitignore in a code editor
- Paste the following into the file and save it:
node_modules/
npm-debug.log
.DS_Store
Step 4. Initialize the project
To initialize the project with defaults, run this command in the project folder:
npm init -y
That will create a new package.json file that will look something like this:
{
"name": "workspace",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
Step 5. Add a package to the workspace
- Create a new package:
npm init -w ./packages/alpha
When prompted, add your scope to the package name. In my case, the package would be @mitchallen/alpha - change @mitchallen to whatever scope you want.
Respond to the prompts so the resulting package.json file looks like this:
{
"name": "@mitchallen/alpha",
"version": "0.0.1",
"description": "Test package alpha",
"main": "index.js",
"scripts": {
"mocha test/*.test.js"
},
"author": "Mitch Allen",
"license": "MIT"
}
This does the following:
- creates a new packages folder
- creates a new packages/alpha folder
- creates a new packages/alpha/package.json file and populates it with your answers
- adds a workspaces property in the root package.json file, referencing the alpha folder
You can see the results for yourself on a Mac with this command:
cat packages/alpha/package.json
If you open the root package.json you will see the new workspaces property:
"workspaces": [ "packages/alpha" ]
Step 6. Create a library
- Create a new index.js file in the package folder:
touch packages/alpha/index.js
- Replace the code in packages/alpha/index.js with this code and save it:
"use strict";
module.exports = {
add: (a,b) => a + b,
subtract: (a,b) => a - b
};
It’s a simple module that has two functions:
- add – returns the addition of two arguments
- subtract – returns the subtraction of one argument from another
Step 7. Public access
If you plan to publish the package at a later time, be sure to insert this into the package.json file.
"publishConfig": {
"access": "public"
},
You would need to do that for every package in the workspace.
Step 8. Verify package test script
- Check the scripts section of the alpha package.json file. It should look like this:
"scripts": {
"test": "mocha test/*.test.js"
},
Step 9. Write the alpha test cases
If there isn't a test folder under the alpha package, create it with this command:
mkdir packages/alpha/test
- Create a test file in that folder:
touch packages/alpha/test/smoke.test.js
- Edit packages/alpha/test/smoke.test.js
- Replace all code in the file with the following and save it:
'use strict';
var assert = require('assert');
const alpha = require('..');
describe('alpha', function () {
context('smoke test', function () {
it('add should add two numbers together', function (done) {
assert.strictEqual(alpha.add(100,200),300);
done();
});
it('subtract should subtract one number from another', function (done) {
assert.strictEqual(alpha.subtract(100,200),-100);
done();
});
});
});
- Run the test case with this command:
npm test -ws --if-present
Step 10. Create a second package
Create a second package called beta:
npm init -w ./packages/beta
- When prompted use your scope in the package name (like @mitchallen/beta)
- Answer the prompts so the beta package.json file looks something like this:
{
"name": "@mitchallen/beta",
"version": "0.0.1",
"description": "Test beta package",
"main": "index.js",
"devDependencies": {},
"scripts": {
"test": "mocha test/*.test.js"
},
"author": "Mitch Allen",
"license": "MIT"
}
- Create a new index.js file in the package folder:
touch packages/beta/index.js
- Replace the code in packages/beta/index.js with the code below and save it
- Replace @YOUR-SCOPE with the scope you used for the alpha package
"use strict";
var alpha = require('@YOUR-SCOPE/alpha');
module.exports = beta;
function beta(a,b,c) {
// return a + b - c
return alpha.subtract(alpha.add(a,b),c);
}
The purpose is to show how one package (beta) can depend on another package (alpha) in the same monorepo without explicit linking or publishing.
Step 11. Verify the package test script
- Check the scripts section of the beta package.json file. It should look like this:
"scripts": {
"test": "mocha test/*.test.js"
}
Step 12. Write the test cases
- Create a folder called test under the beta package folder:
mkdir packages/beta/test
- Create a file called smoke.test.js under the test folder:
touch packages/beta/test/smoke.test.js
- Edit packages/beta/test/smoke.test.js
- Replace all code in the file with the following and save it:
"use strict";
var assert = require('assert');
const beta = require('..');
describe('beta', function () {
context('smoke test', function () {
it('should add first two numbers and subtract the third', function (done) {
const a = 100, b = 200, c = 50;
const expected = a + b - c;
assert.strictEqual(beta( a, b, c ), expected );
done();
});
});
})
Step 13. Run the tests
Run the tests with the following command:
npm test -ws --if-present
You should see the mocha test results for your package.
To run the tests for each package individually (substituting with your scope):
npm test -w @mitchallen/alpha
npm test -w @mitchallen/beta
Step 14. Specify a dependency
Things work fine when testing inside a monorepo. But if you publish the individual packages they will need to know where to find and install their dependencies.
Run this command to make the alpha package a dependency for the beta package (substituting for your scope):
npm install @mitchallen/alpha -w @mitchallen/beta
Run this command to see that alpha is now a dependency in the beta package:
cat packages/beta/package.json
Here is what the dependency should look like:
"dependencies": {
"@mitchallen/alpha": "^0.0.1"
}
Whenever you make changes, you should run the tests again to make sure nothing broke:
npm test -ws --if-present
Example Repo
You can find an example of the repo created in this article here:
Conclusion
In this article, you learned how to:
- Setup a workspaces JavaScript monorepo for testing using mocha (MochaJS)
- Add multiple packages
- Add mocha for testing
- Create dependencies between packages without the need for linking or publishing
Reference
- workspaces | npm Docs - [1]