Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to develop/build Javascript monorepo project like Visual Studio projects?

In a typical .NET Core project (with Visual Studio 2019) we have a structure like this:

Example/
|-- Example.Common/
|   |-- FileExample1.cs
|   |-- FileExample2.cs
|   `-- Example.Common.csproj
|-- Example.WebAPI/
|   |-- Controllers/
|   |-- Program.cs
|   |-- Startup.cs
|   `-- Example.WebAPI.csproj
|-- Example.CLI/
|   |-- Program.cs
|   `-- Example.CLI.csproj
|-- Example.sln

In this project, both Example.CLI and Example.Web.API references the Example.Common project. The Example.sln file have references to all three *.csproj and each csproj have its own dependencies, that can be of one of these 4 kinds:

<!-- It is a NuGet package (similar to Node.js npm packages) that always resolves from the public nuget.org registry -->
<PackageReference Include="Oracle.ManagedDataAccess.Core" Version="2.18.3" />

<!-- It is local, never resolves from registry -->
<ProjectReference Include="../Example.Common/Example.Common.csproj" />

<!-- Resolves from a machine-wide installed DLL -->
<Reference Include="Global.Dependency.Example" />

<!-- Resolves from a local DLL -->
<Reference Include="Local.Dependency.Example">
  <HintPath>path/to/Local.Dependency.Example.dll</HintPath>
</Reference>

When running local during development, if I change something in the Common source code and run the CLI project, it automatically rebuilds the Common and copy the DLL to the destination so the CLI can run a version with these latest changes. Even if I have more projects in the solution, it will just rebuild the projects that the CLI depends and if there are any changes on it since the last run.

When deploying, it rebuilds everything and resolve the local dependencies locally. My problem with Node.js and NPM packages is that:

  • When it gets easy for local development, it gets hard to deploy and generate the docker image.
  • When it gets easy to deploy and generate the docker image, it gets hard for local development.

I want do the same thing in a Node.js project, share the common source code to be used in web-api and cli, and I want to break each one of these components of the project in a npm package, so each package can have its own dependencies.

In a Node.js project, I have a similar structure:

example/
|-- common/
|   |-- file-example1.js
|   |-- file-example2.js
|   `-- package.json
|-- web-api/
|   |-- controllers/
|   |-- index.js
|   |-- routes.js
|   `-- package.json
|-- cli/
|   |-- index.js
|   `-- package.json
|-- package.json

The problem is how Node.js and npm/yarn/pnpm resolves the dependencies. I tried using Yarn Workspaces, Lerna, Lerna + Yarn Workspaces, but it seems that all these tools were made for packages that are published to registries, and not to help modularize a single project.

What I want is an easy way to do this:

  • When developing local, make changes in the common source code and run the web-api or cli with an updated version of common, without need to call yarn install every time or creating npm link manually
  • When deploying, resolve the dependencies using the local source code / build, and build it in the correct order

I tried:

  • Yarn link: protocol, works good for development, but when I run yarn install --production it tries to resolve using the registry. Won't work because any of my packages will never be publish to any registry.
  • NPM file: protocol, works good for deployment, but for development, when I make changes in the common package, I need to delete the common folder inside node_modules and run yarn install again. Even with file: protocol, I still need a way to build in the correct order, otherwise, npm/yarn would copy just the source code of the dependencies to the destination node_modules folder.
like image 879
lmcarreiro Avatar asked Feb 28 '26 09:02

lmcarreiro


1 Answers

Short answer:

lerna bootstrap
lerna run dev

bootstrap will install dependencies to the respective packages. Example: common should be installed in web-api. Lerna will install common in web-api as a node_module.

in package.json adding private: true will also ensure lerna publish will not publish those projects.


(Long answer:)

Given the folder directory

example/
|-- common/
|   |-- file-example1.js
|   |-- file-example2.js
|   `-- package.json
|-- web-api/
|   |-- controllers/
|   |-- index.js
|   |-- routes.js
|   `-- package.json
|-- package.json

1. Yarn workspaces.

example/package.json

{
  "private": true,
  "workspaces": ["common", "web-api"]
}

Initialise yarn in respective sub-folders.

Terminal

~/example         $ cd ./common
~/example/common  $ yarn init -y
~/example/common  $ cd ../web-api
~/example/web-api $ yarn init -y

in example/common/package.json

{
  "name": "common",
  "version": 1.0.0
  ...
}

in example/web-api/package.json

{
  "name" "web-api",
  "dependencies": {
    "common": "1.0.0"
  }
}

Terminal (whilst in web-api dir)

~/example/web-api $ yarn install

In the root directory there should be a node_modules which should include:

web-api/
common/

yarn will create a symlink between the common and webapi from the node_modules folder that is created at the root level.

However, yarnpkg convention is to use

{
  "private": true,
  "workspaces": ["packages/"]
}

Reference: Ben awad tutorial on yarn workspaces


2. lerna

Lerna uses yarn workspaces under the hood ref but you require a lerna.json config (which can specify the package manager you require npm, yarn...)

lerna.json

{
  "packages": [
    "packages/*"
  ],
  "version": "0.0.0"
}

There's extra commands in lerna such as:
- lerna diff common which will give you the git diff since the last commit, or. - lerna run test which will run the test script in each of your packages. (use --scope={common} to only target common test script).

In create-react-app they also include a "changelog" field which i assume would how people would automatically prefix their commit messages.

Reference: Ben awad tutorial on lerna

like image 84
Denis Tsoi Avatar answered Mar 02 '26 21:03

Denis Tsoi



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!